MythTV  master
tmdb3.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 # ----------------------
4 # Name: tmdb3.py
5 # Python Script
6 # Author: Raymond Wagner
7 # Purpose: This python script is intended to translate lookups between the
8 # TheMovieDB.org V3 API and MythTV's internal metadata format.
9 # http://www.mythtv.org/wiki/MythVideo_Grabber_Script_Format
10 # http://help.themoviedb.org/kb/api/about-3
11 #-----------------------
12 __title__ = "TheMovieDB.org V3"
13 __author__ = "Raymond Wagner"
14 __version__ = "0.3.7"
15 # 0.1.0 Initial version
16 # 0.2.0 Add language support, move cache to home directory
17 # 0.3.0 Enable version detection to allow use in MythTV
18 # 0.3.1 Add --test parameter for proper compatibility with mythmetadatalookup
19 # 0.3.2 Add --area parameter to allow country selection for release date and
20 # parental ratings
21 # 0.3.3 Use translated title if available
22 # 0.3.4 Add support for finding by IMDB under -D (simulate previous version)
23 # 0.3.5 Add debugging mode
24 # 0.3.6 Add handling for TMDB site and library returning null results in
25 # search. This should only need to be a temporary fix, and should be
26 # resolved upstream.
27 # 0.3.7 Add handling for TMDB site returning insufficient results from a
28 # query
29 # 0.3.7.a : Added compatibiliy to python3, tested with python 3.6 and 2.7
30 
31 from optparse import OptionParser
32 import sys
33 import signal
34 
35 def print_etree(etostr):
36  """lxml.etree.tostring is a bytes object in python3, and a str in python2.
37  """
38  if sys.version_info[0] == 2:
39  sys.stdout.write(etostr)
40  else:
41  sys.stdout.write(etostr.decode())
42 
43 def timeouthandler(signal, frame):
44  raise RuntimeError("Timed out")
45 
46 def buildSingle(inetref, opts):
47  from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
48  from MythTV.tmdb3 import Movie
49  from MythTV import VideoMetadata
50  from lxml import etree
51 
52  import re
53  if re.match('^0[0-9]{6}$', inetref):
54  movie = Movie.fromIMDB(inetref)
55  else:
56  movie = Movie(inetref)
57 
58  tree = etree.XML(u'<metadata></metadata>')
59  mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
60  ['releasedate', 'releasedate'], ['tagline', 'tagline'],
61  ['description', 'overview'], ['homepage', 'homepage'],
62  ['userrating', 'userrating'], ['popularity', 'popularity'],
63  ['budget', 'budget'], ['revenue', 'revenue']]
64  m = VideoMetadata()
65  for i,j in mapping:
66  try:
67  if getattr(movie, j):
68  setattr(m, i, getattr(movie, j))
69  except TMDBRequestInvalid:
70  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
71  xml_declaration=True))
72  sys.exit()
73 
74  if movie.title:
75  m.title = movie.title
76 
77  releases = list(movie.releases.items())
78 
79 # get the release date for the wanted country
80 # TODO if that is not part of the reply use the primary release date (Primary=true)
81 # if that is not part of the reply use whatever release date is first in list
82 # if there is not a single release date in the reply, then leave it empty
83  if len(releases) > 0:
84  if opts.country:
85  # resort releases with selected country at top to ensure it
86  # is selected by the metadata libraries
87  r = list(zip(*releases))
88  if opts.country in r[0]:
89  index = r[0].index(opts.country)
90  releases.insert(0, releases.pop(index))
91 
92  m.releasedate = releases[0][1].releasedate
93 
94  m.inetref = str(movie.id)
95  if movie.collection:
96  m.collectionref = str(movie.collection.id)
97  if m.releasedate:
98  m.year = m.releasedate.year
99  for country, release in releases:
100  if release.certification:
101  m.certifications[country] = release.certification
102  for genre in movie.genres:
103  m.categories.append(genre.name)
104  for studio in movie.studios:
105  m.studios.append(studio.name)
106  for country in movie.countries:
107  m.countries.append(country.name)
108  for cast in movie.cast:
109  d = {'name':cast.name, 'character':cast.character, 'department':'Actors',
110  'job':'Actor', 'url':'http://www.themoviedb.org/people/{0}'.format(cast.id)}
111  if cast.profile: d['thumb'] = cast.profile.geturl()
112  m.people.append(d)
113  for crew in movie.crew:
114  d = {'name':crew.name, 'job':crew.job, 'department':crew.department,
115  'url':'http://www.themoviedb.org/people/{0}'.format(crew.id)}
116  if crew.profile: d['thumb'] = crew.profile.geturl()
117  m.people.append(d)
118  for backdrop in movie.backdrops:
119  m.images.append({'type':'fanart', 'url':backdrop.geturl(),
120  'thumb':backdrop.geturl(backdrop.sizes()[0]),
121  'height':str(backdrop.height),
122  'width':str(backdrop.width)})
123  for poster in movie.posters:
124  m.images.append({'type':'coverart', 'url':poster.geturl(),
125  'thumb':poster.geturl(poster.sizes()[0]),
126  'height':str(poster.height),
127  'width':str(poster.width)})
128  tree.append(m.toXML())
129  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
130  xml_declaration=True))
131  sys.exit()
132 
133 def buildList(query, opts):
134  # TEMPORARY FIX:
135  # replace all dashes from queries to work around search behavior
136  # as negative to all text that comes afterwards
137  query = query.replace('-',' ')
138 
139  from MythTV.tmdb3 import searchMovie
140  from MythTV import VideoMetadata
141  from lxml import etree
142  results = iter(searchMovie(query))
143  tree = etree.XML(u'<metadata></metadata>')
144  mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
145  ['releasedate', 'releasedate'], ['tagline', 'tagline'],
146  ['description', 'overview'], ['homepage', 'homepage'],
147  ['userrating', 'userrating'], ['popularity', 'popularity']]
148 
149  count = 0
150  while True:
151  try:
152  res = next(results)
153  except StopIteration:
154  # end of results
155  break
156  except IndexError:
157  # unexpected end of results
158  # we still want to return whatever we have so far
159  break
160 
161  if res is None:
162  # faulty data, skip it and continue
163  continue
164 
165  m = VideoMetadata()
166  for i,j in mapping:
167  if getattr(res, j):
168  setattr(m, i, getattr(res, j))
169  m.inetref = str(res.id)
170 
171  if res.title:
172  m.title = res.title
173 
174  #TODO:
175  # should releasedate and year be pulled from the country-specific data
176  # or should it be left to the default information to cut down on
177  # traffic from searches
178  if res.releasedate:
179  m.year = res.releasedate.year
180  if res.backdrop:
181  b = res.backdrop
182  m.images.append({'type':'fanart', 'url':b.geturl(),
183  'thumb':b.geturl(b.sizes()[0])})
184  if res.poster:
185  p = res.poster
186  m.images.append({'type':'coverart', 'url':p.geturl(),
187  'thumb':p.geturl(p.sizes()[0])})
188  tree.append(m.toXML())
189  count += 1
190  if count >= 60:
191  # page limiter, dont want to overload the server
192  break
193 
194  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
195  xml_declaration=True))
196  sys.exit(0)
197 
198 def buildCollection(inetref, opts):
199  from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
200  from MythTV.tmdb3 import Collection
201  from MythTV import VideoMetadata
202  from lxml import etree
203  collection = Collection(inetref)
204  tree = etree.XML(u'<metadata></metadata>')
205  m = VideoMetadata()
206  m.collectionref = str(collection.id)
207  try:
208  m.title = collection.name
209  except TMDBRequestInvalid:
210  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
211  xml_declaration=True))
212  sys.exit()
213  if collection.backdrop:
214  b = collection.backdrop
215  m.images.append({'type':'fanart', 'url':b.geturl(),
216  'thumb':b.geturl(b.sizes()[0])})
217  if collection.poster:
218  p = collection.poster
219  m.images.append({'type':'coverart', 'url':p.geturl(),
220  'thumb':p.geturl(p.sizes()[0])})
221  tree.append(m.toXML())
222  print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
223  xml_declaration=True))
224  sys.exit()
225 
227  from lxml import etree
228  version = etree.XML(u'<grabber></grabber>')
229  etree.SubElement(version, "name").text = __title__
230  etree.SubElement(version, "author").text = __author__
231  etree.SubElement(version, "thumbnail").text = 'tmdb.png'
232  etree.SubElement(version, "command").text = 'tmdb3.py'
233  etree.SubElement(version, "type").text = 'movie'
234  etree.SubElement(version, "description").text = \
235  'Search and metadata downloads for themoviedb.org'
236  etree.SubElement(version, "version").text = __version__
237  etree.SubElement(version, "accepts").text = 'tmdb.py'
238  etree.SubElement(version, "accepts").text = 'tmdb.pl'
239  print_etree(etree.tostring(version, encoding='UTF-8', pretty_print=True,
240  xml_declaration=True))
241  sys.exit(0)
242 
244  err = 0
245  try:
246  import MythTV
247  except:
248  err = 1
249  print ("Failed to import MythTV bindings. Check your `configure` output "
250  "to make sure installation was not disabled due to external "
251  "dependencies")
252  try:
253  import MythTV.tmdb3
254  except:
255  err = 1
256  print ("Failed to import PyTMDB3 library. This should have been included "
257  "with the python MythTV bindings.")
258  try:
259  import lxml
260  except:
261  err = 1
262  print ("Failed to import python lxml library.")
263 
264  if not err:
265  print ("Everything appears in order.")
266  sys.exit(err)
267 
268 def main():
269  parser = OptionParser()
270 
271  parser.add_option('-v', "--version", action="store_true", default=False,
272  dest="version", help="Display version and author")
273  parser.add_option('-t', "--test", action="store_true", default=False,
274  dest="test", help="Perform self-test for dependencies.")
275  parser.add_option('-M', "--movielist", action="store_true", default=False,
276  dest="movielist", help="Get Movies matching search.")
277  parser.add_option('-D', "--moviedata", action="store_true", default=False,
278  dest="moviedata", help="Get Movie data.")
279  parser.add_option('-C', "--collection", action="store_true", default=False,
280  dest="collectiondata", help="Get Collection data.")
281  parser.add_option('-l', "--language", metavar="LANGUAGE", default=u'en',
282  dest="language", help="Specify language for filtering.")
283  parser.add_option('-a', "--area", metavar="COUNTRY", default=None,
284  dest="country", help="Specify country for custom data.")
285  parser.add_option('--debug', action="store_true", default=False,
286  dest="debug", help=("Disable caching and enable raw "
287  "data output."))
288 
289  opts, args = parser.parse_args()
290 
291  signal.signal(signal.SIGALRM, timeouthandler)
292  signal.alarm(180)
293 
294  if opts.version:
295  buildVersion()
296 
297  if opts.test:
299 
300  from MythTV.tmdb3 import set_key, set_cache, set_locale
301  set_key('c27cb71cff5bd76e1a7a009380562c62')
302 
303  if opts.debug:
304  import MythTV.tmdb3
305  MythTV.tmdb3.request.DEBUG = True
306  set_cache(engine='null')
307  else:
308  import os
309  confdir = os.environ.get('MYTHCONFDIR', '')
310  if (not confdir) or (confdir == '/'):
311  confdir = os.environ.get('HOME', '')
312  if (not confdir) or (confdir == '/'):
313  print ("Unable to find MythTV directory for metadata cache.")
314  sys.exit(1)
315  confdir = os.path.join(confdir, '.mythtv')
316  cachedir = os.path.join(confdir, 'cache')
317  if not os.path.exists(cachedir):
318  os.makedirs(cachedir)
319  cachepath = os.path.join(cachedir, 'pytmdb3.cache')
320  set_cache(engine='file', filename=cachepath)
321 
322  if opts.language:
323  set_locale(language=opts.language, fallthrough=True)
324  if opts.country:
325  set_locale(country=opts.country, fallthrough=True)
326 
327  if (len(args) != 1) or (args[0] == ''):
328  sys.stdout.write('ERROR: tmdb3.py requires exactly one non-empty argument')
329  sys.exit(1)
330 
331  try:
332  if opts.movielist:
333  buildList(args[0], opts)
334 
335  if opts.moviedata:
336  buildSingle(args[0], opts)
337 
338  if opts.collectiondata:
339  buildCollection(args[0], opts)
340  except RuntimeError as exc:
341  sys.stdout.write('ERROR: ' + str(exc) + ' exception')
342  sys.exit(1)
343 
344 if __name__ == '__main__':
345  main()
tmdb3.performSelfTest
def performSelfTest()
Definition: tmdb3.py:243
VideoMetadata
Definition: videometadata.h:25
tmdb3.print_etree
def print_etree(etostr)
Definition: tmdb3.py:35
tmdb3.buildSingle
def buildSingle(inetref, opts)
Definition: tmdb3.py:46
tmdb3.timeouthandler
def timeouthandler(signal, frame)
Definition: tmdb3.py:43
tmdb3.main
def main()
Definition: tmdb3.py:268
tmdb3.buildVersion
def buildVersion()
Definition: tmdb3.py:226
tmdb3.buildList
def buildList(query, opts)
Definition: tmdb3.py:133
tmdb3.buildCollection
def buildCollection(inetref, opts)
Definition: tmdb3.py:198