MythTV  master
tvmaze.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 # -*- coding: UTF-8 -*-
3 
4 __title__ = "TVmaze.com"
5 __author__ = "Roland Ernst, Steve Erlenborn"
6 __version__ = "0.5.1"
7 
8 
9 import sys
10 import os
11 import shlex
12 from optparse import OptionParser
13 
14 
15 def print_etree(etostr):
16  """lxml.etree.tostring is a bytes object in python3, and a str in python2.
17  """
18  sys.stdout.write(etostr.decode("utf-8"))
19 
20 
21 def check_item(m, mitem, ignore=True):
22  # item is a tuple of (str, value)
23  # ToDo: Add this to the 'Metadata' class of MythTV's python bindings
24  try:
25  k, v = mitem
26  if v is None:
27  return None
28  m._inv_trans[m._global_type[k]](v)
29  return v
30  except:
31  if ignore:
32  return None
33  else:
34  raise
35 
36 
37 def get_show_art_lists(tvmaze_show_id):
38  from MythTV.tvmaze import tvmaze_api as tvmaze
39 
40  artlist = tvmaze.get_show_artwork(tvmaze_show_id)
41 
42  #--------------------------------------------------------------------------
43  # The Main flag is true for artwork which is "official" from the Network.
44  # Under the theory that "official" artwork should be high quality, we want
45  # those artworks to be located at the beginning of the generated list.
46  #--------------------------------------------------------------------------
47  posterList = [(art_item.original, art_item.medium) for art_item in artlist \
48  if (art_item.main and (art_item.type == 'poster'))]
49  posterNorm = [(art_item.original, art_item.medium) for art_item in artlist \
50  if ((not art_item.main) and (art_item.type == 'poster'))]
51  posterList.extend(posterNorm)
52 
53  fanartList = [(art_item.original, art_item.medium) for art_item in artlist \
54  if (art_item.main and (art_item.type == 'background'))]
55  fanartNorm = [(art_item.original, art_item.medium) for art_item in artlist \
56  if ((not art_item.main) and (art_item.type == 'background'))]
57  fanartList.extend(fanartNorm)
58 
59  bannerList = [(art_item.original, art_item.medium) for art_item in artlist \
60  if (art_item.main and (art_item.type == 'banner'))]
61  bannerNorm = [(art_item.original, art_item.medium) for art_item in artlist \
62  if ((not art_item.main) and (art_item.type == 'banner'))]
63  bannerList.extend(bannerNorm)
64 
65  return posterList, fanartList, bannerList
66 
67 
68 def buildList(tvtitle, opts):
69  # option -M title
70  from lxml import etree
71  from MythTV import VideoMetadata
72  from MythTV.tvmaze import tvmaze_api as tvmaze
73  from MythTV.tvmaze import locales
74 
75  # set the session
76  if opts.session:
77  tvmaze.set_session(opts.session)
78 
79  if opts.debug:
80  print("Function 'buildList' called with argument '%s'" % tvtitle)
81 
82  showlist = tvmaze.search_show(tvtitle)
83 
84  if opts.debug:
85  print("tvmaze.search_show(%s) returned :" % tvtitle)
86  for l in showlist:
87  print(l, type(l))
88  for k, v in l.__dict__.items():
89  print(k, " : ", v)
90 
91  tree = etree.XML(u'<metadata></metadata>')
92 
93  for show_info in showlist:
94  m = VideoMetadata()
95  m.title = check_item(m, ("title", show_info.name), ignore=False)
96  m.description = check_item(m, ("description", show_info.summary))
97  m.inetref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
98  m.collectionref = check_item(m, ("collectionref", str(show_info.id)), ignore=False)
99  m.language = check_item(m, ("language", str(locales.Language.getstored(show_info.language))))
100  m.userrating = check_item(m, ("userrating", show_info.rating['average']))
101  try:
102  m.popularity = check_item(m, ("popularity", float(show_info.weight)), ignore=False)
103  except (TypeError, ValueError):
104  pass
105  if show_info.premiere_date:
106  m.releasedate = check_item(m, ("releasedate", show_info.premiere_date))
107  m.year = check_item(m, ("year", show_info.premiere_date.year))
108 
109  posterList, fanartList, bannerList = get_show_art_lists(show_info.id)
110 
111  # Generate one image line for each type of artwork
112  if posterList:
113  posterEntry = posterList[0]
114  if (posterEntry[0] is not None) and (posterEntry[1] is not None):
115  m.images.append({'type': 'coverart', 'url': posterEntry[0], 'thumb': posterEntry[1]})
116  elif posterEntry[0] is not None:
117  m.images.append({'type': 'coverart', 'url': posterEntry[0]})
118 
119  if fanartList:
120  fanartEntry = fanartList[0]
121  if (fanartEntry[0] is not None) and (fanartEntry[1] is not None):
122  m.images.append({'type': 'fanart', 'url': fanartEntry[0], 'thumb': fanartEntry[1]})
123  elif fanartEntry[0] is not None:
124  m.images.append({'type': 'fanart', 'url': fanartEntry[0]})
125 
126  if bannerList:
127  bannerEntry = bannerList[0]
128  if (bannerEntry[0] is not None) and (bannerEntry[1] is not None):
129  m.images.append({'type': 'banner', 'url': bannerEntry[0], 'thumb': bannerEntry[1]})
130  elif bannerEntry[0] is not None:
131  m.images.append({'type': 'banner', 'url': bannerEntry[0]})
132 
133  tree.append(m.toXML())
134 
135  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
136  xml_declaration=True))
137 
138 
139 def buildNumbers(args, opts):
140  # either option -N <inetref> <subtitle> e.g. -N 69 "Elizabeth Keen"
141  # or option -N <inetref> <date time> e.g. -N 69 "2021-01-29 19:00:00"
142  # or option -N <title> <subtitle> e.g. -N "The Blacklist" "Elizabeth Keen"
143  # or option -N <title> <date time> e.g. -N "The Blacklist" "2021-01-29 19:00:00"
144  from MythTV.utility import levenshtein
145  from MythTV.utility.dt import posixtzinfo
146  from MythTV.tvmaze import tvmaze_api as tvmaze
147  from MythTV import datetime
148  from lxml import etree
149  from datetime import timedelta
150 
151  if opts.debug:
152  print("Function 'buildNumbers' called with arguments: " +
153  (" ".join(["'%s'" % i for i in args])))
154 
155  # set the session
156  if opts.session:
157  tvmaze.set_session(opts.session)
158 
159  dtInLocalZone = None
160  # ToDo:
161  # below check shows a deficiency of the MythTV grabber API itself:
162  # TV-Shows or Movies with an integer as title are not recognized correctly.
163  # see https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
164  # and https://code.mythtv.org/trac/ticket/11850
165  try:
166  inetref = int(args[0])
167  tvsubtitle = args[1]
168  inetrefList = [inetref]
169 
170  except ValueError:
171  tvtitle = args[0]
172  tvsubtitle = args[1]
173  inetrefList = [] # inetrefs for shows with title matches
174  best_show_quality = 0.5 # require at least this quality on string match
175 
176  showlist = tvmaze.search_show(tvtitle)
177 
178  # It's problematic to make decisions solely upon the Levenshtein distance.
179  # If the strings are really long or really short, a simple rule, such as
180  # "accept any distance < 6" can provide misleading results.
181  # To establish a more useful measurement, we'll use the Levenshtein
182  # distance to figure out the ratio (0 - 1) of matching characters in the
183  # longer string, and call this 'match_quality'.
184  # "Risk", "Call" -> distance = 4
185  # match_quality = (4 - 4) / 4 = 0
186  # "In Sickness and in Health", "Sickness and Health" -> distance = 6
187  # match_quality = (25 - 6)/25 = .76
188 
189  for show_info in showlist:
190  try:
191  inetref = int(show_info.id)
192  distance = levenshtein(show_info.name.lower(), tvtitle.lower())
193  if len(tvtitle) > len(show_info.name):
194  match_quality = float(len(tvtitle) - distance) / len(tvtitle)
195  else:
196  match_quality = float(len(show_info.name) - distance) / len(show_info.name)
197  if match_quality >= best_show_quality:
198  #if opts.debug:
199  #print ('show_info =', show_info, ', match_quality =', match_quality)
200  if match_quality == best_show_quality:
201  inetrefList.append(inetref)
202  else:
203  # Any items previously appended for a lesser match need to be eliminated
204  inetrefList = [inetref]
205  best_show_quality = match_quality
206  except(TypeError, ValueError):
207  pass
208 
209  # check whether the 'subtitle' is really a timestamp
210  try:
211  dtInLocalZone = datetime.strptime(tvsubtitle, "%Y-%m-%d %H:%M:%S") # defaults to local timezone
212  except ValueError:
213  dtInLocalZone = None
214 
215  matchesFound = 0
216  best_ep_quality = 0.5 # require at least this quality on string match
217  tree = etree.XML(u'<metadata></metadata>')
218  for inetref in inetrefList:
219  dtInTgtZone = None
220  if dtInLocalZone:
221  try:
222  show_info = tvmaze.get_show(inetref)
223  # Some cases have 'network' = None, but webChannel != None. If we
224  # find such a case, we'll set show_network to the webChannel.
225  show_network = show_info.network
226  if show_network is None:
227  show_network = show_info.streaming_service
228  show_country = show_network.get('country')
229  # Some webChannels don't specify country or timezone
230  if show_country:
231  show_tz = show_country.get('timezone')
232  else:
233  show_tz = None
234  dtInTgtZone = dtInLocalZone.astimezone(posixtzinfo(show_tz))
235 
236  except (ValueError, AttributeError) as e:
237  if opts.debug:
238  print('show_tz =%s, except = %s' % (show_tz, e))
239  dtInTgtZone = None
240 
241  if dtInTgtZone:
242  # get episode info based on inetref and datetime in target zone
243  try:
244  #print('get_show_episodes_by_date(', inetref, ',', dtInTgtZone, ')')
245  episodes = tvmaze.get_show_episodes_by_date(inetref, dtInTgtZone)
246  except SystemExit:
247  episodes = []
248  time_match_list = []
249  early_match_list = []
250  minTimeDelta = timedelta(minutes=60)
251  for i, ep in enumerate(episodes):
252  if ep.timestamp:
253  epInTgtZone = datetime.fromIso(ep.timestamp, tz = posixtzinfo(show_tz))
254  if ep.duration:
255  durationDelta = timedelta(minutes=ep.duration)
256  else:
257  durationDelta = timedelta(minutes=0)
258 
259  if epInTgtZone == dtInTgtZone:
260  if opts.debug:
261  print('Recording matches \'%s\' at %s' % (ep, epInTgtZone))
262  time_match_list.append(i)
263  minTimeDelta = timedelta(minutes=0)
264  # Consider it a match if the recording starts late,
265  # but within the duration of the show.
266  elif epInTgtZone < dtInTgtZone < epInTgtZone+durationDelta:
267  # Recording start time is within the range of this episode
268  if opts.debug:
269  print('Recording in range of \'%s\' (%s ... %s)' \
270  % (ep, epInTgtZone, epInTgtZone+durationDelta))
271  time_match_list.append(i)
272  minTimeDelta = timedelta(minutes=0)
273  # Consider it a match if the recording is a little bit early. This helps cases
274  # where you set up a rule to record, at say 9:00, and the broadcaster uses a
275  # slightly odd start time, like 9:05.
276  elif epInTgtZone-minTimeDelta <= dtInTgtZone < epInTgtZone:
277  # Recording started earlier than this episode, so see if it's the closest match
278  if epInTgtZone - dtInTgtZone == minTimeDelta:
279  if opts.debug:
280  print('Adding episode \'%s\' to closest list. Offset = %s' \
281  % (ep, epInTgtZone - dtInTgtZone))
282  early_match_list.append(i)
283  elif epInTgtZone - dtInTgtZone < minTimeDelta:
284  if opts.debug:
285  print('Episode \'%s\' is new closest. Offset = %s' \
286  % (ep, epInTgtZone - dtInTgtZone))
287  minTimeDelta = epInTgtZone - dtInTgtZone
288  early_match_list = [i]
289 
290  if not time_match_list:
291  # No exact matches found, so use the list of the closest episode(s)
292  time_match_list = early_match_list
293 
294  if time_match_list:
295  for ep_index in time_match_list:
296  season_nr = str(episodes[ep_index].season)
297  episode_id = episodes[ep_index].id
298  item = buildSingleItem(inetref, season_nr, episode_id)
299  if item is not None:
300  tree.append(item.toXML())
301  matchesFound += 1
302  else:
303  # get episode based on subtitle
304  episodes = tvmaze.get_show_episode_list(inetref)
305 
306  min_dist_list = []
307  for i, ep in enumerate(episodes):
308  if 0 and opts.debug:
309  print("tvmaze.get_show_episode_list(%s) returned :" % inetref)
310  for k, v in ep.__dict__.items():
311  print(k, " : ", v)
312  distance = levenshtein(ep.name, tvsubtitle)
313  if len(tvsubtitle) >= len(ep.name):
314  match_quality = float(len(tvsubtitle) - distance) / len(tvsubtitle)
315  else:
316  match_quality = float(len(ep.name) - distance) / len(ep.name)
317  #if opts.debug:
318  #print('inetref', inetref, 'episode =', ep.name, ', distance =', distance, ', match_quality =', match_quality)
319  if match_quality >= best_ep_quality:
320  if match_quality == best_ep_quality:
321  min_dist_list.append(i)
322  if opts.debug:
323  print('"%s" added to best list, match_quality = %g' % (ep.name, match_quality))
324  else:
325  # Any items previously appended for a lesser match need to be eliminated
326  tree = etree.XML(u'<metadata></metadata>')
327  min_dist_list = [i]
328  best_ep_quality = match_quality
329  if opts.debug:
330  print('"%s" is new best match_quality = %g' % (ep.name, match_quality))
331 
332  # The list is constructed in order of oldest season to newest.
333  # If episodes with equivalent match quality show up in multiple
334  # seasons, we want to list the most recent first. To accomplish
335  # this, we'll process items starting at the end of the list, and
336  # proceed to the beginning.
337  while min_dist_list:
338  ep_index = min_dist_list.pop()
339  season_nr = str(episodes[ep_index].season)
340  episode_id = episodes[ep_index].id
341  if opts.debug:
342  episode_nr = str(episodes[ep_index].number)
343  print("tvmaze.get_show_episode_list(%s) returned :" % inetref)
344  print("with season : %s and episode %s" % (season_nr, episode_nr))
345  print("Chosen episode index '%d' based on match quality %g"
346  % (ep_index, best_ep_quality))
347 
348  # we have now inetref, season, episode_id
349  item = buildSingleItem(inetref, season_nr, episode_id)
350  if item is not None:
351  tree.append(item.toXML())
352  matchesFound += 1
353 
354  if matchesFound > 0:
355  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
356  xml_declaration=True))
357  else:
358  if dtInLocalZone:
359  raise Exception("Cannot find any episode with timestamp matching '%s'." % tvsubtitle)
360  else:
361  # tvmaze.py -N 4711 "Episode 42"
362  raise Exception("Cannot find any episode with subtitle '%s'." % tvsubtitle)
363 
364 
365 def buildSingle(args, opts, tvmaze_episode_id=None):
366  """
367  The tvmaze api returns different id's for season, episode and series.
368  MythTV stores only the series-id, therefore we need to fetch the correct id's
369  for season and episode for that series-id.
370  """
371  # option -D inetref season episode
372 
373  from lxml import etree
374  from MythTV.tvmaze import tvmaze_api as tvmaze
375 
376  if opts.debug:
377  dstr = "Function 'buildSingle' called with arguments: " + \
378  (" ".join(["'%s'" % i for i in args]))
379  if tvmaze_episode_id is not None:
380  dstr += " tvmaze_episode_id = %d" % tvmaze_episode_id
381  print(dstr)
382  inetref = args[0]
383  season = args[1]
384  episode = args[2]
385 
386  # set the session
387  if opts.session:
388  tvmaze.set_session(opts.session)
389 
390  # get the episode_id if not provided:
391  if tvmaze_episode_id is None:
392  episodes = tvmaze.get_show_episode_list(inetref)
393  for ep in (episodes):
394  if 0 and opts.debug:
395  print("tvmaze.get_show_episode_list(%s) returned :" % inetref)
396  for k, v in ep.__dict__.items():
397  print(k, " : ", v)
398  if ep.season == int(season) and ep.number == int(episode):
399  tvmaze_episode_id = ep.id
400  if opts.debug:
401  print(" Found tvmaze_episode_id : %d" % tvmaze_episode_id)
402  break
403 
404  # build xml:
405  tree = etree.XML(u'<metadata></metadata>')
406  item = buildSingleItem(inetref, season, tvmaze_episode_id)
407  if item is not None:
408  tree.append(item.toXML())
409  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
410  xml_declaration=True))
411 
412 
413 def buildSingleItem(inetref, season, episode_id):
414  """
415  This routine returns a video metadata item for one episode.
416  """
417  from MythTV import VideoMetadata
418  from MythTV.tvmaze import tvmaze_api as tvmaze
419  from MythTV.tvmaze import locales
420 
421  # get global info for all seasons/episodes:
422  posterList, fanartList, bannerList = get_show_art_lists(inetref)
423  show_info = tvmaze.get_show(inetref, populated=True)
424 
425  # get info for season episodes:
426  ep_info = tvmaze.get_episode_information(episode_id)
427  m = VideoMetadata()
428  if show_info.genres is not None and len(show_info.genres) > 0:
429  for g in show_info.genres:
430  try:
431  if g is not None and len(g) > 0:
432  m.categories.append(g)
433  except:
434  pass
435  m.title = check_item(m, ("title", show_info.name), ignore=False)
436  m.subtitle = check_item(m, ("title", ep_info.name), ignore=False)
437  m.season = check_item(m, ("season", ep_info.season), ignore=False)
438  m.episode = check_item(m, ("episode", ep_info.number), ignore=False)
439  m.description = check_item(m, ("description", ep_info.summary))
440  if m.description is None:
441  m.description = check_item(m, ("description", show_info.summary))
442  try:
443  sinfo = show_info.network['name']
444  if sinfo is not None and len(sinfo) > 0:
445  m.studios.append(sinfo)
446  except:
447  pass
448  m.inetref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
449  m.collectionref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
450  m.language = check_item(m, ("language", str(locales.Language.getstored(show_info.language))))
451  m.userrating = check_item(m, ("userrating", show_info.rating['average']))
452  try:
453  m.popularity = check_item(m, ("popularity", float(show_info.weight)), ignore=False)
454  except (TypeError, ValueError):
455  pass
456  # prefer episode airdate dates:
457  if ep_info.airdate:
458  m.releasedate = check_item(m, ("releasedate", ep_info.airdate))
459  m.year = check_item(m, ("year", ep_info.airdate.year))
460  elif show_info.premiere_date:
461  m.releasedate = check_item(m, ("releasedate", show_info.premiere_date))
462  m.year = check_item(m, ("year", show_info.premiere_date.year))
463  if ep_info.duration:
464  m.runtime = check_item(m, ("runtime", int(ep_info.duration)))
465 
466  for actor in show_info.cast:
467  try:
468  if len(actor.person.name) > 0 and len(actor.name) > 0:
469  d = {'name': actor.person.name, 'character': actor.name, 'job': 'Actor'}
470 
471  m.people.append(d)
472  except:
473  pass
474 
475  for member in show_info.crew:
476  try:
477  if len(member.name) > 0 and len(member.job) > 0:
478  d = {'name': member.name, 'job': member.job}
479  m.people.append(d)
480  except:
481  pass
482 
483  # get info for dedicated season:
484  season_info = show_info.seasons[int(season)]
485  #for k, v in season_info.__dict__.items():
486  #print(k, " : ", v)
487 
488  # prefer season coverarts over series coverart:
489  if season_info.images is not None and len(season_info.images) > 0:
490  m.images.append({'type': 'coverart', 'url': season_info.images['original'],
491  'thumb': season_info.images['medium']})
492 
493  # generate series coverart, fanart, and banners
494  for posterEntry in posterList:
495  if (posterEntry[0] is not None) and (posterEntry[1] is not None):
496  image_entry = {'type': 'coverart', 'url': posterEntry[0], 'thumb': posterEntry[1]}
497  elif posterEntry[0] is not None:
498  image_entry = {'type': 'coverart', 'url': posterEntry[0]}
499  # Avoid duplicate coverart entries
500  if image_entry not in m.images:
501  m.images.append(image_entry)
502 
503  for fanartEntry in fanartList:
504  if (fanartEntry[0] is not None) and (fanartEntry[1] is not None):
505  m.images.append({'type': 'fanart', 'url': fanartEntry[0], 'thumb': fanartEntry[1]})
506  elif fanartEntry[0] is not None:
507  m.images.append({'type': 'fanart', 'url': fanartEntry[0]})
508 
509  for bannerEntry in bannerList:
510  if (bannerEntry[0] is not None) and (bannerEntry[1] is not None):
511  m.images.append({'type': 'banner', 'url': bannerEntry[0], 'thumb': bannerEntry[1]})
512  elif bannerEntry[0] is not None:
513  m.images.append({'type': 'banner', 'url': bannerEntry[0]})
514 
515  # screenshot is associated to episode
516  if ep_info.images is not None and len(ep_info.images) > 0:
517  m.images.append({'type': 'screenshot', 'url': ep_info.images['original'],
518  'thumb': ep_info.images['medium']})
519  return m
520 
521 
522 def buildCollection(tvinetref, opts):
523  # option -C inetref
524  from lxml import etree
525  from MythTV import VideoMetadata
526  from MythTV.tvmaze import tvmaze_api as tvmaze
527  from MythTV.tvmaze import locales
528 
529  # set the session
530  if opts.session:
531  tvmaze.set_session(opts.session)
532 
533  if opts.debug:
534  print("Function 'buildCollection' called with argument '%s'" % tvinetref)
535 
536  show_info = tvmaze.get_show(tvinetref)
537  if opts.debug:
538  for k, v in show_info.__dict__.items():
539  print(k, " : ", v)
540 
541  tree = etree.XML(u'<metadata></metadata>')
542  m = VideoMetadata()
543  m.title = check_item(m, ("title", show_info.name), ignore=False)
544  m.description = check_item(m, ("description", show_info.summary))
545  if show_info.genres is not None and len(show_info.genres) > 0:
546  for g in show_info.genres:
547  try:
548  if g is not None and len(g) > 0:
549  m.categories.append(g)
550  except:
551  pass
552  m.inetref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
553  m.collectionref = check_item(m, ("collectionref", str(show_info.id)), ignore=False)
554  m.imdb = check_item(m, ("imdb", str(show_info.external_ids['imdb'])))
555  m.language = check_item(m, ("language", str(locales.Language.getstored(show_info.language))))
556  m.userrating = check_item(m, ("userrating", show_info.rating['average']))
557  m.runtime = check_item(m, ("runtime", show_info.runtime))
558  try:
559  m.popularity = check_item(m, ("popularity", float(show_info.weight)), ignore=False)
560  except (TypeError, ValueError):
561  pass
562  if show_info.premiere_date:
563  m.releasedate = check_item(m, ("releasedate", show_info.premiere_date))
564  m.year = check_item(m, ("year", show_info.premiere_date.year))
565  try:
566  sinfo = show_info.network['name']
567  if sinfo is not None and len(sinfo) > 0:
568  m.studios.append(sinfo)
569  except:
570  pass
571 
572  posterList, fanartList, bannerList = get_show_art_lists(show_info.id)
573 
574  # Generate image lines for every piece of artwork
575  for posterEntry in posterList:
576  if (posterEntry[0] is not None) and (posterEntry[1] is not None):
577  m.images.append({'type': 'coverart', 'url': posterEntry[0], 'thumb': posterEntry[1]})
578  elif posterEntry[0] is not None:
579  m.images.append({'type': 'coverart', 'url': posterEntry[0]})
580 
581  for fanartEntry in fanartList:
582  if (fanartEntry[0] is not None) and (fanartEntry[1] is not None):
583  m.images.append({'type': 'fanart', 'url': fanartEntry[0], 'thumb': fanartEntry[1]})
584  elif fanartEntry[0] is not None:
585  m.images.append({'type': 'fanart', 'url': fanartEntry[0]})
586 
587  for bannerEntry in bannerList:
588  if (bannerEntry[0] is not None) and (bannerEntry[1] is not None):
589  m.images.append({'type': 'banner', 'url': bannerEntry[0], 'thumb': bannerEntry[1]})
590  elif bannerEntry[0] is not None:
591  m.images.append({'type': 'banner', 'url': bannerEntry[0]})
592 
593  tree.append(m.toXML())
594 
595  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
596  xml_declaration=True))
597 
598 
600  from lxml import etree
601  version = etree.XML(u'<grabber></grabber>')
602  etree.SubElement(version, "name").text = __title__
603  etree.SubElement(version, "author").text = __author__
604  etree.SubElement(version, "thumbnail").text = 'tvmaze.png'
605  etree.SubElement(version, "command").text = 'tvmaze.py'
606  etree.SubElement(version, "type").text = 'television'
607  etree.SubElement(version, "description").text = \
608  'Search and metadata downloads for tvmaze.com'
609  etree.SubElement(version, "version").text = __version__
610  print_etree(etree.tostring(version, encoding='UTF-8', pretty_print=True,
611  xml_declaration=True))
612  sys.exit(0)
613 
614 
615 def performSelfTest(opts):
616  err = 0
617  try:
618  import lxml
619  except:
620  err = 1
621  print("Failed to import python lxml library.")
622  try:
623  import requests
624  import requests_cache
625  except:
626  err = 1
627  print("Failed to import python-requests or python-request-cache library.")
628  try:
629  import MythTV
630  except:
631  err = 1
632  print("Failed to import MythTV bindings. Check your `configure` output "
633  "to make sure installation was not disabled due to external "
634  "dependencies")
635  try:
636  from MythTV.tvmaze import tvmaze_api as tvmaze
637  if opts.debug:
638  print("File location: ", tvmaze.__file__)
639  print("TVMAZE Script Version: ", __version__)
640  print("TVMAZE-API version: ", tvmaze.MYTHTV_TVMAZE_API_VERSION)
641  except:
642  err = 1
643  print("Failed to import PyTVmaze library. This should have been included "
644  "with the python MythTV bindings.")
645  if not err:
646  print("Everything appears in order.")
647  sys.exit(err)
648 
649 
650 def main():
651  """
652  Main executor for MythTV's tvmaze grabber.
653  """
654 
655  parser = OptionParser()
656 
657  parser.add_option('-v', "--version", action="store_true", default=False,
658  dest="version", help="Display version and author")
659  parser.add_option('-t', "--test", action="store_true", default=False,
660  dest="test", help="Perform self-test for dependencies.")
661  parser.add_option('-M', "--list", action="store_true", default=False,
662  dest="tvlist", help="Get TV Shows matching search.")
663  parser.add_option('-D', "--data", action="store_true", default=False,
664  dest="tvdata", help="Get TV Show data.")
665  parser.add_option('-C', "--collection", action="store_true", default=False,
666  dest="collectiondata", help="Get Collection data.")
667  parser.add_option('-N', "--numbers", action="store_true", default=False,
668  dest="tvnumbers", help="Get Season and Episode numbers")
669  parser.add_option('-l', "--language", metavar="LANGUAGE", default=u'en',
670  dest="language", help="Specify language for filtering.")
671  parser.add_option('-a', "--area", metavar="COUNTRY", default=None,
672  dest="country", help="Specify country for custom data.")
673  parser.add_option('--debug', action="store_true", default=False,
674  dest="debug", help=("Disable caching and enable raw "
675  "data output."))
676  parser.add_option('--doctest', action="store_true", default=False,
677  dest="doctest", help="Run doctests")
678 
679  opts, args = parser.parse_args()
680 
681  if opts.debug:
682  print("Args: ", args)
683  print("Opts: ", opts)
684 
685  if opts.doctest:
686  import doctest
687  try:
688  with open("tvmaze_tests.txt") as f:
689  dtests = "".join(f.readlines())
690  main.__doc__ += dtests
691  except IOError:
692  pass
693  # perhaps try optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
694  doctest.testmod(verbose=opts.debug, optionflags=doctest.ELLIPSIS)
695 
696  if opts.version:
697  buildVersion()
698 
699  if opts.test:
700  performSelfTest(opts)
701 
702  if opts.debug:
703  import requests
704  else:
705  confdir = os.environ.get('MYTHCONFDIR', '')
706  if (not confdir) or (confdir == '/'):
707  confdir = os.environ.get('HOME', '')
708  if (not confdir) or (confdir == '/'):
709  print("Unable to find MythTV directory for metadata cache.")
710  sys.exit(1)
711  confdir = os.path.join(confdir, '.mythtv')
712  cachedir = os.path.join(confdir, 'cache')
713  if not os.path.exists(cachedir):
714  os.makedirs(cachedir)
715  cache_name = os.path.join(cachedir, 'py3tvmaze')
716  import requests
717  import requests_cache
718  requests_cache.install_cache(cache_name, backend='sqlite', expire_after=3600)
719 
720  with requests.Session() as s:
721  s.headers.update({'Accept': 'application/json',
722  'User-Agent': 'mythtv tvmaze grabber %s' % __version__})
723  opts.session = s
724  try:
725  if opts.tvlist:
726  # option -M title
727  if (len(args) != 1) or (len(args[0]) == 0):
728  sys.stdout.write('ERROR: tvmaze -M requires exactly one non-empty argument')
729  sys.exit(1)
730  buildList(args[0], opts)
731 
732  if opts.tvnumbers:
733  # either option -N inetref subtitle
734  # or option -N title subtitle
735  if (len(args) != 2) or (len(args[0]) == 0) or (len(args[1]) == 0):
736  sys.stdout.write('ERROR: tvmaze -N requires exactly two non-empty arguments')
737  sys.exit(1)
738  buildNumbers(args, opts)
739 
740  if opts.tvdata:
741  # option -D inetref season episode
742  if (len(args) != 3) or (len(args[0]) == 0) or (len(args[1]) == 0) or (len(args[2]) == 0):
743  sys.stdout.write('ERROR: tvmaze -D requires exactly three non-empty arguments')
744  sys.exit(1)
745  buildSingle(args, opts)
746 
747  if opts.collectiondata:
748  # option -C inetref
749  if (len(args) != 1) or (len(args[0]) == 0):
750  sys.stdout.write('ERROR: tvmaze -C requires exactly one non-empty argument')
751  sys.exit(1)
752  buildCollection(args[0], opts)
753  except:
754  if opts.debug:
755  raise
756  sys.stdout.write('ERROR: ' + str(sys.exc_info()[0]) + ' : ' + str(sys.exc_info()[1]) + '\n')
757  sys.exit(1)
758 
759 
760 if __name__ == "__main__":
761  main()
VideoMetadata
Definition: videometadata.h:24
tvmaze.print_etree
def print_etree(etostr)
Definition: tvmaze.py:15
tvmaze.buildCollection
def buildCollection(tvinetref, opts)
Definition: tvmaze.py:522
tvmaze.buildNumbers
def buildNumbers(args, opts)
Definition: tvmaze.py:139
tvmaze.buildSingle
def buildSingle(args, opts, tvmaze_episode_id=None)
Definition: tvmaze.py:365
tvmaze.get_show_art_lists
def get_show_art_lists(tvmaze_show_id)
Definition: tvmaze.py:37
tvmaze.main
def main()
Definition: tvmaze.py:650
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
tvmaze.check_item
def check_item(m, mitem, ignore=True)
Definition: tvmaze.py:21
tvmaze.performSelfTest
def performSelfTest(opts)
Definition: tvmaze.py:615
tvmaze.buildVersion
def buildVersion()
Definition: tvmaze.py:599
tvmaze.buildList
def buildList(tvtitle, opts)
Definition: tvmaze.py:68
tvmaze.buildSingleItem
def buildSingleItem(inetref, season, episode_id)
Definition: tvmaze.py:413