Fix colorCode parsing from TXT
[pyTivo/wmcbrine.git] / metadata.py
bloba779a51c8a1b9f2b18ec8785c2b5b249d5b28a78
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 xml.dom import minidom
11 from xml.parsers import expat
12 try:
13 import plistlib
14 except:
15 pass
17 import mutagen
18 from lrucache import LRUCache
20 import config
21 import plugins.video.transcode
22 import turing
24 # Something to strip
25 TRIBUNE_CR = ' Copyright Tribune Media Services, Inc.'
27 TV_RATINGS = {'TV-Y7': 1, 'TV-Y': 2, 'TV-G': 3, 'TV-PG': 4, 'TV-14': 5,
28 'TV-MA': 6, 'TV-NR': 7, 'TVY7': 1, 'TVY': 2, 'TVG': 3,
29 'TVPG': 4, 'TV14': 5, 'TVMA': 6, 'TVNR': 7, 'Y7': 1,
30 'Y': 2, 'G': 3, 'PG': 4, '14': 5, 'MA': 6, 'NR': 7,
31 'UNRATED': 7, 'X1': 1, 'X2': 2, 'X3': 3, 'X4': 4, 'X5': 5,
32 'X6': 6, 'X7': 7}
34 MPAA_RATINGS = {'G': 1, 'PG': 2, 'PG-13': 3, 'PG13': 3, 'R': 4, 'X': 5,
35 'NC-17': 6, 'NC17': 6, 'NR': 8, 'UNRATED': 8, 'G1': 1,
36 'P2': 2, 'P3': 3, 'R4': 4, 'X5': 5, 'N6': 6, 'N8': 8}
38 STAR_RATINGS = {'1': 1, '1.5': 2, '2': 3, '2.5': 4, '3': 5, '3.5': 6,
39 '4': 7, '*': 1, '**': 3, '***': 5, '****': 7, 'X1': 1,
40 'X2': 2, 'X3': 3, 'X4': 4, 'X5': 5, 'X6': 6, 'X7': 7}
42 HUMAN = {'mpaaRating': {1: 'G', 2: 'PG', 3: 'PG-13', 4: 'R', 5: 'X',
43 6: 'NC-17', 8: 'NR'},
44 'tvRating': {1: 'Y7', 2: 'Y', 3: 'G', 4: 'PG', 5: '14',
45 6: 'MA', 7: 'NR'},
46 'starRating': {1: '1', 2: '1.5', 3: '2', 4: '2.5', 5: '3',
47 6: '3.5', 7: '4'}}
49 COLOR_CODES = {'1': ["B & W", 1],
50 '2': ["COLOR AND B & W", 2],
51 '3': ["COLORIZED", 3],
52 '4': ["COLOR", 4]}
54 BOM = '\xef\xbb\xbf'
56 GB = 1024 ** 3
57 MB = 1024 ** 2
58 KB = 1024
60 tivo_cache = LRUCache(50)
61 mp4_cache = LRUCache(50)
62 dvrms_cache = LRUCache(50)
63 nfo_cache = LRUCache(50)
65 mswindows = (sys.platform == "win32")
67 def get_mpaa(rating):
68 return HUMAN['mpaaRating'].get(rating, 'NR')
70 def get_tv(rating):
71 return HUMAN['tvRating'].get(rating, 'NR')
73 def get_stars(rating):
74 return HUMAN['starRating'].get(rating, '')
76 def human_size(raw):
77 raw = float(raw)
78 if raw > GB:
79 tsize = '%.2f GB' % (raw / GB)
80 elif raw > MB:
81 tsize = '%.2f MB' % (raw / MB)
82 elif raw > KB:
83 tsize = '%.2f KB' % (raw / KB)
84 else:
85 tsize = '%d Bytes' % raw
86 return tsize
88 def tag_data(element, tag):
89 for name in tag.split('/'):
90 found = False
91 for new_element in element.childNodes:
92 if new_element.nodeName == name:
93 found = True
94 element = new_element
95 break
96 if not found:
97 return ''
98 if not element.firstChild:
99 return ''
100 return element.firstChild.data
102 def _vtag_data(element, tag):
103 for name in tag.split('/'):
104 new_element = element.getElementsByTagName(name)
105 if not new_element:
106 return []
107 element = new_element[0]
108 elements = element.getElementsByTagName('element')
109 return [x.firstChild.data for x in elements if x.firstChild]
111 def _vtag_data_alternate(element, tag):
112 elements = [element]
113 for name in tag.split('/'):
114 new_elements = []
115 for elmt in elements:
116 new_elements += elmt.getElementsByTagName(name)
117 elements = new_elements
118 return [x.firstChild.data for x in elements if x.firstChild]
120 def _tag_value(element, tag):
121 item = element.getElementsByTagName(tag)
122 if item:
123 value = item[0].attributes['value'].value
124 return int(value[0])
126 def from_moov(full_path):
127 if full_path in mp4_cache:
128 return mp4_cache[full_path]
130 metadata = {}
131 len_desc = 0
133 try:
134 mp4meta = mutagen.File(unicode(full_path, 'utf-8'))
135 assert(mp4meta)
136 except:
137 mp4_cache[full_path] = {}
138 return {}
140 # The following 1-to-1 correspondence of atoms to pyTivo
141 # variables is TV-biased
142 keys = {'tvnn': 'callsign',
143 'tvsh': 'seriesTitle'}
144 isTVShow = False
145 if 'stik' in mp4meta:
146 isTVShow = (mp4meta['stik'] == mutagen.mp4.MediaKind.TV_SHOW)
147 else:
148 isTVShow = 'tvsh' in mp4meta
149 for key, value in mp4meta.items():
150 if type(value) == list:
151 value = value[0]
152 if key in keys:
153 metadata[keys[key]] = value
154 elif key == 'tven':
155 #could be programId (EP, SH, or MV) or "SnEn"
156 if value.startswith('SH'):
157 metadata['isEpisode'] = 'false'
158 elif value.startswith('MV') or value.startswith('EP'):
159 metadata['isEpisode'] = 'true'
160 metadata['programId'] = value
161 elif key.startswith('S') and key.count('E') == 1:
162 epstart = key.find('E')
163 seasonstr = key[1:epstart]
164 episodestr = key[epstart+1:]
165 if (seasonstr.isdigit() and episodestr.isdigit()):
166 if len(episodestr) < 2:
167 episodestr = '0' + episodestr
168 metadata['episodeNumber'] = seasonstr+episodestr
169 elif key == 'tvsn':
170 #put together tvsn and tves to make episodeNumber
171 tvsn = str(value)
172 tves = '00'
173 if 'tves' in mp4meta:
174 tvesValue = mp4meta['tves']
175 if type(tvesValue) == list:
176 tvesValue = tvesValue[0]
177 tves = str(tvesValue)
178 if len(tves) < 2:
179 tves = '0' + tves
180 metadata['episodeNumber'] = tvsn+tves
181 # These keys begin with the copyright symbol \xA9
182 elif key == '\xa9day':
183 if isTVShow :
184 if len(value) == 4:
185 value += '-01-01T16:00:00Z'
186 metadata['originalAirDate'] = value
187 else:
188 if len(value) >= 4:
189 metadata['movieYear'] = value[:4]
190 #metadata['time'] = value
191 elif key in ['\xa9gen', 'gnre']:
192 for k in ('vProgramGenre', 'vSeriesGenre'):
193 if k in metadata:
194 metadata[k].append(value)
195 else:
196 metadata[k] = [value]
197 elif key == '\xa9nam':
198 if isTVShow:
199 metadata['episodeTitle'] = value
200 else:
201 metadata['title'] = value
203 # Description in desc, cmt, and/or ldes tags. Keep the longest.
204 elif key in ['desc', '\xa9cmt', 'ldes'] and len(value) > len_desc:
205 metadata['description'] = value
206 len_desc = len(value)
208 # A common custom "reverse DNS format" tag
209 elif (key == '----:com.apple.iTunes:iTunEXTC' and
210 ('us-tv' in value or 'mpaa' in value)):
211 rating = value.split("|")[1].upper()
212 if rating in TV_RATINGS and 'us-tv' in value:
213 metadata['tvRating'] = TV_RATINGS[rating]
214 elif rating in MPAA_RATINGS and 'mpaa' in value:
215 metadata['mpaaRating'] = MPAA_RATINGS[rating]
217 # Actors, directors, producers, AND screenwriters may be in a long
218 # embedded XML plist.
219 elif (key == '----:com.apple.iTunes:iTunMOVI' and
220 'plistlib' in sys.modules):
221 items = {'cast': 'vActor', 'directors': 'vDirector',
222 'producers': 'vProducer', 'screenwriters': 'vWriter'}
223 try:
224 data = plistlib.readPlistFromString(value)
225 except:
226 pass
227 else:
228 for item in items:
229 if item in data:
230 metadata[items[item]] = [x['name'] for x in data[item]]
232 mp4_cache[full_path] = metadata
233 return metadata
235 def from_mscore(rawmeta):
236 metadata = {}
237 keys = {'title': ['Title'],
238 'description': ['Description', 'WM/SubTitleDescription'],
239 'episodeTitle': ['WM/SubTitle'],
240 'callsign': ['WM/MediaStationCallSign'],
241 'displayMajorNumber': ['WM/MediaOriginalChannel'],
242 'originalAirDate': ['WM/MediaOriginalBroadcastDateTime'],
243 'rating': ['WM/ParentalRating'],
244 'credits': ['WM/MediaCredits'], 'genre': ['WM/Genre']}
246 for tagname in keys:
247 for tag in keys[tagname]:
248 try:
249 if tag in rawmeta:
250 value = rawmeta[tag][0]
251 if type(value) not in (str, unicode):
252 value = str(value)
253 if value:
254 metadata[tagname] = value
255 except:
256 pass
258 if 'episodeTitle' in metadata and 'title' in metadata:
259 metadata['seriesTitle'] = metadata['title']
260 if 'genre' in metadata:
261 value = metadata['genre'].split(',')
262 metadata['vProgramGenre'] = value
263 metadata['vSeriesGenre'] = value
264 del metadata['genre']
265 if 'credits' in metadata:
266 value = [x.split('/') for x in metadata['credits'].split(';')]
267 if len(value) > 3:
268 metadata['vActor'] = [x for x in (value[0] + value[3]) if x]
269 metadata['vDirector'] = [x for x in value[1] if x]
270 del metadata['credits']
271 if 'rating' in metadata:
272 rating = metadata['rating']
273 if rating in TV_RATINGS:
274 metadata['tvRating'] = TV_RATINGS[rating]
275 del metadata['rating']
277 return metadata
279 def from_dvrms(full_path):
280 if full_path in dvrms_cache:
281 return dvrms_cache[full_path]
283 try:
284 rawmeta = mutagen.File(unicode(full_path, 'utf-8'))
285 assert(rawmeta)
286 except:
287 dvrms_cache[full_path] = {}
288 return {}
290 metadata = from_mscore(rawmeta)
291 dvrms_cache[full_path] = metadata
292 return metadata
294 def from_eyetv(full_path):
295 keys = {'TITLE': 'title', 'SUBTITLE': 'episodeTitle',
296 'DESCRIPTION': 'description', 'YEAR': 'movieYear',
297 'EPISODENUM': 'episodeNumber'}
298 metadata = {}
299 path = os.path.dirname(unicode(full_path, 'utf-8'))
300 eyetvp = [x for x in os.listdir(path) if x.endswith('.eyetvp')][0]
301 eyetvp = os.path.join(path, eyetvp)
302 try:
303 eyetv = plistlib.readPlist(eyetvp)
304 except:
305 return metadata
306 if 'epg info' in eyetv:
307 info = eyetv['epg info']
308 for key in keys:
309 if info[key]:
310 metadata[keys[key]] = info[key]
311 if info['SUBTITLE']:
312 metadata['seriesTitle'] = info['TITLE']
313 if info['ACTORS']:
314 metadata['vActor'] = [x.strip() for x in info['ACTORS'].split(',')]
315 if info['DIRECTOR']:
316 metadata['vDirector'] = [info['DIRECTOR']]
318 for ptag, etag, ratings in [('tvRating', 'TV_RATING', TV_RATINGS),
319 ('mpaaRating', 'MPAA_RATING', MPAA_RATINGS),
320 ('starRating', 'STAR_RATING', STAR_RATINGS)]:
321 x = info[etag].upper()
322 if x and x in ratings:
323 metadata[ptag] = ratings[x]
325 # movieYear must be set for the mpaa/star ratings to work
326 if (('mpaaRating' in metadata or 'starRating' in metadata) and
327 'movieYear' not in metadata):
328 metadata['movieYear'] = eyetv['info']['start'].year
329 return metadata
331 def from_text(full_path):
332 metadata = {}
333 full_path = unicode(full_path, 'utf-8')
334 path, name = os.path.split(full_path)
335 title, ext = os.path.splitext(name)
337 search_paths = []
338 ptmp = full_path
339 while ptmp:
340 parent = os.path.dirname(ptmp)
341 if ptmp != parent:
342 ptmp = parent
343 else:
344 break
345 search_paths.append(os.path.join(ptmp, 'default.txt'))
347 search_paths.append(os.path.join(path, title) + '.properties')
348 search_paths.reverse()
350 search_paths += [full_path + '.txt',
351 os.path.join(path, '.meta', 'default.txt'),
352 os.path.join(path, '.meta', name) + '.txt']
354 for metafile in search_paths:
355 if os.path.exists(metafile):
356 sep = ':='[metafile.endswith('.properties')]
357 for line in file(metafile, 'U'):
358 if line.startswith(BOM):
359 line = line[3:]
360 if line.strip().startswith('#') or not sep in line:
361 continue
362 key, value = [x.strip() for x in line.split(sep, 1)]
363 if not key or not value:
364 continue
365 if key.startswith('v'):
366 if key in metadata:
367 metadata[key].append(value)
368 else:
369 metadata[key] = [value]
370 else:
371 metadata[key] = value
373 for rating, ratings in [('tvRating', TV_RATINGS),
374 ('mpaaRating', MPAA_RATINGS),
375 ('starRating', STAR_RATINGS),
376 ('colorCode', COLOR_CODES)]:
377 x = metadata.get(rating, '').upper()
378 if x in ratings:
379 metadata[rating] = ratings[x]
380 else:
381 try:
382 x = int(x)
383 metadata[rating] = x
384 except:
385 pass
387 return metadata
389 def basic(full_path, mtime=None):
390 base_path, name = os.path.split(full_path)
391 title, ext = os.path.splitext(name)
392 if not mtime:
393 mtime = os.path.getmtime(unicode(full_path, 'utf-8'))
394 try:
395 originalAirDate = datetime.utcfromtimestamp(mtime)
396 except:
397 originalAirDate = datetime.utcnow()
399 metadata = {'title': title,
400 'originalAirDate': originalAirDate.isoformat()}
401 ext = ext.lower()
402 if ext in ['.mp4', '.m4v', '.mov']:
403 metadata.update(from_moov(full_path))
404 elif ext in ['.dvr-ms', '.asf', '.wmv']:
405 metadata.update(from_dvrms(full_path))
406 elif 'plistlib' in sys.modules and base_path.endswith('.eyetv'):
407 metadata.update(from_eyetv(full_path))
408 metadata.update(from_nfo(full_path))
409 metadata.update(from_text(full_path))
411 return metadata
413 def from_container(xmldoc):
414 metadata = {}
416 keys = {'title': 'Title', 'episodeTitle': 'EpisodeTitle',
417 'description': 'Description', 'programId': 'ProgramId',
418 'seriesId': 'SeriesId', 'episodeNumber': 'EpisodeNumber',
419 'tvRating': 'TvRating', 'displayMajorNumber': 'SourceChannel',
420 'callsign': 'SourceStation', 'showingBits': 'ShowingBits',
421 'mpaaRating': 'MpaaRating'}
423 details = xmldoc.getElementsByTagName('Details')[0]
425 for key in keys:
426 data = tag_data(details, keys[key])
427 if data:
428 if key == 'description':
429 data = data.replace(TRIBUNE_CR, '')
430 elif key == 'tvRating':
431 data = int(data)
432 elif key == 'displayMajorNumber':
433 if '-' in data:
434 data, metadata['displayMinorNumber'] = data.split('-')
435 metadata[key] = data
437 return metadata
439 def from_details(xml):
440 metadata = {}
442 xmldoc = minidom.parseString(xml)
443 showing = xmldoc.getElementsByTagName('showing')[0]
444 program = showing.getElementsByTagName('program')[0]
446 items = {'description': 'program/description',
447 'title': 'program/title',
448 'episodeTitle': 'program/episodeTitle',
449 'episodeNumber': 'program/episodeNumber',
450 'programId': 'program/uniqueId',
451 'seriesId': 'program/series/uniqueId',
452 'seriesTitle': 'program/series/seriesTitle',
453 'originalAirDate': 'program/originalAirDate',
454 'isEpisode': 'program/isEpisode',
455 'movieYear': 'program/movieYear',
456 'partCount': 'partCount',
457 'partIndex': 'partIndex',
458 'time': 'time'}
460 for item in items:
461 data = tag_data(showing, items[item])
462 if data:
463 if item == 'description':
464 data = data.replace(TRIBUNE_CR, '')
465 metadata[item] = data
467 vItems = ['vActor', 'vChoreographer', 'vDirector',
468 'vExecProducer', 'vProgramGenre', 'vGuestStar',
469 'vHost', 'vProducer', 'vWriter']
471 for item in vItems:
472 data = _vtag_data(program, item)
473 if data:
474 metadata[item] = data
476 sb = showing.getElementsByTagName('showingBits')
477 if sb:
478 metadata['showingBits'] = sb[0].attributes['value'].value
480 #for tag in ['starRating', 'mpaaRating', 'colorCode']:
481 for tag in ['starRating', 'mpaaRating']:
482 value = _tag_value(program, tag)
483 if value:
484 metadata[tag] = value
486 rating = _tag_value(showing, 'tvRating')
487 if rating:
488 metadata['tvRating'] = rating
490 return metadata
492 def _nfo_vitems(source, metadata):
494 vItems = {'vGenre': 'genre',
495 'vWriter': 'credits',
496 'vDirector': 'director',
497 'vActor': 'actor/name'}
499 for key in vItems:
500 data = _vtag_data_alternate(source, vItems[key])
501 if data:
502 metadata.setdefault(key, [])
503 for dat in data:
504 if not dat in metadata[key]:
505 metadata[key].append(dat)
507 if 'vGenre' in metadata:
508 metadata['vSeriesGenre'] = metadata['vProgramGenre'] = metadata['vGenre']
510 return metadata
512 def _parse_nfo(nfo_path, nfo_data=None):
513 # nfo files can contain XML or a URL to seed the XBMC metadata scrapers
514 # It's also possible to have both (a URL after the XML metadata)
515 # pyTivo only parses the XML metadata, but we'll try to stip the URL
516 # from mixed XML/URL files. Returns `None` when XML can't be parsed.
517 if nfo_data is None:
518 nfo_data = [line.strip() for line in file(nfo_path, 'rU')]
519 xmldoc = None
520 try:
521 xmldoc = minidom.parseString(os.linesep.join(nfo_data))
522 except expat.ExpatError, err:
523 if expat.ErrorString(err.code) == expat.errors.XML_ERROR_INVALID_TOKEN:
524 # might be a URL outside the xml
525 while len(nfo_data) > err.lineno:
526 if len(nfo_data[-1]) == 0:
527 nfo_data.pop()
528 else:
529 break
530 if len(nfo_data) == err.lineno:
531 # last non-blank line contains the error
532 nfo_data.pop()
533 return _parse_nfo(nfo_path, nfo_data)
534 return xmldoc
536 def _from_tvshow_nfo(tvshow_nfo_path):
537 if tvshow_nfo_path in nfo_cache:
538 return nfo_cache[tvshow_nfo_path]
540 items = {'description': 'plot',
541 'title': 'title',
542 'seriesTitle': 'showtitle',
543 'starRating': 'rating',
544 'tvRating': 'mpaa'}
546 nfo_cache[tvshow_nfo_path] = metadata = {}
548 xmldoc = _parse_nfo(tvshow_nfo_path)
549 if not xmldoc:
550 return metadata
552 tvshow = xmldoc.getElementsByTagName('tvshow')
553 if tvshow:
554 tvshow = tvshow[0]
555 else:
556 return metadata
558 for item in items:
559 data = tag_data(tvshow, items[item])
560 if data:
561 metadata[item] = data
563 metadata = _nfo_vitems(tvshow, metadata)
565 nfo_cache[tvshow_nfo_path] = metadata
566 return metadata
568 def _from_episode_nfo(nfo_path, xmldoc):
569 metadata = {}
571 items = {'description': 'plot',
572 'episodeTitle': 'title',
573 'seriesTitle': 'showtitle',
574 'originalAirDate': 'aired',
575 'starRating': 'rating',
576 'tvRating': 'mpaa'}
578 # find tvshow.nfo
579 path = nfo_path
580 while True:
581 basepath = os.path.dirname(path)
582 if path == basepath:
583 break
584 path = basepath
585 tv_nfo = os.path.join(path, 'tvshow.nfo')
586 if os.path.exists(tv_nfo):
587 metadata.update(_from_tvshow_nfo(tv_nfo))
588 break
590 episode = xmldoc.getElementsByTagName('episodedetails')
591 if episode:
592 episode = episode[0]
593 else:
594 return metadata
596 metadata['isEpisode'] = 'true'
597 for item in items:
598 data = tag_data(episode, items[item])
599 if data:
600 metadata[item] = data
602 season = tag_data(episode, 'displayseason')
603 if not season or season == "-1":
604 season = tag_data(episode, 'season')
605 if not season:
606 season = 1
608 ep_num = tag_data(episode, 'displayepisode')
609 if not ep_num or ep_num == "-1":
610 ep_num = tag_data(episode, 'episode')
611 if ep_num and ep_num != "-1":
612 metadata['episodeNumber'] = "%d%02d" % (int(season), int(ep_num))
614 if 'originalAirDate' in metadata:
615 metadata['originalAirDate'] += 'T00:00:00Z'
617 metadata = _nfo_vitems(episode, metadata)
619 return metadata
621 def _from_movie_nfo(xmldoc):
622 metadata = {}
624 movie = xmldoc.getElementsByTagName('movie')
625 if movie:
626 movie = movie[0]
627 else:
628 return metadata
630 items = {'description': 'plot',
631 'title': 'title',
632 'movieYear': 'year',
633 'starRating': 'rating',
634 'mpaaRating': 'mpaa'}
636 metadata['isEpisode'] = 'false'
638 for item in items:
639 data = tag_data(movie, items[item])
640 if data:
641 metadata[item] = data
643 metadata['movieYear'] = "%04d" % int(metadata.get('movieYear', 0))
645 metadata = _nfo_vitems(movie, metadata)
646 return metadata
648 def from_nfo(full_path):
649 if full_path in nfo_cache:
650 return nfo_cache[full_path]
652 metadata = nfo_cache[full_path] = {}
654 nfo_path = "%s.nfo" % os.path.splitext(full_path)[0]
655 if not os.path.exists(nfo_path):
656 return metadata
658 xmldoc = _parse_nfo(nfo_path)
659 if not xmldoc:
660 return metadata
662 if xmldoc.getElementsByTagName('episodedetails'):
663 # it's an episode
664 metadata.update(_from_episode_nfo(nfo_path, xmldoc))
665 elif xmldoc.getElementsByTagName('movie'):
666 # it's a movie
667 metadata.update(_from_movie_nfo(xmldoc))
669 # common nfo cleanup
670 if 'starRating' in metadata:
671 # .NFO 0-10 -> TiVo 1-7
672 rating = int(float(metadata['starRating']) * 6 / 10 + 1.5)
673 metadata['starRating'] = rating
675 for key, mapping in [('mpaaRating', MPAA_RATINGS),
676 ('tvRating', TV_RATINGS)]:
677 if key in metadata:
678 rating = mapping.get(metadata[key], None)
679 if rating:
680 metadata[key] = rating
681 else:
682 del metadata[key]
684 nfo_cache[full_path] = metadata
685 return metadata
687 def _tdcat_bin(tdcat_path, full_path, tivo_mak):
688 fname = unicode(full_path, 'utf-8')
689 if mswindows:
690 fname = fname.encode('cp1252')
691 tcmd = [tdcat_path, '-m', tivo_mak, '-2', fname]
692 tdcat = subprocess.Popen(tcmd, stdout=subprocess.PIPE)
693 return tdcat.stdout.read()
695 def _tdcat_py(full_path, tivo_mak):
696 xml_data = {}
698 tfile = open(full_path, 'rb')
699 header = tfile.read(16)
700 offset, chunks = struct.unpack('>LH', header[10:])
701 rawdata = tfile.read(offset - 16)
702 tfile.close()
704 count = 0
705 for i in xrange(chunks):
706 chunk_size, data_size, id, enc = struct.unpack('>LLHH',
707 rawdata[count:count + 12])
708 count += 12
709 data = rawdata[count:count + data_size]
710 xml_data[id] = {'enc': enc, 'data': data, 'start': count + 16}
711 count += chunk_size - 12
713 chunk = xml_data[2]
714 details = chunk['data']
715 if chunk['enc']:
716 xml_key = xml_data[3]['data']
718 hexmak = hashlib.md5('tivo:TiVo DVR:' + tivo_mak).hexdigest()
719 key = hashlib.sha1(hexmak + xml_key).digest()[:16] + '\0\0\0\0'
721 turkey = hashlib.sha1(key[:17]).digest()
722 turiv = hashlib.sha1(key).digest()
724 details = turing.Turing(turkey, turiv).crypt(details, chunk['start'])
726 return details
728 def from_tivo(full_path):
729 if full_path in tivo_cache:
730 return tivo_cache[full_path]
732 tdcat_path = config.get_bin('tdcat')
733 tivo_mak = config.get_server('tivo_mak')
734 try:
735 assert(tivo_mak)
736 if tdcat_path:
737 details = _tdcat_bin(tdcat_path, full_path, tivo_mak)
738 else:
739 details = _tdcat_py(full_path, tivo_mak)
740 metadata = from_details(details)
741 tivo_cache[full_path] = metadata
742 except:
743 metadata = {}
745 return metadata
747 def force_utf8(text):
748 if type(text) == str:
749 try:
750 text = text.decode('utf8')
751 except:
752 if sys.platform == 'darwin':
753 text = text.decode('macroman')
754 else:
755 text = text.decode('cp1252')
756 return text.encode('utf-8')
758 def dump(output, metadata):
759 for key in metadata:
760 value = metadata[key]
761 if type(value) == list:
762 for item in value:
763 output.write('%s : %s\n' % (key, item.encode('utf-8')))
764 else:
765 if key in HUMAN and value in HUMAN[key]:
766 output.write('%s : %s\n' % (key, HUMAN[key][value]))
767 else:
768 output.write('%s : %s\n' % (key, value.encode('utf-8')))
770 if __name__ == '__main__':
771 if len(sys.argv) > 1:
772 metadata = {}
773 config.init([])
774 logging.basicConfig()
775 fname = force_utf8(sys.argv[1])
776 ext = os.path.splitext(fname)[1].lower()
777 if ext == '.tivo':
778 metadata.update(from_tivo(fname))
779 elif ext in ['.mp4', '.m4v', '.mov']:
780 metadata.update(from_moov(fname))
781 elif ext in ['.dvr-ms', '.asf', '.wmv']:
782 metadata.update(from_dvrms(fname))
783 elif ext == '.wtv':
784 vInfo = plugins.video.transcode.video_info(fname)
785 metadata.update(from_mscore(vInfo['rawmeta']))
786 dump(sys.stdout, metadata)