Tidy up the Turing license text; more tweaks.
[pyTivo/wmcbrine.git] / metadata.py
blob2a57415290dda671a2c6ad7f07eb2417a2c99408
1 #!/usr/bin/env python
3 import hashlib
4 import logging
5 import os
6 import struct
7 import subprocess
8 import sys
9 from datetime import datetime
10 from itertools import izip
11 from xml.dom import minidom
12 from xml.parsers import expat
13 try:
14 import plistlib
15 except:
16 pass
18 import mutagen
19 from lrucache import LRUCache
21 import config
22 import plugins.video.transcode
23 import turing
25 # Something to strip
26 TRIBUNE_CR = ' Copyright Tribune Media Services, Inc.'
28 TV_RATINGS = {'TV-Y7': 1, 'TV-Y': 2, 'TV-G': 3, 'TV-PG': 4, 'TV-14': 5,
29 'TV-MA': 6, 'TV-NR': 7, 'TVY7': 1, 'TVY': 2, 'TVG': 3,
30 'TVPG': 4, 'TV14': 5, 'TVMA': 6, 'TVNR': 7, 'Y7': 1,
31 'Y': 2, 'G': 3, 'PG': 4, '14': 5, 'MA': 6, 'NR': 7,
32 'UNRATED': 7, 'X1': 1, 'X2': 2, 'X3': 3, 'X4': 4, 'X5': 5,
33 'X6': 6, 'X7': 7}
35 MPAA_RATINGS = {'G': 1, 'PG': 2, 'PG-13': 3, 'PG13': 3, 'R': 4, 'X': 5,
36 'NC-17': 6, 'NC17': 6, 'NR': 8, 'UNRATED': 8, 'G1': 1,
37 'P2': 2, 'P3': 3, 'R4': 4, 'X5': 5, 'N6': 6, 'N8': 8}
39 STAR_RATINGS = {'1': 1, '1.5': 2, '2': 3, '2.5': 4, '3': 5, '3.5': 6,
40 '4': 7, '*': 1, '**': 3, '***': 5, '****': 7, 'X1': 1,
41 'X2': 2, 'X3': 3, 'X4': 4, 'X5': 5, 'X6': 6, 'X7': 7}
43 HUMAN = {'mpaaRating': {1: 'G', 2: 'PG', 3: 'PG-13', 4: 'R', 5: 'X',
44 6: 'NC-17', 8: 'NR'},
45 'tvRating': {1: 'Y7', 2: 'Y', 3: 'G', 4: 'PG', 5: '14',
46 6: 'MA', 7: 'NR'},
47 'starRating': {1: '1', 2: '1.5', 3: '2', 4: '2.5', 5: '3',
48 6: '3.5', 7: '4'}}
50 BOM = '\xef\xbb\xbf'
52 GB = 1024 ** 3
53 MB = 1024 ** 2
54 KB = 1024
56 tivo_cache = LRUCache(50)
57 mp4_cache = LRUCache(50)
58 dvrms_cache = LRUCache(50)
59 nfo_cache = LRUCache(50)
61 mswindows = (sys.platform == "win32")
63 def get_mpaa(rating):
64 return HUMAN['mpaaRating'].get(rating, 'NR')
66 def get_tv(rating):
67 return HUMAN['tvRating'].get(rating, 'NR')
69 def get_stars(rating):
70 return HUMAN['starRating'].get(rating, '')
72 def human_size(raw):
73 raw = float(raw)
74 if raw > GB:
75 tsize = '%.2f GB' % (raw / GB)
76 elif raw > MB:
77 tsize = '%.2f MB' % (raw / MB)
78 elif raw > KB:
79 tsize = '%.2f KB' % (raw / KB)
80 else:
81 tsize = '%d Bytes' % raw
82 return tsize
84 def tag_data(element, tag):
85 for name in tag.split('/'):
86 found = False
87 for new_element in element.childNodes:
88 if new_element.nodeName == name:
89 found = True
90 element = new_element
91 break
92 if not found:
93 return ''
94 if not element.firstChild:
95 return ''
96 return element.firstChild.data
98 def _vtag_data(element, tag):
99 for name in tag.split('/'):
100 new_element = element.getElementsByTagName(name)
101 if not new_element:
102 return []
103 element = new_element[0]
104 elements = element.getElementsByTagName('element')
105 return [x.firstChild.data for x in elements if x.firstChild]
107 def _vtag_data_alternate(element, tag):
108 elements = [element]
109 for name in tag.split('/'):
110 new_elements = []
111 for elmt in elements:
112 new_elements += elmt.getElementsByTagName(name)
113 elements = new_elements
114 return [x.firstChild.data for x in elements if x.firstChild]
116 def _tag_value(element, tag):
117 item = element.getElementsByTagName(tag)
118 if item:
119 value = item[0].attributes['value'].value
120 return int(value[0])
122 def from_moov(full_path):
123 if full_path in mp4_cache:
124 return mp4_cache[full_path]
126 metadata = {}
127 len_desc = 0
129 try:
130 mp4meta = mutagen.File(unicode(full_path, 'utf-8'))
131 assert(mp4meta)
132 except:
133 mp4_cache[full_path] = {}
134 return {}
136 # The following 1-to-1 correspondence of atoms to pyTivo
137 # variables is TV-biased
138 keys = {'tvnn': 'callsign', 'tven': 'episodeNumber',
139 'tvsh': 'seriesTitle'}
141 for key, value in mp4meta.items():
142 if type(value) == list:
143 value = value[0]
144 if key == 'stik':
145 metadata['isEpisode'] = ['false', 'true'][value == 'TV Show']
146 elif key in keys:
147 metadata[keys[key]] = value
148 # These keys begin with the copyright symbol \xA9
149 elif key == '\xa9day':
150 if len(value) == 4:
151 value += '-01-01T16:00:00Z'
152 metadata['originalAirDate'] = value
153 #metadata['time'] = value
154 elif key in ['\xa9gen', 'gnre']:
155 for k in ('vProgramGenre', 'vSeriesGenre'):
156 if k in metadata:
157 metadata[k].append(value)
158 else:
159 metadata[k] = [value]
160 elif key == '\xa9nam':
161 if 'tvsh' in mp4meta:
162 metadata['episodeTitle'] = value
163 else:
164 metadata['title'] = value
166 # Description in desc, cmt, and/or ldes tags. Keep the longest.
167 elif key in ['desc', '\xa9cmt', 'ldes'] and len(value) > len_desc:
168 metadata['description'] = value
169 len_desc = len(value)
171 # A common custom "reverse DNS format" tag
172 elif (key == '----:com.apple.iTunes:iTunEXTC' and
173 ('us-tv' in value or 'mpaa' in value)):
174 rating = value.split("|")[1].upper()
175 if rating in TV_RATINGS and 'us-tv' in value:
176 metadata['tvRating'] = TV_RATINGS[rating]
177 elif rating in MPAA_RATINGS and 'mpaa' in value:
178 metadata['mpaaRating'] = MPAA_RATINGS[rating]
180 # Actors, directors, producers, AND screenwriters may be in a long
181 # embedded XML plist.
182 elif (key == '----:com.apple.iTunes:iTunMOVI' and
183 'plistlib' in sys.modules):
184 items = {'cast': 'vActor', 'directors': 'vDirector',
185 'producers': 'vProducer', 'screenwriters': 'vWriter'}
186 try:
187 data = plistlib.readPlistFromString(value)
188 except:
189 pass
190 else:
191 for item in items:
192 if item in data:
193 metadata[items[item]] = [x['name'] for x in data[item]]
195 mp4_cache[full_path] = metadata
196 return metadata
198 def from_mscore(rawmeta):
199 metadata = {}
200 keys = {'title': ['Title'],
201 'description': ['Description', 'WM/SubTitleDescription'],
202 'episodeTitle': ['WM/SubTitle'],
203 'callsign': ['WM/MediaStationCallSign'],
204 'displayMajorNumber': ['WM/MediaOriginalChannel'],
205 'originalAirDate': ['WM/MediaOriginalBroadcastDateTime'],
206 'rating': ['WM/ParentalRating'],
207 'credits': ['WM/MediaCredits'], 'genre': ['WM/Genre']}
209 for tagname in keys:
210 for tag in keys[tagname]:
211 try:
212 if tag in rawmeta:
213 value = rawmeta[tag][0]
214 if type(value) not in (str, unicode):
215 value = str(value)
216 if value:
217 metadata[tagname] = value
218 except:
219 pass
221 if 'episodeTitle' in metadata and 'title' in metadata:
222 metadata['seriesTitle'] = metadata['title']
223 if 'genre' in metadata:
224 value = metadata['genre'].split(',')
225 metadata['vProgramGenre'] = value
226 metadata['vSeriesGenre'] = value
227 del metadata['genre']
228 if 'credits' in metadata:
229 value = [x.split('/') for x in metadata['credits'].split(';')]
230 if len(value) > 3:
231 metadata['vActor'] = [x for x in (value[0] + value[3]) if x]
232 metadata['vDirector'] = [x for x in value[1] if x]
233 del metadata['credits']
234 if 'rating' in metadata:
235 rating = metadata['rating']
236 if rating in TV_RATINGS:
237 metadata['tvRating'] = TV_RATINGS[rating]
238 del metadata['rating']
240 return metadata
242 def from_dvrms(full_path):
243 if full_path in dvrms_cache:
244 return dvrms_cache[full_path]
246 try:
247 rawmeta = mutagen.File(unicode(full_path, 'utf-8'))
248 assert(rawmeta)
249 except:
250 dvrms_cache[full_path] = {}
251 return {}
253 metadata = from_mscore(rawmeta)
254 dvrms_cache[full_path] = metadata
255 return metadata
257 def from_eyetv(full_path):
258 keys = {'TITLE': 'title', 'SUBTITLE': 'episodeTitle',
259 'DESCRIPTION': 'description', 'YEAR': 'movieYear',
260 'EPISODENUM': 'episodeNumber'}
261 metadata = {}
262 path = os.path.dirname(unicode(full_path, 'utf-8'))
263 eyetvp = [x for x in os.listdir(path) if x.endswith('.eyetvp')][0]
264 eyetvp = os.path.join(path, eyetvp)
265 try:
266 eyetv = plistlib.readPlist(eyetvp)
267 except:
268 return metadata
269 if 'epg info' in eyetv:
270 info = eyetv['epg info']
271 for key in keys:
272 if info[key]:
273 metadata[keys[key]] = info[key]
274 if info['SUBTITLE']:
275 metadata['seriesTitle'] = info['TITLE']
276 if info['ACTORS']:
277 metadata['vActor'] = [x.strip() for x in info['ACTORS'].split(',')]
278 if info['DIRECTOR']:
279 metadata['vDirector'] = [info['DIRECTOR']]
281 for ptag, etag, ratings in [('tvRating', 'TV_RATING', TV_RATINGS),
282 ('mpaaRating', 'MPAA_RATING', MPAA_RATINGS),
283 ('starRating', 'STAR_RATING', STAR_RATINGS)]:
284 x = info[etag].upper()
285 if x and x in ratings:
286 metadata[ptag] = ratings[x]
288 # movieYear must be set for the mpaa/star ratings to work
289 if (('mpaaRating' in metadata or 'starRating' in metadata) and
290 'movieYear' not in metadata):
291 metadata['movieYear'] = eyetv['info']['start'].year
292 return metadata
294 def from_text(full_path):
295 metadata = {}
296 full_path = unicode(full_path, 'utf-8')
297 path, name = os.path.split(full_path)
298 title, ext = os.path.splitext(name)
300 search_paths = []
301 ptmp = full_path
302 while ptmp:
303 parent = os.path.dirname(ptmp)
304 if ptmp != parent:
305 ptmp = parent
306 else:
307 break
308 search_paths.append(os.path.join(ptmp, 'default.txt'))
310 search_paths.append(os.path.join(path, title) + '.properties')
311 search_paths.reverse()
313 search_paths += [full_path + '.txt',
314 os.path.join(path, '.meta', 'default.txt'),
315 os.path.join(path, '.meta', name) + '.txt']
317 for metafile in search_paths:
318 if os.path.exists(metafile):
319 sep = ':='[metafile.endswith('.properties')]
320 for line in file(metafile, 'U'):
321 if line.startswith(BOM):
322 line = line[3:]
323 if line.strip().startswith('#') or not sep in line:
324 continue
325 key, value = [x.strip() for x in line.split(sep, 1)]
326 if not key or not value:
327 continue
328 if key.startswith('v'):
329 if key in metadata:
330 metadata[key].append(value)
331 else:
332 metadata[key] = [value]
333 else:
334 metadata[key] = value
336 for rating, ratings in [('tvRating', TV_RATINGS),
337 ('mpaaRating', MPAA_RATINGS),
338 ('starRating', STAR_RATINGS)]:
339 x = metadata.get(rating, '').upper()
340 if x in ratings:
341 metadata[rating] = ratings[x]
342 else:
343 try:
344 x = int(x)
345 metadata[rating] = x
346 except:
347 pass
349 return metadata
351 def basic(full_path):
352 base_path, name = os.path.split(full_path)
353 title, ext = os.path.splitext(name)
354 mtime = os.stat(unicode(full_path, 'utf-8')).st_mtime
355 if (mtime < 0):
356 mtime = 0
357 originalAirDate = datetime.utcfromtimestamp(mtime)
359 metadata = {'title': title,
360 'originalAirDate': originalAirDate.isoformat()}
361 ext = ext.lower()
362 if ext in ['.mp4', '.m4v', '.mov']:
363 metadata.update(from_moov(full_path))
364 elif ext in ['.dvr-ms', '.asf', '.wmv']:
365 metadata.update(from_dvrms(full_path))
366 elif 'plistlib' in sys.modules and base_path.endswith('.eyetv'):
367 metadata.update(from_eyetv(full_path))
368 metadata.update(from_nfo(full_path))
369 metadata.update(from_text(full_path))
371 return metadata
373 def from_container(xmldoc):
374 metadata = {}
376 keys = {'title': 'Title', 'episodeTitle': 'EpisodeTitle',
377 'description': 'Description', 'programId': 'ProgramId',
378 'seriesId': 'SeriesId', 'episodeNumber': 'EpisodeNumber',
379 'tvRating': 'TvRating', 'displayMajorNumber': 'SourceChannel',
380 'callsign': 'SourceStation', 'showingBits': 'ShowingBits',
381 'mpaaRating': 'MpaaRating'}
383 details = xmldoc.getElementsByTagName('Details')[0]
385 for key in keys:
386 data = tag_data(details, keys[key])
387 if data:
388 if key == 'description':
389 data = data.replace(TRIBUNE_CR, '')
390 elif key == 'tvRating':
391 data = int(data)
392 elif key == 'displayMajorNumber':
393 if '-' in data:
394 data, metadata['displayMinorNumber'] = data.split('-')
395 metadata[key] = data
397 return metadata
399 def from_details(xml):
400 metadata = {}
402 xmldoc = minidom.parseString(xml)
403 showing = xmldoc.getElementsByTagName('showing')[0]
404 program = showing.getElementsByTagName('program')[0]
406 items = {'description': 'program/description',
407 'title': 'program/title',
408 'episodeTitle': 'program/episodeTitle',
409 'episodeNumber': 'program/episodeNumber',
410 'programId': 'program/uniqueId',
411 'seriesId': 'program/series/uniqueId',
412 'seriesTitle': 'program/series/seriesTitle',
413 'originalAirDate': 'program/originalAirDate',
414 'isEpisode': 'program/isEpisode',
415 'movieYear': 'program/movieYear',
416 'partCount': 'partCount',
417 'partIndex': 'partIndex',
418 'time': 'time'}
420 for item in items:
421 data = tag_data(showing, items[item])
422 if data:
423 if item == 'description':
424 data = data.replace(TRIBUNE_CR, '')
425 metadata[item] = data
427 vItems = ['vActor', 'vChoreographer', 'vDirector',
428 'vExecProducer', 'vProgramGenre', 'vGuestStar',
429 'vHost', 'vProducer', 'vWriter']
431 for item in vItems:
432 data = _vtag_data(program, item)
433 if data:
434 metadata[item] = data
436 sb = showing.getElementsByTagName('showingBits')
437 if sb:
438 metadata['showingBits'] = sb[0].attributes['value'].value
440 #for tag in ['starRating', 'mpaaRating', 'colorCode']:
441 for tag in ['starRating', 'mpaaRating']:
442 value = _tag_value(program, tag)
443 if value:
444 metadata[tag] = value
446 rating = _tag_value(showing, 'tvRating')
447 if rating:
448 metadata['tvRating'] = rating
450 return metadata
452 def _nfo_vitems(source, metadata):
454 vItems = {'vGenre': 'genre',
455 'vWriter': 'credits',
456 'vDirector': 'director',
457 'vActor': 'actor/name'}
459 for key in vItems:
460 data = _vtag_data_alternate(source, vItems[key])
461 if data:
462 metadata.setdefault(key, [])
463 for dat in data:
464 if not dat in metadata[key]:
465 metadata[key].append(dat)
467 if 'vGenre' in metadata:
468 metadata['vSeriesGenre'] = metadata['vProgramGenre'] = metadata['vGenre']
470 return metadata
472 def _parse_nfo(nfo_path, nfo_data=None):
473 # nfo files can contain XML or a URL to seed the XBMC metadata scrapers
474 # It's also possible to have both (a URL after the XML metadata)
475 # pyTivo only parses the XML metadata, but we'll try to stip the URL
476 # from mixed XML/URL files. Returns `None` when XML can't be parsed.
477 if nfo_data is None:
478 nfo_data = [line.strip() for line in file(nfo_path, 'rU')]
479 xmldoc = None
480 try:
481 xmldoc = minidom.parseString(os.linesep.join(nfo_data))
482 except expat.ExpatError, err:
483 if expat.ErrorString(err.code) == expat.errors.XML_ERROR_INVALID_TOKEN:
484 # might be a URL outside the xml
485 while len(nfo_data) > err.lineno:
486 if len(nfo_data[-1]) == 0:
487 nfo_data.pop()
488 else:
489 break
490 if len(nfo_data) == err.lineno:
491 # last non-blank line contains the error
492 nfo_data.pop()
493 return _parse_nfo(nfo_path, nfo_data)
494 return xmldoc
496 def _from_tvshow_nfo(tvshow_nfo_path):
497 if tvshow_nfo_path in nfo_cache:
498 return nfo_cache[tvshow_nfo_path]
500 items = {'description': 'plot',
501 'title': 'title',
502 'seriesTitle': 'showtitle',
503 'starRating': 'rating',
504 'tvRating': 'mpaa'}
506 nfo_cache[tvshow_nfo_path] = metadata = {}
508 xmldoc = _parse_nfo(tvshow_nfo_path)
509 if not xmldoc:
510 return metadata
512 tvshow = xmldoc.getElementsByTagName('tvshow')
513 if tvshow:
514 tvshow = tvshow[0]
515 else:
516 return metadata
518 for item in items:
519 data = tag_data(tvshow, items[item])
520 if data:
521 metadata[item] = data
523 metadata = _nfo_vitems(tvshow, metadata)
525 nfo_cache[tvshow_nfo_path] = metadata
526 return metadata
528 def _from_episode_nfo(nfo_path, xmldoc):
529 metadata = {}
531 items = {'description': 'plot',
532 'episodeTitle': 'title',
533 'seriesTitle': 'showtitle',
534 'originalAirDate': 'aired',
535 'starRating': 'rating',
536 'tvRating': 'mpaa'}
538 # find tvshow.nfo
539 path = nfo_path
540 while True:
541 basepath = os.path.dirname(path)
542 if path == basepath:
543 break
544 path = basepath
545 tv_nfo = os.path.join(path, 'tvshow.nfo')
546 if os.path.exists(tv_nfo):
547 metadata.update(_from_tvshow_nfo(tv_nfo))
548 break
550 episode = xmldoc.getElementsByTagName('episodedetails')
551 if episode:
552 episode = episode[0]
553 else:
554 return metadata
556 metadata['isEpisode'] = 'true'
557 for item in items:
558 data = tag_data(episode, items[item])
559 if data:
560 metadata[item] = data
562 season = tag_data(episode, 'displayseason')
563 if not season or season == "-1":
564 season = tag_data(episode, 'season')
565 if not season:
566 season = 1
568 ep_num = tag_data(episode, 'displayepisode')
569 if not ep_num or ep_num == "-1":
570 ep_num = tag_data(episode, 'episode')
571 if ep_num and ep_num != "-1":
572 metadata['episodeNumber'] = "%d%02d" % (int(season), int(ep_num))
574 if 'originalAirDate' in metadata:
575 metadata['originalAirDate'] += 'T00:00:00Z'
577 metadata = _nfo_vitems(episode, metadata)
579 return metadata
581 def _from_movie_nfo(xmldoc):
582 metadata = {}
584 movie = xmldoc.getElementsByTagName('movie')
585 if movie:
586 movie = movie[0]
587 else:
588 return metadata
590 items = {'description': 'plot',
591 'title': 'title',
592 'movieYear': 'year',
593 'starRating': 'rating',
594 'mpaaRating': 'mpaa'}
596 metadata['isEpisode'] = 'false'
598 for item in items:
599 data = tag_data(movie, items[item])
600 if data:
601 metadata[item] = data
603 metadata['movieYear'] = "%04d" % int(metadata.get('movieYear', 0))
605 metadata = _nfo_vitems(movie, metadata)
606 return metadata
608 def from_nfo(full_path):
609 if full_path in nfo_cache:
610 return nfo_cache[full_path]
612 metadata = nfo_cache[full_path] = {}
614 nfo_path = "%s.nfo" % os.path.splitext(full_path)[0]
615 if not os.path.exists(nfo_path):
616 return metadata
618 xmldoc = _parse_nfo(nfo_path)
619 if not xmldoc:
620 return metadata
622 if xmldoc.getElementsByTagName('episodedetails'):
623 # it's an episode
624 metadata.update(_from_episode_nfo(nfo_path, xmldoc))
625 elif xmldoc.getElementsByTagName('movie'):
626 # it's a movie
627 metadata.update(_from_movie_nfo(xmldoc))
629 # common nfo cleanup
630 if 'starRating' in metadata:
631 # .NFO 0-10 -> TiVo 1-7
632 rating = int(float(metadata['starRating']) * 6 / 10 + 1.5)
633 metadata['starRating'] = rating
635 for key, mapping in [('mpaaRating', MPAA_RATINGS),
636 ('tvRating', TV_RATINGS)]:
637 if key in metadata:
638 rating = mapping.get(metadata[key], None)
639 if rating:
640 metadata[key] = str(rating)
641 else:
642 del metadata[key]
644 nfo_cache[full_path] = metadata
645 return metadata
647 def _tdcat_bin(tdcat_path, full_path, tivo_mak):
648 fname = unicode(full_path, 'utf-8')
649 if mswindows:
650 fname = fname.encode('iso8859-1')
651 tcmd = [tdcat_path, '-m', tivo_mak, '-2', fname]
652 tdcat = subprocess.Popen(tcmd, stdout=subprocess.PIPE)
653 return tdcat.stdout.read()
655 def _tdcat_py(full_path, tivo_mak):
656 xml_data = {}
658 tfile = open(full_path, 'rb')
659 header = tfile.read(16)
660 offset, chunks = struct.unpack('>LH', header[10:])
661 rawdata = tfile.read(offset - 16)
662 tfile.close()
664 count = 0
665 for i in xrange(chunks):
666 chunk_size, data_size, id, enc = struct.unpack('>LLHH',
667 rawdata[count:count + 12])
668 count += 12
669 data = rawdata[count:count + data_size]
670 xml_data[id] = {'enc': enc, 'data': data, 'start': count + 16}
671 count += chunk_size - 12
673 chunk = xml_data[2]
674 details = chunk['data']
675 if chunk['enc']:
676 xml_key = xml_data[3]['data']
678 hexmak = hashlib.md5('tivo:TiVo DVR:' + tivo_mak).hexdigest()
679 key = hashlib.sha1(hexmak + xml_key).digest()[:16] + '\0\0\0\0'
681 turkey = hashlib.sha1(key[:17]).digest()
682 turiv = hashlib.sha1(key).digest()
684 xor_data = turing.Turing(turkey, turiv).gen(chunk['start'],
685 len(details))
686 fmt = '%dB' % len(details)
687 d2 = struct.unpack(fmt, details)
688 x2 = struct.unpack(fmt, xor_data)
689 details = struct.pack(fmt, *(a ^ b for a, b in izip(d2, x2)))
691 return details
693 def from_tivo(full_path):
694 if full_path in tivo_cache:
695 return tivo_cache[full_path]
697 tdcat_path = config.get_bin('tdcat')
698 tivo_mak = config.get_server('tivo_mak')
699 try:
700 assert(tivo_mak)
701 if tdcat_path:
702 details = _tdcat_bin(tdcat_path, full_path, tivo_mak)
703 else:
704 details = _tdcat_py(full_path, tivo_mak)
705 metadata = from_details(details)
706 tivo_cache[full_path] = metadata
707 except:
708 raise
709 metadata = {}
711 return metadata
713 def force_utf8(text):
714 if type(text) == str:
715 try:
716 text = text.decode('utf8')
717 except:
718 if sys.platform == 'darwin':
719 text = text.decode('macroman')
720 else:
721 text = text.decode('iso8859-1')
722 return text.encode('utf-8')
724 def dump(output, metadata):
725 for key in metadata:
726 value = metadata[key]
727 if type(value) == list:
728 for item in value:
729 output.write('%s: %s\n' % (key, item.encode('utf-8')))
730 else:
731 if key in HUMAN and value in HUMAN[key]:
732 output.write('%s: %s\n' % (key, HUMAN[key][value]))
733 else:
734 output.write('%s: %s\n' % (key, value.encode('utf-8')))
736 if __name__ == '__main__':
737 if len(sys.argv) > 1:
738 metadata = {}
739 config.init([])
740 logging.basicConfig()
741 fname = force_utf8(sys.argv[1])
742 ext = os.path.splitext(fname)[1].lower()
743 if ext == '.tivo':
744 metadata.update(from_tivo(fname))
745 elif ext in ['.mp4', '.m4v', '.mov']:
746 metadata.update(from_moov(fname))
747 elif ext in ['.dvr-ms', '.asf', '.wmv']:
748 metadata.update(from_dvrms(fname))
749 elif ext == '.wtv':
750 vInfo = plugins.video.transcode.video_info(fname)
751 metadata.update(from_mscore(vInfo['rawmeta']))
752 dump(sys.stdout, metadata)