MythTV master
mbutils.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# This file is part of MythTV.
3# Copyright 2016, Paul Harrison.
4#
5# Permission is hereby granted, free of charge, to any person obtaining
6# a copy of this software and associated documentation files (the
7# "Software"), to deal in the Software without restriction, including
8# without limitation the rights to use, copy, modify, merge, publish,
9# distribute, sublicense, and/or sell copies of the Software, and to
10# permit persons to whom the Software is furnished to do so, subject to
11# the following conditions:
12#
13# The above copyright notice and this permission notice shall be
14# included in all copies or substantial portions of the Software.
15
16"""Various utilities using the musicbrainzngs python bindings to access the MB database"""
17
18from optparse import OptionParser
19import musicbrainzngs
20import sys
21import pprint
22
23__author__ = "Paul Harrison'"
24__title__ = "Music Brainz utilities"
25__description__ = "Various utilities using the musicbrainzngs python bindings to access the MB database"
26__version__ = "0.2"
27
28debug = False
29
30musicbrainzngs.set_useragent(
31 "MythTV",
32 "32.0",
33 "https://www.mythtv.org",
34)
35
36musicbrainzngs.set_hostname("musicbrainz.org")
37
38def log(debug, txt):
39 if debug:
40 print(txt)
41
42def convert_etree(etostr):
43 """lxml.etree.tostring is a bytes object in python3, and a str in python2.
44 """
45 return(etostr.decode())
46
47def search_releases(artist, album, limit):
48 from lxml import etree
49
50 root = etree.XML(u'<searchreleases></searchreleases>')
51
52 result = musicbrainzngs.search_releases(artist=artist, release=album, country="GB", limit=limit)
53
54 if not result['release-list']:
55 etree.SubElement(root, "error").text = "No Releases found"
56 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
57 sys.exit(1)
58
59 for (idx, release) in enumerate(result['release-list']):
60 pprint.pprint(release)
61 relNode = etree.SubElement(root, "release")
62
63 etree.SubElement(relNode, "id").text = release['id']
64 etree.SubElement(relNode, "ext-score").text = release['ext:score']
65 etree.SubElement(relNode, "title").text = release['title']
66 etree.SubElement(relNode, "artist-credit-phrase").text = release["artist-credit-phrase"]
67 etree.SubElement(relNode, "country").text = release["country"]
68
69 if 'date' in release:
70 etree.SubElement(relNode, "date").text = release['date']
71 etree.SubElement(relNode, "status").text = release['status']
72
73 if 'release-group' in release:
74 etree.SubElement(relNode, "releasegroup").text = release["release-group"]["id"]
75
76 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
77
78 sys.exit(0)
79
80def search_artists(artist, limit):
81 from lxml import etree
82
83 root = etree.XML(u'<artists></artists>')
84 result = musicbrainzngs.search_artists(artist=artist, limit=limit)
85
86 if debug:
87 pprint.pprint(result)
88
89 if result['artist-list']:
90 for (idx, artist) in enumerate(result['artist-list']):
91 artistNode = etree.SubElement(root, "id")
92 etree.SubElement(artistNode, "id").text = artist['id']
93 etree.SubElement(artistNode, "name").text = artist['name']
94 etree.SubElement(artistNode, "sort-name").text = artist['sort-name']
95 else:
96 etree.SubElement(root, "error").text = "No Artists found"
97 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
98 sys.exit(1)
99
100 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
101
102 sys.exit(0)
103
104def get_artist(artistid):
105 from lxml import etree
106
107 root = etree.XML(u'<artist></artist>')
108
109 # lookup the artist id
110 result = musicbrainzngs.get_artist_by_id(artistid, includes=['url-rels'])
111
112 if debug:
113 pprint.pprint(result)
114
115 if result:
116 etree.SubElement(root, "id").text = result['artist']['id']
117 etree.SubElement(root, "name").text = result['artist']['name']
118 etree.SubElement(root, "sort-name").text = result['artist']['sort-name']
119
120 if 'url-relation-list' in result['artist']:
121 urls = result['artist']['url-relation-list']
122 if urls:
123 for (idx, url) in enumerate(urls):
124 node = etree.SubElement(root, "url")
125 node.text = url['target']
126 node.set("type", url['type'])
127 else:
128 etree.SubElement(root, "error").text = "MusicBrainz ID was not found"
129 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
130 sys.exit(1)
131
132 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
133 sys.exit(0)
134
135def find_coverart(release):
136 from lxml import etree
137
138 root = etree.XML(u'<coverart></coverart>')
139
140 try:
141 data = musicbrainzngs.get_image_list(release)
142 except musicbrainzngs.ResponseError as err:
143 if err.cause.code == 404:
144 etree.SubElement(root, "error").text = "Release ID not found"
145 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
146 sys.exit(1)
147 else:
148 etree.SubElement(root, "error").text = "Received bad response from the MB server"
149 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
150 sys.exit(1)
151
152 if debug:
153 pprint.pprint(data)
154
155 for image in data["images"]:
156 imageNode = etree.SubElement(root, "image")
157 etree.SubElement(imageNode, "image").text = image["image"]
158 etree.SubElement(imageNode, "approved").text = str(image["approved"])
159 etree.SubElement(imageNode, "front").text = str(image["front"])
160 etree.SubElement(imageNode, "back").text = str(image["back"])
161 #etree.SubElement(imageNode, "types").text = image["types"]
162 etree.SubElement(imageNode, "thumb-small").text = image["thumbnails"]["small"]
163 etree.SubElement(imageNode, "thumb-large").text = image["thumbnails"]["large"]
164
165 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
166 sys.exit(0)
167
169 from lxml import etree
170
171 root = etree.XML(u'<coverart></coverart>')
172
173 try:
174 data = musicbrainzngs.get_release_group_image_list(releaseGroup)
175 except musicbrainzngs.ResponseError as err:
176 if err.cause.code == 404:
177 etree.SubElement(root, "error").text = "Release ID not found"
178 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
179 sys.exit(1)
180 else:
181 etree.SubElement(root, "error").text = "Received bad response from the MB server"
182 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
183 sys.exit(1)
184
185 if debug:
186 pprint.pprint(data)
187
188 for image in data["images"]:
189 imageNode = etree.SubElement(root, "image")
190 etree.SubElement(imageNode, "image").text = image["image"]
191 etree.SubElement(imageNode, "approved").text = str(image["approved"])
192 etree.SubElement(imageNode, "front").text = str(image["front"])
193 etree.SubElement(imageNode, "back").text = str(image["back"])
194 #etree.SubElement(imageNode, "types").text = image["types"]
195 etree.SubElement(imageNode, "thumb-small").text = image["thumbnails"]["small"]
196 etree.SubElement(imageNode, "thumb-large").text = image["thumbnails"]["large"]
197
198 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
199 sys.exit(0)
200
201def find_disc(cddrive):
202 import discid
203 from lxml import etree
204
205 root = etree.XML(u'<finddisc></finddisc>')
206
207 try:
208 disc = discid.read(cddrive, ["mcn", "isrc"])
209 id = disc.id
210 toc = disc.toc_string
211 except discid.DiscError as err:
212 etree.SubElement(root, "error").text = "Failed to get discid ({})".format(str(err))
213 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
214 sys.exit(1)
215
216 etree.SubElement(root, "discid").text = id
217 etree.SubElement(root, "toc").text = toc
218
219 try:
220 # the "labels" include enables the cat#s we display
221 result = musicbrainzngs.get_releases_by_discid(id, includes=["labels"], toc=toc, cdstubs=False)
222 except musicbrainzngs.ResponseError as err:
223 if err.cause.code == 404:
224 etree.SubElement(root, "error").text = "Disc not found"
225 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
226 sys.exit(1)
227 else:
228 etree.SubElement(root, "error").text = "Received bad response from the MB server"
229 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
230 sys.exit(1)
231
232 # The result can either be a "disc" or a "cdstub"
233 if result.get('disc'):
234 discnode = etree.SubElement(root, "disc")
235
236 etree.SubElement(discnode, "sectors").text = result['disc']['sectors']
237
238 if "offset-list" in result['disc']:
239 offsets = None
240 for offset in result['disc']['offset-list']:
241 if offsets is None:
242 offsets = str(offset)
243 else:
244 offsets += " " + str(offset)
245
246 etree.SubElement(discnode, "offsets").text = offsets
247 etree.SubElement(discnode, "tracks").text = str(result['disc']['offset-count'])
248
249 for release in result['disc']['release-list']:
250 relnode = etree.SubElement(discnode, "release")
251
252 etree.SubElement(relnode, "title").text = release['title']
253 etree.SubElement(relnode, "musicbrainzid").text = release['id']
254
255 if release.get('barcode'):
256 etree.SubElement(relnode, "barcode").text = release['barcode']
257 for info in release['label-info-list']:
258 if info.get('catalog-number'):
259 etree.SubElement(relnode, "catalog-number").text = info['catalog-number']
260 elif result.get('cdstub'):
261 stubnode = etree.SubElement(root, "cdstub")
262
263 etree.SubElement(stubnode, "artist").text = result['cdstub']['artist']
264 etree.SubElement(stubnode, "title").text = result['cdstub']['title']
265
266 if result['cdstub'].get('barcode'):
267 etree.SubElement(stubnode, "barcode").text = result['cdstub']['barcode']
268 else:
269 etree.SubElement(root, "error").text = "No valid results"
270 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
271 sys.exit(1)
272
273 log(True, convert_etree(etree.tostring(root, encoding='UTF-8', pretty_print=True, xml_declaration=True)))
274 sys.exit(0)
275
277 from lxml import etree
278 version = etree.XML(u'<version></version>')
279 etree.SubElement(version, "name").text = __title__
280 etree.SubElement(version, "author").text = __author__
281 etree.SubElement(version, "command").text = 'mbutils.py'
282 etree.SubElement(version, "description").text = __description__
283 etree.SubElement(version, "version").text = __version__
284
285 log(True, convert_etree(etree.tostring(version, encoding='UTF-8', pretty_print=True,
286 xml_declaration=True)))
287 sys.exit(0)
288
290 err = 0
291 try:
292 import discid
293 except:
294 err = 1
295 print ("Failed to import python discid lirary. Is libdiscid installed?")
296 try:
297 import lxml
298 except:
299 err = 1
300 print("Failed to import python lxml library.")
301
302 if not err:
303 print("Everything appears in order.")
304 sys.exit(err)
305
306
307def main():
308 global debug
309
310 parser = OptionParser()
311
312 parser.add_option('-v', "--version", action="store_true", default=False,
313 dest="version", help="Display version and author")
314 parser.add_option('-t', "--test", action="store_true", default=False,
315 dest="test", help="Perform self-test for dependencies.")
316 parser.add_option('-r', "--searchreleases", action="store_true", default=False,
317 dest="searchreleases", help="Search for musicbrainz release id's for a given artist & album. Requires --artist/--album. Optional --limit.")
318 parser.add_option('-s', "--searchartists", action="store_true", default=False,
319 dest="searchartists", help="Search for musicbrainz artist id's for a given artist. Requires --artist. Optional --limit.")
320 parser.add_option('-g', "--getartist", action="store_true", default=False,
321 dest="getartist", help="Lookup the details of a given musicbrainz id of an artist. Requires --id.")
322 parser.add_option('-c', "--finddisc", action="store_true", default=None,
323 dest="finddisc", help="Find the musicbrainz id for a cd. Requires --cddevice.")
324 parser.add_option('-f', "--findcoverart", action="store_true", default=None,
325 dest="findcoverart", help="Find coverart for a given musicbrainz id of a release or release group. Requires --id or --relgroupid.")
326 parser.add_option('-d', '--debug', action="store_true", default=False,
327 dest="debug", help=("Show debug messages"))
328 parser.add_option('-a', '--artist', metavar="ARTIST", default=None,
329 dest="artist", help=("Name of artist"))
330 parser.add_option('-b', '--album', metavar="ALBUM", default=None,
331 dest="album", help=("Name of Album"))
332 parser.add_option('-l', '--limit', metavar="LIMIT", default=None,
333 dest="limit", help=("Limits the maximum number of results to return for some commands (defaults to 5)"))
334 parser.add_option('-i', '--id', metavar="ID", default=None,
335 dest="id", help=("Music Brainz ID to use"))
336 parser.add_option('-I', '--relgroupid', metavar="RELEASEGROUPID", default=None,
337 dest="relgroupid", help=("Music Brainz ID of Release Group to use"))
338 parser.add_option('-D', '--cddevice', metavar="CDDEVICE", default=None,
339 dest="cddevice", help=("CD device to use"))
340
341 opts, args = parser.parse_args()
342
343 if opts.debug:
344 debug = True
345
346 if opts.version:
348
349 if opts.test:
351
352 if opts.searchreleases:
353 if opts.artist is None:
354 print("Missing --artist argument")
355 sys.exit(1)
356
357 if opts.album is None:
358 print("Missing --album argument")
359 sys.exit(1)
360
361 limit = 5
362 if opts.limit:
363 limit = int(opts.limit)
364
365 search_releases(opts.artist, opts.album, limit)
366
367 if opts.searchartists:
368 if opts.artist is None:
369 print("Missing --artist argument")
370 sys.exit(1)
371
372 limit = 5
373 if opts.limit:
374 limit = int(opts.limit)
375
376 search_artists(opts.artist, limit)
377
378 if opts.getartist:
379 if opts.id is None:
380 print("Missing --id argument")
381 sys.exit(1)
382
383 get_artist(opts.id)
384
385 if opts.finddisc:
386 if opts.cddevice is None:
387 print("Missing --cddevice argument")
388 sys.exit(1)
389
390 find_disc(opts.cddevice)
391
392 if opts.findcoverart:
393 if opts.id is None and opts.relgroupid is None:
394 print("Missing --id or --relgroupid argument")
395 sys.exit(1)
396
397 if opts.id is not None:
398 find_coverart(opts.id)
399 else:
400 find_coverart_releasegroup(opts.relgroupid)
401
402 sys.exit(0)
403
404if __name__ == '__main__':
405 main()
def main()
Definition: mbutils.py:307
def search_releases(artist, album, limit)
Definition: mbutils.py:47
def log(debug, txt)
Definition: mbutils.py:38
def find_coverart_releasegroup(releaseGroup)
Definition: mbutils.py:168
def buildVersion()
Definition: mbutils.py:276
def find_coverart(release)
Definition: mbutils.py:135
def convert_etree(etostr)
Definition: mbutils.py:42
def get_artist(artistid)
Definition: mbutils.py:104
def search_artists(artist, limit)
Definition: mbutils.py:80
def find_disc(cddrive)
Definition: mbutils.py:201
def performSelfTest()
Definition: mbutils.py:289
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)