comparison import-rating-rhythmbox.py @ 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
comparison
equal deleted inserted replaced
-1:000000000000 0:4fab77a9ac9d
1 #! /usr/bin/env python3
2
3 from xml.dom.minidom import parse
4 import sys
5 from os import path
6 from os.path import expanduser
7 from urllib.parse import urlparse, unquote
8 import mutagen
9
10
11 ### Usage: python3 ./quodlibet-to-rhythmbox.py > output.xml; \
12 ### mv output.xml ~/.local/share/rhythmbox/rhythmdb.xml
13
14
15 # List of sub-keys that we will take POPM ratings from
16 # The order they are here is their priority - we take the first one we find
17 TRANSFER_KEYS = [
18 'quodlibet@lists.sacredchao.net',
19 'Banshee'
20 ]
21
22 DEBUG_LEVEL = 0
23
24
25 def _debug(message, level=1):
26 if level <= DEBUG_LEVEL:
27 print(message, file=sys.stderr)
28
29
30 def _get_text(nodelist):
31 content = []
32 for node in nodelist:
33 if node.nodeType == node.TEXT_NODE:
34 content.append(node.data)
35 return ''.join(content)
36
37
38 def _get_path_from_url(file_uri):
39 parsed = urlparse(file_uri)
40 return path.abspath(path.join(parsed.netloc, unquote(parsed.path)))
41
42
43 def _normalise_rating(rating):
44 # Take the Banshee approach to ratings, but assume that a rating of 5
45 # or below is an exact rating (normally a rogue Banshee rating)
46 # https://git.gnome.org/browse/banshee/tree/src/Core/Banshee.Core/Banshee.Streaming/StreamRatingTagger.cs#n54
47 if rating <= 5:
48 return rating
49 elif rating < 64:
50 return 1
51 elif rating < 128:
52 return 2
53 elif rating < 192:
54 return 3
55 elif rating < 255:
56 return 4
57 else:
58 return 5
59
60
61 def _set_rating(doc, entry, value):
62 rating = _normalise_rating(value)
63 _debug("Setting rating to {} based on value {}".format(rating, value))
64 rating_tag = doc.createElement("rating")
65 rating_tag.appendChild(doc.createTextNode(str(rating)))
66 entry.appendChild(rating_tag)
67
68 def transfer_ratings():
69 """
70 Transfers the ratings from IDv3 POPM tags to Rhythmbox's XML database
71 and prints the resulting XML to stdout
72 """
73 home = expanduser("~")
74
75 doc_path = path.join(home, '.local/share/rhythmbox/rhythmdb.xml')
76 doc = parse(doc_path)
77
78 if len(doc.childNodes) != 1:
79 _debug("Invalid document structure", 0)
80 exit(1)
81
82 rhythmdb = doc.childNodes[0]
83
84 if rhythmdb.nodeName != 'rhythmdb':
85 _debug("Invalid document structure", 0)
86 exit(1)
87
88 for entry in rhythmdb.childNodes:
89 if entry.nodeType == entry.TEXT_NODE or entry.getAttribute('type') != 'song':
90 continue
91 if len(entry.getElementsByTagName('rating')) > 0:
92 continue
93
94 locations = entry.getElementsByTagName('location')
95
96 if len(locations) != 1:
97 _debug("Song did not have one location", 0)
98 continue
99
100 location = locations[0]
101 file_path = _get_path_from_url(_get_text(location.childNodes))
102
103 if not path.isfile(file_path):
104 _debug("{} was not a file".format(file_path))
105
106 try:
107 track = mutagen.File(file_path)
108
109 for key_suffix in TRANSFER_KEYS:
110 key = 'POPM:{}'.format(key_suffix)
111 if key in track.tags:
112 rating = track.tags[key].rating
113 if rating > 0:
114 _debug("Found rating from {} for {}".format(key_suffix, file_path))
115 _set_rating(doc, entry, track.tags[key].rating)
116 break
117 except mutagen.MutagenError as ex:
118 _debug("Error processing {}: {}".format(file_path, ex), 0)
119
120 print(doc.toxml())
121
122
123 if __name__ == '__main__':
124 transfer_ratings()