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