[picard] Update to 0.16 (#752860). Update plugins, add titlesort, titleversion plugins.

alexlan alexlan at fedoraproject.org
Mon Nov 14 23:47:00 UTC 2011


commit b5e8859a206af38951dd36c2c424491d34ecfe8f
Author: Alex Lancaster <alexlan[AT]fedoraproject org>
Date:   Mon Nov 14 18:45:15 2011 -0500

    Update to 0.16 (#752860).  Update plugins, add titlesort, titleversion plugins.

 .gitignore        |    1 +
 __init__lastfm.py |   78 +++++++++++++++++++++++++++++++++-------------------
 addrelease.py     |   78 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 picard.spec       |   14 ++++++++-
 sources           |    2 +-
 titlesort.py      |   59 ++++++++++++++++++++++++++++++++++++++++
 titleversion.py   |   27 ++++++++++++++++++
 7 files changed, 227 insertions(+), 32 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index b143f75..0df7454 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 picard-0.15beta1.tar.gz
 /picard-0.15.1.tar.gz
+/picard-0.16.tar.gz
diff --git a/__init__lastfm.py b/__init__lastfm.py
index b13ad98..a2cc147 100644
--- a/__init__lastfm.py
+++ b/__init__lastfm.py
@@ -3,17 +3,31 @@
 PLUGIN_NAME = u'Last.fm'
 PLUGIN_AUTHOR = u'Lukáš Lalinský'
 PLUGIN_DESCRIPTION = u'Use tags from Last.fm as genre.'
-PLUGIN_VERSION = "0.2"
-PLUGIN_API_VERSIONS = ["0.9.0", "0.10", "0.15"]
+PLUGIN_VERSION = "0.4"
+PLUGIN_API_VERSIONS = ["0.15"]
 
-from PyQt4 import QtGui, QtCore
-from picard.metadata import register_album_metadata_processor, register_track_metadata_processor
+from PyQt4 import QtCore
+from picard.metadata import register_track_metadata_processor
 from picard.ui.options import register_options_page, OptionsPage
 from picard.config import BoolOption, IntOption, TextOption
 from picard.plugins.lastfm.ui_options_lastfm import Ui_LastfmOptionsPage
 from picard.util import partial
+import traceback
 
+LASTFM_HOST = "ws.audioscrobbler.com"
+LASTFM_PORT = 80
+
+# From http://www.last.fm/api/tos, 2011-07-30
+# 4.4 (...) You will not make more than 5 requests per originating IP address per second, averaged over a
+# 5 minute period, without prior written consent. (...)
+from picard.webservice import REQUEST_DELAY
+REQUEST_DELAY[(LASTFM_HOST, LASTFM_PORT)] = 200
+
+# Cache for Tags to avoid re-requesting tags within same Picard session
 _cache = {}
+# Keeps track of requests for tags made to webservice API but not yet returned (to avoid re-requesting the same URIs)
+_pending_xmlws_requests = {}
+
 # TODO: move this to an options page
 TRANSLATE_TAGS = {
     "hip hop": u"Hip-Hop",
@@ -25,7 +39,6 @@ TITLE_CASE = True
 
 def _tags_finalize(album, metadata, tags, next):
     if next:
-        album._requests += 1
         next(tags)
     else:
         tags = list(set(tags))
@@ -36,14 +49,14 @@ def _tags_finalize(album, metadata, tags, next):
             metadata["genre"] = tags
 
 
-def _tags_downloaded(album, metadata, min_usage, ignore, next, current, data, http, error):
+def _tags_downloaded(album, metadata, min_usage, ignore, next, current, data, reply, error):
     try:
         try: intags = data.toptags[0].tag
         except AttributeError: intags = []
         tags = []
         for tag in intags:
             name = tag.name[0].text.strip()
-            try: count = int(tag.count[0].text.strip(), 10)
+            try: count = int(tag.count[0].text.strip())
             except ValueError: count = 0
             if count < min_usage:
                 break
@@ -51,8 +64,20 @@ def _tags_downloaded(album, metadata, min_usage, ignore, next, current, data, ht
             except KeyError: pass
             if name.lower() not in ignore:
                 tags.append(name.title())
-        _cache[str(http.currentRequest().path())] = tags
+        url = str(reply.url().path())
+        _cache[url] = tags
         _tags_finalize(album, metadata, current + tags, next)
+
+        # Process any pending requests for the same URL
+        if url in _pending_xmlws_requests:
+            pending = _pending_xmlws_requests[url]
+            del _pending_xmlws_requests[url]
+            for delayed_call in pending:
+                delayed_call()
+
+    except:
+        album.tagger.log.error("Problem processing downloaded tags in last.fm plugin: %s", traceback.format_exc())
+        raise
     finally:
         album._requests -= 1
         album._finalize_loading(None)
@@ -60,18 +85,20 @@ def _tags_downloaded(album, metadata, min_usage, ignore, next, current, data, ht
 
 def get_tags(album, metadata, path, min_usage, ignore, next, current):
     """Get tags from an URL."""
-    try:
-        if path in _cache:
-            _tags_finalize(album, metadata, current + _cache[path], next)
+    url = str(QtCore.QUrl.fromPercentEncoding(path))
+    if url in _cache:
+        _tags_finalize(album, metadata, current + _cache[url], next)
+    else:
+
+        # If we have already sent a request for this URL, delay this call until later
+        if url in _pending_xmlws_requests:
+            _pending_xmlws_requests[url].append(partial(get_tags, album, metadata, path, min_usage, ignore, next, current))
         else:
+            _pending_xmlws_requests[url] = []
             album._requests += 1
-            album.tagger.xmlws.get("ws.audioscrobbler.com", 80, path,
-                partial(_tags_downloaded, album, metadata, min_usage, ignore, next, current),
-                position=1)
-    finally:
-        album._requests -= 1
-        album._finalize_loading(None)
-    return False
+            album.tagger.xmlws.get(LASTFM_HOST, LASTFM_PORT, path,
+                                   partial(_tags_downloaded, album, metadata, min_usage, ignore, next, current),
+                                   priority=True, important=True)
 
 
 def encode_str(s):
@@ -83,13 +110,13 @@ def encode_str(s):
 def get_track_tags(album, metadata, artist, track, min_usage, ignore, next, current):
     """Get track top tags."""
     path = "/1.0/track/%s/%s/toptags.xml" % (encode_str(artist), encode_str(track))
-    return get_tags(album, metadata, path, min_usage, ignore, next, current)
+    get_tags(album, metadata, path, min_usage, ignore, next, current)
 
 
 def get_artist_tags(album, metadata, artist, min_usage, ignore, next, current):
     """Get artist top tags."""
     path = "/1.0/artist/%s/toptags.xml" % (encode_str(artist),)
-    return get_tags(album, metadata, path, min_usage, ignore, next, current)
+    get_tags(album, metadata, path, min_usage, ignore, next, current)
 
 
 def process_track(album, metadata, release, track):
@@ -107,12 +134,9 @@ def process_track(album, metadata, release, track):
             else:
                 get_artist_tags_func = None
             if title and use_track_tags:
-                func = partial(get_track_tags, album, metadata, artist, title, min_tag_usage, ignore_tags, get_artist_tags_func, [])
+                get_track_tags(album, metadata, artist, title, min_tag_usage, ignore_tags, get_artist_tags_func, [])
             elif get_artist_tags_func:
-                func = partial(get_artist_tags_func, [])
-            if func:
-                album._requests += 1
-                tagger.xmlws.add_task(func, position=1)
+                get_artist_tags_func([])
 
 
 class LastfmOptionsPage(OptionsPage):
@@ -124,7 +148,6 @@ class LastfmOptionsPage(OptionsPage):
     options = [
         BoolOption("setting", "lastfm_use_track_tags", False),
         BoolOption("setting", "lastfm_use_artist_tags", False),
-        #BoolOption("setting", "lastfm_use_artist_images", False),
         IntOption("setting", "lastfm_min_tag_usage", 15),
         TextOption("setting", "lastfm_ignore_tags", "seen live,favorites"),
         TextOption("setting", "lastfm_join_tags", ""),
@@ -138,7 +161,6 @@ class LastfmOptionsPage(OptionsPage):
     def load(self):
         self.ui.use_track_tags.setChecked(self.config.setting["lastfm_use_track_tags"])
         self.ui.use_artist_tags.setChecked(self.config.setting["lastfm_use_artist_tags"])
-        #self.ui.use_artist_images.setChecked(self.config.setting["lastfm_use_artist_images"])
         self.ui.min_tag_usage.setValue(self.config.setting["lastfm_min_tag_usage"])
         self.ui.ignore_tags.setText(self.config.setting["lastfm_ignore_tags"])
         self.ui.join_tags.setEditText(self.config.setting["lastfm_join_tags"])
@@ -146,12 +168,10 @@ class LastfmOptionsPage(OptionsPage):
     def save(self):
         self.config.setting["lastfm_use_track_tags"] = self.ui.use_track_tags.isChecked()
         self.config.setting["lastfm_use_artist_tags"] = self.ui.use_artist_tags.isChecked()
-        #self.config.setting["lastfm_use_artist_images"] = self.ui.use_artist_images.isChecked()
         self.config.setting["lastfm_min_tag_usage"] = self.ui.min_tag_usage.value()
         self.config.setting["lastfm_ignore_tags"] = unicode(self.ui.ignore_tags.text())
         self.config.setting["lastfm_join_tags"] = unicode(self.ui.join_tags.currentText())
 
 
 register_track_metadata_processor(process_track)
-#register_album_metadata_processor(process_album)
 register_options_page(LastfmOptionsPage)
diff --git a/addrelease.py b/addrelease.py
new file mode 100644
index 0000000..131b8f2
--- /dev/null
+++ b/addrelease.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+
+PLUGIN_NAME = u"Add Cluster As Release"
+PLUGIN_AUTHOR = u"Lukáš Lalinský, Philip Jägenstedt"
+PLUGIN_DESCRIPTION = ""
+PLUGIN_VERSION = "0.2"
+PLUGIN_API_VERSIONS = ["0.9.0", "0.10", "0.15.0"]
+
+from picard.cluster import Cluster
+from picard.util import webbrowser2
+from picard.ui.itemviews import BaseAction, register_cluster_action
+
+import codecs
+import os
+import tempfile
+
+HTML_HEAD = """<!doctype html>
+<meta charset="UTF-8">
+<title>Add Cluster As Release</title>
+<form action="http://musicbrainz.org/release/add" method="post">
+"""
+HTML_INPUT = """<input type="hidden" name="%s" value="%s">
+"""
+HTML_TAIL = """<input type="submit" value="Add Release">
+</form>
+<script>document.forms[0].submit()</script>
+"""
+HTML_ATTR_ESCAPE = {
+    "&": "&amp;",
+    '"': "&quot;"
+}
+
+class AddClusterAsRelease(BaseAction):
+    NAME = "Add Cluster As Release..."
+
+    def callback(self, objs):
+        if len(objs) != 1 or not isinstance(objs[0], Cluster):
+            return
+        cluster = objs[0]
+
+        (fd, fp) = tempfile.mkstemp(suffix=".html")
+        f = codecs.getwriter("utf-8")(os.fdopen(fd, "w"))
+
+        def esc(s):
+            return "".join(HTML_ATTR_ESCAPE.get(c, c) for c in s)
+        # add a global (release-level) name-value
+        def nv(n, v):
+            f.write(HTML_INPUT % (esc(n), esc(v)))
+
+        f.write(HTML_HEAD)
+
+        nv("artist_credit.names.0.artist.name", cluster.metadata["artist"])
+        nv("name", cluster.metadata["album"])
+
+        for i, file in enumerate(cluster.files):
+            try:
+                i = int(file.metadata["tracknumber"]) - 1
+            except:
+                pass
+            try:
+                m = int(file.metadata["discnumber"]) - 1
+            except:
+                m = 0
+
+            # add a track-level name-value
+            def tnv(n, v):
+                nv("mediums.%d.track.%d.%s" % (m, i, n), v)
+
+            tnv("name", file.metadata["title"])
+            if file.metadata["artist"] != cluster.metadata["artist"]:
+                tnv("artist_credit.names.0.name", file.metadata["artist"])
+            tnv("length", str(file.metadata.length))
+
+        f.write(HTML_TAIL)
+        f.close()
+        webbrowser2.open("file://"+fp)
+
+register_cluster_action(AddClusterAsRelease())
diff --git a/picard.spec b/picard.spec
index fd66606..4729fb2 100644
--- a/picard.spec
+++ b/picard.spec
@@ -2,7 +2,7 @@
 %{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
 
 Name:             picard
-Version:          0.15.1
+Version:          0.16
 Release:          1%{?dist}
 Summary:          MusicBrainz-based audio tagger
 Group:            Applications/Multimedia
@@ -30,8 +30,11 @@ Source14:          http://users.musicbrainz.org/~luks/picard-plugins/replaygain/
 Source15:	   http://users.musicbrainz.org/~luks/picard-plugins/featartistsintitles.py
 Source16:	   http://users.musicbrainz.org/~luks/picard-plugins/release_type.py
 Source17:	   http://users.musicbrainz.org/~luks/picard-plugins/no_release.py
+Source18:	   http://gitorious.org/musicbrainz/addrelease/blobs/raw/master/addrelease.py
+Source19:	   http://github.com/voiceinsideyou/creaps-picard-plugins/raw/master/titlesort.py
+Source20:	   http://github.com/voiceinsideyou/creaps-picard-plugins/raw/master/titleversion.py
 
-# addrelease.py and bonusdisc.py not yet ported
+# bonusdisc.py not yet ported
 # search plugins are obsolete
 
 License:          GPLv2+
@@ -91,6 +94,9 @@ install -pm 0644 %{SOURCE14} ${PLUGINDIR}/replaygain/
 install -pm 0644 %{SOURCE15} ${PLUGINDIR}
 install -pm 0644 %{SOURCE16} ${PLUGINDIR}
 install -pm 0644 %{SOURCE17} ${PLUGINDIR}
+install -pm 0644 %{SOURCE18} ${PLUGINDIR}
+install -pm 0644 %{SOURCE19} ${PLUGINDIR}
+install -pm 0644 %{SOURCE20} ${PLUGINDIR}
 
 
 echo %{fedora}
@@ -115,6 +121,10 @@ rm -rf $RPM_BUILD_ROOT
 %{python_sitearch}/picard/*
 
 %changelog
+* Mon Nov 14 2011  <alex at sup35.ccdom.wi.mit.edu> - 0.16-1
+- Update to 0.16
+- Update plugins, add titlesort, titleversion plugins.
+
 * Sun Aug 21 2011 Alex Lancaster <alexlan[AT]fedoraproject org> - 0.15.1-1
 - Update to 0.15.1
 - Add more plugins
diff --git a/sources b/sources
index bbc7497..37a3097 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-21b51e229c246e2d17b746649bfa370f  picard-0.15.1.tar.gz
+5791a5ae1ce92ac1ffb1cc8f15917ad7  picard-0.16.tar.gz
diff --git a/titlesort.py b/titlesort.py
new file mode 100644
index 0000000..d65e0da
--- /dev/null
+++ b/titlesort.py
@@ -0,0 +1,59 @@
+PLUGIN_NAME = 'Title sort names'
+PLUGIN_AUTHOR = 'Jacob Rask'
+PLUGIN_DESCRIPTION = 'Guesses title and album sortnames (language specific) and adds as titlesort and albumsort tags.'
+PLUGIN_VERSION = "0.1.4"
+PLUGIN_API_VERSIONS = ["0.12", "0.15"]
+
+from picard.metadata import register_track_metadata_processor
+from picard.metadata import register_album_metadata_processor
+import re
+
+# define articles
+_articles = {}
+_articles['deu'] = ['Der ', 'Das ', 'Die ', 'Eine? '] # German
+_articles['eng'] = ['Th[ae] ', 'Da ', 'An? '] # English
+_articles['esp'] = ['El ', 'La ', 'L[ao]s ', 'Una? ', 'Un[ao]s '] # Spanish
+_articles['fra'] = ["Les? ", "La ", "L'", "Une? ", "Des "] # French
+_articles['ita'] = ["Il ", "L[aeo] ", "L'", "I ", "Gli ", "Un[ao]? ", "Un'"] # Italian
+_articles['swe'] = ['De[nt]? ', 'Dom ', 'E(n|tt) '] # Swedish
+
+# compile sort language regular expressions
+_re_articles = {}
+_regmul = ''
+for lang, a in _articles.iteritems():
+    reg = ''
+    for i in range(len(a)):
+        reg = '|^' + _articles[lang][i] + reg
+        _re_articles[lang] = re.compile(reg[1:])
+    _regmul = _regmul + reg
+# all articles are collected and used for "multiple languages"
+_re_articles['mul'] = re.compile(_regmul[1:])
+
+def make_sorttitle(title, lang):
+    if lang not in _re_articles:
+        lang = "mul"
+    sort_re = _re_articles[lang]
+    match = sort_re.match(title)
+    titlesort = title
+    if match:
+        sort_prefix = match.group().strip()
+        titlesort = sort_re.sub("", title).strip() + ", " + sort_prefix
+        titlesort = titlesort[0].upper() + titlesort[1:] # capitalize first letter
+    return titlesort
+
+def add_titlesort(tagger, metadata, release, track):
+    if metadata["titlesort"]:
+        titlesort = metadata["titlesort"]
+    else:
+        titlesort = metadata["title"]
+    metadata["titlesort"] = make_sorttitle(titlesort, metadata["language"])
+
+def add_albumsort(tagger, metadata, release):
+    if metadata["albumsort"]:
+        titlesort = metadata["albumsort"]
+    else:
+        titlesort = metadata["album"]
+    metadata["albumsort"] = make_sorttitle(titlesort, metadata["language"])
+
+register_track_metadata_processor(add_titlesort)
+register_album_metadata_processor(add_albumsort)
diff --git a/titleversion.py b/titleversion.py
new file mode 100644
index 0000000..ffaa5dc
--- /dev/null
+++ b/titleversion.py
@@ -0,0 +1,27 @@
+PLUGIN_NAME = 'Move metadata to version tag'
+PLUGIN_AUTHOR = 'Jacob Rask'
+PLUGIN_DESCRIPTION = 'Moves song metadata such as "demo", "live" from title and titlesort to version tag.'
+PLUGIN_VERSION = "0.1.4"
+PLUGIN_API_VERSIONS = ["0.12", "0.15"]
+
+from picard.metadata import register_track_metadata_processor
+import re
+
+_p_re = re.compile(r"\(.*?\)")
+_v_re = re.compile(r"((\s|-)?(acoustic|akustisk|album|bonus|clean|club|cut|C=64|dance|dirty|disco|encore|extended|inch|maxi|live|original|radio|redux|rehearsal|reprise|reworked|ringtone|[Ss]essions?|short|studio|take|variant|version|vocal)(\s|-)?|.*?(capp?ella)\s?|(\s|-)?(alternat|demo|dub|edit|ext|fail|instr|long|orchestr|record|remaster|remix|strument|[Tt]ape|varv).*?|.*?(complete|mix|inspel).*?)")
+
+def add_title_version(tagger, metadata, release, track):
+    if metadata["titlesort"]:
+        title = metadata["titlesort"]
+    else:
+        title = metadata["title"]
+    pmatch = _p_re.findall(title)
+    if pmatch: # if there's a parenthesis, investigate
+        pstr = pmatch[-1][1:-1] # get last match and strip paranthesis
+        vmatch = _v_re.search(pstr)
+        if vmatch:
+            metadata["titlesort"] = re.sub("\(" + pstr + "\)", "", title).strip()
+            metadata["title"] = re.sub("\(" + pstr + "\)", "", title).strip()
+            metadata["version"] = pstr
+
+register_track_metadata_processor(add_title_version)


More information about the scm-commits mailing list