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 
18 from optparse import OptionParser
19 import musicbrainzngs
20 import sys
21 import 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 
28 debug = False
29 
30 musicbrainzngs.set_useragent(
31  "MythTV",
32  "32.0",
33  "https://www.mythtv.org",
34 )
35 
36 musicbrainzngs.set_hostname("musicbrainz.org")
37 
38 def log(debug, txt):
39  if debug:
40  print(txt)
41 
42 def convert_etree(etostr):
43  """lxml.etree.tostring is a bytes object in python3, and a str in python2.
44  """
45  return(etostr.decode())
46 
47 def 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 
80 def 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 
104 def 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 
135 def 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 
168 def find_coverart_releasegroup(releaseGroup):
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 
201 def 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 
307 def 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:
347  buildVersion()
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 
404 if __name__ == '__main__':
405  main()
mbutils.main
def main()
Definition: mbutils.py:307
mbutils.buildVersion
def buildVersion()
Definition: mbutils.py:276
discid.disc.DiscError
Definition: disc.py:80
mbutils.search_artists
def search_artists(artist, limit)
Definition: mbutils.py:80
mbutils.search_releases
def search_releases(artist, album, limit)
Definition: mbutils.py:47
musicbrainzngs.musicbrainz.ResponseError
Definition: musicbrainz.py:212
mbutils.find_disc
def find_disc(cddrive)
Definition: mbutils.py:201
mbutils.find_coverart_releasegroup
def find_coverart_releasegroup(releaseGroup)
Definition: mbutils.py:168
mbutils.log
def log(debug, txt)
Definition: mbutils.py:38
print
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)
Definition: vbi608extractor.cpp:29
mbutils.convert_etree
def convert_etree(etostr)
Definition: mbutils.py:42
hardwareprofile.distros.all.get
def get()
Definition: all.py:22
mbutils.get_artist
def get_artist(artistid)
Definition: mbutils.py:104
mbutils.performSelfTest
def performSelfTest()
Definition: mbutils.py:289
mbutils.find_coverart
def find_coverart(release)
Definition: mbutils.py:135