Mercurial > repos > other > import-rating-rhythmbox
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()