/
lookup.py
484 lines (425 loc) · 17.9 KB
/
lookup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# -*- coding: UTF-8 -*-
# ----------------------
# Name: tmdb3.py
# Python Script
# Author: Raymond Wagner
# Purpose: This python script is intended to translate lookups between the
# TheMovieDB.org V3 API and MythTV's internal metadata format.
# http://www.mythtv.org/wiki/MythVideo_Grabber_Script_Format
# http://help.themoviedb.org/kb/api/about-3
# Code was originally in metadata/Movie/tmdb3.py.
# Moved here so it can be called for movies or TV
#-----------------------
__title__ = "TheMovieDB.org V3"
__author__ = "Raymond Wagner, Roland Ernst"
__version__ = "0.3.9"
# 0.1.0 Initial version
# 0.2.0 Add language support, move cache to home directory
# 0.3.0 Enable version detection to allow use in MythTV
# 0.3.1 Add --test parameter for proper compatibility with mythmetadatalookup
# 0.3.2 Add --area parameter to allow country selection for release date and
# parental ratings
# 0.3.3 Use translated title if available
# 0.3.4 Add support for finding by IMDB under -D (simulate previous version)
# 0.3.5 Add debugging mode
# 0.3.6 Add handling for TMDB site and library returning null results in
# search. This should only need to be a temporary fix, and should be
# resolved upstream.
# 0.3.7 Add handling for TMDB site returning insufficient results from a
# query
# 0.3.7.a : Added compatibiliy to python3, tested with python 3.6 and 2.7
# 0.3.8 Sort posters by system language or 'en', if not found for given language
# 0.3.9 Support TV lookup
# ~ from optparse import OptionParser
import sys
import signal
def print_etree(etostr):
"""lxml.etree.tostring is a bytes object in python3, and a str in python2.
"""
if sys.version_info[0] == 2:
sys.stdout.write(etostr)
else:
sys.stdout.write(etostr.decode())
def timeouthandler(signal, frame):
raise RuntimeError("Timed out")
def buildSingle(inetref, opts):
from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
from MythTV.tmdb3 import Movie, get_locale
from MythTV import VideoMetadata
from lxml import etree
import locale as py_locale
import re
if re.match('^0[0-9]{6}$', inetref):
movie = Movie.fromIMDB(inetref)
else:
movie = Movie(inetref)
tree = etree.XML(u'<metadata></metadata>')
mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
['releasedate', 'releasedate'], ['tagline', 'tagline'],
['description', 'overview'], ['homepage', 'homepage'],
['userrating', 'userrating'], ['popularity', 'popularity'],
['budget', 'budget'], ['revenue', 'revenue']]
m = VideoMetadata()
for i,j in mapping:
try:
if getattr(movie, j):
setattr(m, i, getattr(movie, j))
except TMDBRequestInvalid:
return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True)
if movie.title:
m.title = movie.title
releases = list(movie.releases.items())
# get the release date for the wanted country
# TODO if that is not part of the reply use the primary release date (Primary=true)
# if that is not part of the reply use whatever release date is first in list
# if there is not a single release date in the reply, then leave it empty
if len(releases) > 0:
if opts.country:
# resort releases with selected country at top to ensure it
# is selected by the metadata libraries
r = list(zip(*releases))
if opts.country in r[0]:
index = r[0].index(opts.country)
releases.insert(0, releases.pop(index))
m.releasedate = releases[0][1].releasedate
m.inetref = str(movie.id)
if movie.collection:
m.collectionref = str(movie.collection.id)
if m.releasedate:
m.year = m.releasedate.year
for country, release in releases:
if release.certification:
m.certifications[country] = release.certification
for genre in movie.genres:
m.categories.append(genre.name)
for studio in movie.studios:
m.studios.append(studio.name)
for country in movie.countries:
m.countries.append(country.name)
for cast in movie.cast:
d = {'name':cast.name, 'character':cast.character, 'department':'Actors',
'job':'Actor', 'url':'http://www.themoviedb.org/people/{0}'.format(cast.id)}
if cast.profile: d['thumb'] = cast.profile.geturl()
m.people.append(d)
for crew in movie.crew:
d = {'name':crew.name, 'job':crew.job, 'department':crew.department,
'url':'http://www.themoviedb.org/people/{0}'.format(crew.id)}
if crew.profile: d['thumb'] = crew.profile.geturl()
m.people.append(d)
for backdrop in movie.backdrops:
m.images.append({'type':'fanart', 'url':backdrop.geturl(),
'thumb':backdrop.geturl(backdrop.sizes()[0]),
'height':str(backdrop.height),
'width':str(backdrop.width)})
# tmdb already sorts the posters by language
# if no poster of given language was found,
# try to sort by system language and then by language "en"
system_language = py_locale.getdefaultlocale()[0].split("_")[0]
locale_language = get_locale().language
if opts.debug:
print("system_language : ", system_language)
print("locale_language : ", locale_language)
loc_posters = movie.posters
if len(loc_posters) and loc_posters[0].language != locale_language \
and locale_language != system_language:
if opts.debug:
print("1: No poster found for language '%s', trying to sort posters by '%s' :"
%(locale_language, system_language))
loc_posters = sorted(movie.posters,
key = lambda x: x.language==system_language, reverse = True)
if len(loc_posters) and loc_posters[0].language != system_language \
and loc_posters[0].language != locale_language:
if opts.debug:
print("2: No poster found for language '%s', trying to sort posters by '%s' :"
%(system_language, "en"))
loc_posters = sorted(movie.posters,
key = lambda x: x.language=="en", reverse = True)
for poster in loc_posters:
if opts.debug:
print("Poster : ", poster.language, " | ", poster.userrating,
"\t | ", poster.geturl())
m.images.append({'type':'coverart', 'url':poster.geturl(),
'thumb':poster.geturl(poster.sizes()[0]),
'height':str(poster.height),
'width':str(poster.width)})
tree.append(m.toXML())
return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True)
def buildMovieList(query, opts):
# TEMPORARY FIX:
# replace all dashes from queries to work around search behavior
# as negative to all text that comes afterwards
query = query.replace('-',' ')
from MythTV.tmdb3 import searchMovie
from MythTV import VideoMetadata
from lxml import etree
results = iter(searchMovie(query))
tree = etree.XML(u'<metadata></metadata>')
mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
['releasedate', 'releasedate'], ['tagline', 'tagline'],
['description', 'overview'], ['homepage', 'homepage'],
['userrating', 'userrating'], ['popularity', 'popularity']]
count = 0
while True:
try:
res = next(results)
except StopIteration:
# end of results
break
except IndexError:
# unexpected end of results
# we still want to return whatever we have so far
break
if res is None:
# faulty data, skip it and continue
continue
m = VideoMetadata()
for i,j in mapping:
if getattr(res, j):
setattr(m, i, getattr(res, j))
m.inetref = str(res.id)
if res.title:
m.title = res.title
#TODO:
# should releasedate and year be pulled from the country-specific data
# or should it be left to the default information to cut down on
# traffic from searches
if res.releasedate:
m.year = res.releasedate.year
if res.backdrop:
b = res.backdrop
m.images.append({'type':'fanart', 'url':b.geturl(),
'thumb':b.geturl(b.sizes()[0])})
if res.poster:
p = res.poster
m.images.append({'type':'coverart', 'url':p.geturl(),
'thumb':p.geturl(p.sizes()[0])})
tree.append(m.toXML())
count += 1
if count >= 60:
# page limiter, dont want to overload the server
break
return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True)
def buildTVList(query, opts):
from MythTV.tmdb3 import searchSeries
from MythTV import VideoMetadata
from lxml import etree
from datetime import date
resultsx = searchSeries(query)
results = iter(resultsx) # searchSeries(query))
mapping = [['language', 'original_language'],
['title', 'name'], ['inetref','id'],
['collectionref','id'], ['description','overview'],
['releasedate','first_air_date']]
tree = etree.XML(u'<metadata></metadata>')
count = 0
while True:
try:
res = next(results)
except StopIteration:
# end of results
break
except IndexError:
# unexpected end of results
# we still want to return whatever we have so far
break
if res is None:
# faulty data, skip it and continue
continue
m = VideoMetadata()
for i,j in mapping:
if getattr(res, j):
setattr(m, i, getattr(res, j))
# These need to be strings not ints
m.inetref = str(res.id)
m.collectionref = str(res.id)
if res.backdrop:
b = res.backdrop
m.images.append({'type':'fanart', 'url':b.geturl(),
'thumb':b.geturl(b.sizes()[0])})
if res.poster:
p = res.poster
m.images.append({'type':'coverart', 'url':p.geturl(),
'thumb':p.geturl(p.sizes()[0])})
tree.append(m.toXML())
count += 1
if count >= 60:
# page limiter, dont want to overload the server
break
return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True)
# for buildEpisode, args are one of the following
# - title subtitle (-N command line)
# - inetref subtitle (-N command line)
# - inetref season episode (-D command line)
def buildEpisode(args, opts):
query = args[0]
from MythTV.tmdb3 import Series, Season, Episode
from MythTV import VideoMetadata
from lxml import etree
from MythTV.tmdb3 import searchSeries
query_is_numeric = False
try:
query_is_numeric = query.isnumeric()
except AttributeError:
# python2 and ascii strings
query_is_numeric = query.isdigit()
if query_is_numeric:
inetref = query
else:
results = searchSeries(query)
series = results[0]
inetref = str(series.id)
series = Series(inetref)
season_number = series.number_of_seasons
episode_number = None
subtitle = None
if len(args) == 2:
subtitle = args[1]
elif len(args) == 3:
season_number = int(args[1])
episode_number = int(args[2])
episode = None
if season_number > series.number_of_seasons:
sys.stdout.write('ERROR: Episode not found: ' + str(args))
return 9
# process seasons backwards because it is more likely
# that you have a recent one than an old one
while season_number > 0:
season = Season(inetref,str(season_number))
if episode_number:
episode = season.episodes[episode_number]
break
for ep_num, ep in season.episodes.items():
if ep.name == subtitle:
episode = ep
episode_number = int(ep_num)
break
if episode:
break
season_number = season_number - 1
if not episode_number and not episode:
sys.stdout.write('ERROR: Episode not found: ' + str(args))
return 9
# reload episode with full details
episode = Episode(inetref,season_number,episode_number)
tree = etree.XML(u'<metadata></metadata>')
mapping = [['subtitle','name'],
['description', 'overview'], ['season', 'season_number'],
['episode', 'episode_number'], ['releasedate', 'air_date']]
m = VideoMetadata()
m.title = series.name
for i,j in mapping:
if getattr(episode, j):
setattr(m, i, getattr(episode, j))
# These need to be strings not ints
m.inetref = inetref
m.collectionref = inetref
for cast in episode.cast:
d = {'name':cast.name, 'character':cast.character, 'department':'Actors',
'job':'Actor', 'url':'http://www.themoviedb.org/people/{0}'.format(cast.id)}
if cast.profile: d['thumb'] = cast.profile.geturl()
m.people.append(d)
for crew in episode.crew:
d = {'name':crew.name, 'job':crew.job, 'department':crew.department,
'url':'http://www.themoviedb.org/people/{0}'.format(crew.id)}
if crew.profile: d['thumb'] = crew.profile.geturl()
m.people.append(d)
for guest in episode.guest_stars:
d = {'name':guest.name, 'job':"Guest Star",
'url':'http://www.themoviedb.org/people/{0}'.format(guest.id)}
if guest.profile: d['thumb'] = guest.profile.geturl()
m.people.append(d)
if episode.still:
b = episode.still
m.images.append({'type':'screenshot', 'url':b.geturl(),
'thumb':b.geturl(b.sizes()[0])})
if season.poster:
p = season.poster
m.images.append({'type':'coverart', 'url':p.geturl(),
'thumb':p.geturl(p.sizes()[0])})
m.language = series.original_language
if series.backdrop:
b = series.backdrop
m.images.append({'type':'fanart', 'url':b.geturl(),
'thumb':b.geturl(b.sizes()[0])})
for genre in series.genres:
m.categories.append(genre.name)
tree.append(m.toXML())
return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True)
def buildCollection(inetref, opts):
from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
from MythTV.tmdb3 import Collection
from MythTV import VideoMetadata
from lxml import etree
collection = Collection(inetref)
tree = etree.XML(u'<metadata></metadata>')
m = VideoMetadata()
m.collectionref = str(collection.id)
try:
m.title = collection.name
except TMDBRequestInvalid:
print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True))
sys.exit()
if collection.backdrop:
b = collection.backdrop
m.images.append({'type':'fanart', 'url':b.geturl(),
'thumb':b.geturl(b.sizes()[0])})
if collection.poster:
p = collection.poster
m.images.append({'type':'coverart', 'url':p.geturl(),
'thumb':p.geturl(p.sizes()[0])})
tree.append(m.toXML())
return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True)
# For television only - get series info
# Same results as for television search but only 1 result
def buildTVSeries(inetref, opts):
from MythTV.tmdb3 import Series
from MythTV import VideoMetadata
from lxml import etree
series = Series(inetref)
mapping = [['language', 'original_language'],
['title', 'name'], ['inetref','id'],
['collectionref','id'], ['description','overview'],
['releasedate','first_air_date']]
tree = etree.XML(u'<metadata></metadata>')
m = VideoMetadata()
for i,j in mapping:
if getattr(series, j):
setattr(m, i, getattr(series, j))
# These need to be strings not ints
m.inetref = str(series.id)
m.collectionref = str(series.id)
for genre in series.genres:
m.categories.append(genre.name)
if series.backdrop:
b = series.backdrop
m.images.append({'type':'fanart', 'url':b.geturl(),
'thumb':b.geturl(b.sizes()[0])})
if series.poster:
p = series.poster
m.images.append({'type':'coverart', 'url':p.geturl(),
'thumb':p.geturl(p.sizes()[0])})
tree.append(m.toXML())
return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
xml_declaration=True)
def buildVersion(showType, command):
from lxml import etree
version = etree.XML(u'<grabber></grabber>')
etree.SubElement(version, "name").text = __title__ + ' ' + showType
etree.SubElement(version, "author").text = __author__
etree.SubElement(version, "thumbnail").text = 'tmdb.png'
etree.SubElement(version, "command").text = command
etree.SubElement(version, "type").text = showType
etree.SubElement(version, "description").text = \
'Search and metadata downloads for themoviedb.org'
etree.SubElement(version, "version").text = __version__
etree.SubElement(version, "accepts").text = 'tmdb.py'
etree.SubElement(version, "accepts").text = 'tmdb.pl'
return etree.tostring(version, encoding='UTF-8', pretty_print=True,
xml_declaration=True)