changeset 0:4fab77a9ac9d default tip

Initial commit of working script
author IBBoard <dev@ibboard.co.uk>
date Sat, 11 Nov 2017 16:27:22 +0000
parents
children
files import-rating-rhythmbox.py
diffstat 1 files changed, 124 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/import-rating-rhythmbox.py	Sat Nov 11 16:27:22 2017 +0000
@@ -0,0 +1,124 @@
+#! /usr/bin/env python3
+
+from xml.dom.minidom import parse
+import sys
+from os import path
+from os.path import expanduser
+from urllib.parse import urlparse, unquote
+import mutagen
+
+
+### Usage: python3 ./quodlibet-to-rhythmbox.py > output.xml; \
+###        mv output.xml ~/.local/share/rhythmbox/rhythmdb.xml
+
+
+# List of sub-keys that we will take POPM ratings from
+# The order they are here is their priority - we take the first one we find
+TRANSFER_KEYS = [
+    'quodlibet@lists.sacredchao.net',
+    'Banshee'
+]
+
+DEBUG_LEVEL = 0
+
+
+def _debug(message, level=1):
+    if level <= DEBUG_LEVEL:
+        print(message, file=sys.stderr)
+
+
+def _get_text(nodelist):
+    content = []
+    for node in nodelist:
+        if node.nodeType == node.TEXT_NODE:
+            content.append(node.data)
+    return ''.join(content)
+
+
+def _get_path_from_url(file_uri):
+    parsed = urlparse(file_uri)
+    return path.abspath(path.join(parsed.netloc, unquote(parsed.path)))
+
+
+def _normalise_rating(rating):
+    # Take the Banshee approach to ratings, but assume that a rating of 5
+    # or below is an exact rating (normally a rogue Banshee rating)
+    # https://git.gnome.org/browse/banshee/tree/src/Core/Banshee.Core/Banshee.Streaming/StreamRatingTagger.cs#n54
+    if rating <= 5:
+        return rating
+    elif rating < 64:
+        return 1
+    elif rating < 128:
+        return 2
+    elif rating < 192:
+        return 3
+    elif rating < 255:
+        return 4
+    else:
+        return 5
+
+
+def _set_rating(doc, entry, value):
+    rating = _normalise_rating(value)
+    _debug("Setting rating to {} based on value {}".format(rating, value))
+    rating_tag = doc.createElement("rating")
+    rating_tag.appendChild(doc.createTextNode(str(rating)))
+    entry.appendChild(rating_tag)
+
+def transfer_ratings():
+    """
+    Transfers the ratings from IDv3 POPM tags to Rhythmbox's XML database
+    and prints the resulting XML to stdout
+    """
+    home = expanduser("~")
+
+    doc_path = path.join(home, '.local/share/rhythmbox/rhythmdb.xml')
+    doc = parse(doc_path)
+
+    if len(doc.childNodes) != 1:
+        _debug("Invalid document structure", 0)
+        exit(1)
+
+    rhythmdb = doc.childNodes[0]
+
+    if rhythmdb.nodeName != 'rhythmdb':
+        _debug("Invalid document structure", 0)
+        exit(1)
+
+    for entry in rhythmdb.childNodes:
+        if entry.nodeType == entry.TEXT_NODE or entry.getAttribute('type') != 'song':
+            continue
+        if len(entry.getElementsByTagName('rating')) > 0:
+            continue
+
+        locations = entry.getElementsByTagName('location')
+
+        if len(locations) != 1:
+            _debug("Song did not have one location", 0)
+            continue
+
+        location = locations[0]
+        file_path = _get_path_from_url(_get_text(location.childNodes))
+
+        if not path.isfile(file_path):
+            _debug("{} was not a file".format(file_path))
+
+        try:
+            track = mutagen.File(file_path)
+
+            for key_suffix in TRANSFER_KEYS:
+                key = 'POPM:{}'.format(key_suffix)
+                if key in track.tags:
+                    rating = track.tags[key].rating
+                    if rating > 0:
+                        _debug("Found rating from {} for {}".format(key_suffix, file_path))
+                        _set_rating(doc, entry, track.tags[key].rating)
+                        break
+        except mutagen.MutagenError as ex:
+            _debug("Error processing {}: {}".format(file_path, ex), 0)
+
+    print(doc.toxml())
+
+
+if __name__ == '__main__':
+    transfer_ratings()