changeset 0:4fab77a9ac9d 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 diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/import-rating-rhythmbox.py	Sat Nov 11 16:27:22 2017 +0000
     1.3 @@ -0,0 +1,124 @@
     1.4 +#! /usr/bin/env python3
     1.5 +
     1.6 +from xml.dom.minidom import parse
     1.7 +import sys
     1.8 +from os import path
     1.9 +from os.path import expanduser
    1.10 +from urllib.parse import urlparse, unquote
    1.11 +import mutagen
    1.12 +
    1.13 +
    1.14 +### Usage: python3 ./quodlibet-to-rhythmbox.py > output.xml; \
    1.15 +###        mv output.xml ~/.local/share/rhythmbox/rhythmdb.xml
    1.16 +
    1.17 +
    1.18 +# List of sub-keys that we will take POPM ratings from
    1.19 +# The order they are here is their priority - we take the first one we find
    1.20 +TRANSFER_KEYS = [
    1.21 +    'quodlibet@lists.sacredchao.net',
    1.22 +    'Banshee'
    1.23 +]
    1.24 +
    1.25 +DEBUG_LEVEL = 0
    1.26 +
    1.27 +
    1.28 +def _debug(message, level=1):
    1.29 +    if level <= DEBUG_LEVEL:
    1.30 +        print(message, file=sys.stderr)
    1.31 +
    1.32 +
    1.33 +def _get_text(nodelist):
    1.34 +    content = []
    1.35 +    for node in nodelist:
    1.36 +        if node.nodeType == node.TEXT_NODE:
    1.37 +            content.append(node.data)
    1.38 +    return ''.join(content)
    1.39 +
    1.40 +
    1.41 +def _get_path_from_url(file_uri):
    1.42 +    parsed = urlparse(file_uri)
    1.43 +    return path.abspath(path.join(parsed.netloc, unquote(parsed.path)))
    1.44 +
    1.45 +
    1.46 +def _normalise_rating(rating):
    1.47 +    # Take the Banshee approach to ratings, but assume that a rating of 5
    1.48 +    # or below is an exact rating (normally a rogue Banshee rating)
    1.49 +    # https://git.gnome.org/browse/banshee/tree/src/Core/Banshee.Core/Banshee.Streaming/StreamRatingTagger.cs#n54
    1.50 +    if rating <= 5:
    1.51 +        return rating
    1.52 +    elif rating < 64:
    1.53 +        return 1
    1.54 +    elif rating < 128:
    1.55 +        return 2
    1.56 +    elif rating < 192:
    1.57 +        return 3
    1.58 +    elif rating < 255:
    1.59 +        return 4
    1.60 +    else:
    1.61 +        return 5
    1.62 +
    1.63 +
    1.64 +def _set_rating(doc, entry, value):
    1.65 +    rating = _normalise_rating(value)
    1.66 +    _debug("Setting rating to {} based on value {}".format(rating, value))
    1.67 +    rating_tag = doc.createElement("rating")
    1.68 +    rating_tag.appendChild(doc.createTextNode(str(rating)))
    1.69 +    entry.appendChild(rating_tag)
    1.70 +
    1.71 +def transfer_ratings():
    1.72 +    """
    1.73 +    Transfers the ratings from IDv3 POPM tags to Rhythmbox's XML database
    1.74 +    and prints the resulting XML to stdout
    1.75 +    """
    1.76 +    home = expanduser("~")
    1.77 +
    1.78 +    doc_path = path.join(home, '.local/share/rhythmbox/rhythmdb.xml')
    1.79 +    doc = parse(doc_path)
    1.80 +
    1.81 +    if len(doc.childNodes) != 1:
    1.82 +        _debug("Invalid document structure", 0)
    1.83 +        exit(1)
    1.84 +
    1.85 +    rhythmdb = doc.childNodes[0]
    1.86 +
    1.87 +    if rhythmdb.nodeName != 'rhythmdb':
    1.88 +        _debug("Invalid document structure", 0)
    1.89 +        exit(1)
    1.90 +
    1.91 +    for entry in rhythmdb.childNodes:
    1.92 +        if entry.nodeType == entry.TEXT_NODE or entry.getAttribute('type') != 'song':
    1.93 +            continue
    1.94 +        if len(entry.getElementsByTagName('rating')) > 0:
    1.95 +            continue
    1.96 +
    1.97 +        locations = entry.getElementsByTagName('location')
    1.98 +
    1.99 +        if len(locations) != 1:
   1.100 +            _debug("Song did not have one location", 0)
   1.101 +            continue
   1.102 +
   1.103 +        location = locations[0]
   1.104 +        file_path = _get_path_from_url(_get_text(location.childNodes))
   1.105 +
   1.106 +        if not path.isfile(file_path):
   1.107 +            _debug("{} was not a file".format(file_path))
   1.108 +
   1.109 +        try:
   1.110 +            track = mutagen.File(file_path)
   1.111 +
   1.112 +            for key_suffix in TRANSFER_KEYS:
   1.113 +                key = 'POPM:{}'.format(key_suffix)
   1.114 +                if key in track.tags:
   1.115 +                    rating = track.tags[key].rating
   1.116 +                    if rating > 0:
   1.117 +                        _debug("Found rating from {} for {}".format(key_suffix, file_path))
   1.118 +                        _set_rating(doc, entry, track.tags[key].rating)
   1.119 +                        break
   1.120 +        except mutagen.MutagenError as ex:
   1.121 +            _debug("Error processing {}: {}".format(file_path, ex), 0)
   1.122 +
   1.123 +    print(doc.toxml())
   1.124 +
   1.125 +
   1.126 +if __name__ == '__main__':
   1.127 +    transfer_ratings()