Allow for pyTivo-style URLs; more Unicode in ToGo.
[pyTivo/wmcbrine.git] / metadata.py
blob84e025fb50fab89a3d1280b1a3676e329eec370e
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 BOM = '\xef\xbb\xbf'
51 GB = 1024 ** 3
52 MB = 1024 ** 2
53 KB = 1024
55 tivo_cache = LRUCache(50)
56 mp4_cache = LRUCache(50)
57 dvrms_cache = LRUCache(50)
58 nfo_cache = LRUCache(50)
60 mswindows = (sys.platform == "win32")
62 def get_mpaa(rating):
63 return HUMAN['mpaaRating'].get(rating, 'NR')
65 def get_tv(rating):
66 return HUMAN['tvRating'].get(rating, 'NR')
68 def get_stars(rating):
69 return HUMAN['starRating'].get(rating, '')
71 def human_size(raw):
72 raw = float(raw)
73 if raw > GB:
74 tsize = '%.2f GB' % (raw / GB)
75 elif raw > MB:
76 tsize = '%.2f MB' % (raw / MB)
77 elif raw > KB:
78 tsize = '%.2f KB' % (raw / KB)
79 else:
80 tsize = '%d Bytes' % raw
81 return tsize
83 def tag_data(element, tag):
84 for name in tag.split('/'):
85 found = False
86 for new_element in element.childNodes:
87 if new_element.nodeName == name:
88 found = True
89 element = new_element
90 break
91 if not found:
92 return ''
93 if not element.firstChild:
94 return ''
95 return element.firstChild.data
97 def _vtag_data(element, tag):
98 for name in tag.split('/'):
99 new_element = element.getElementsByTagName(name)
100 if not new_element:
101 return []
102 element = new_element[0]
103 elements = element.getElementsByTagName('element')
104 return [x.firstChild.data for x in elements if x.firstChild]
106 def _vtag_data_alternate(element, tag):
107 elements = [element]
108 for name in tag.split('/'):
109 new_elements = []
110 for elmt in elements:
111 new_elements += elmt.getElementsByTagName(name)
112 elements = new_elements
113 return [x.firstChild.data for x in elements if x.firstChild]
115 def _tag_value(element, tag):
116 item = element.getElementsByTagName(tag)
117 if item:
118 value = item[0].attributes['value'].value
119 return int(value[0])
121 def from_moov(full_path):
122 if full_path in mp4_cache:
123 return mp4_cache[full_path]
125 metadata = {}
126 len_desc = 0
128 try:
129 mp4meta = mutagen.File(unicode(full_path, 'utf-8'))
130 assert(mp4meta)
131 except:
132 mp4_cache[full_path] = {}
133 return {}
135 # The following 1-to-1 correspondence of atoms to pyTivo
136 # variables is TV-biased
137 keys = {'tvnn': 'callsign', 'tven': 'episodeNumber',
138 'tvsh': 'seriesTitle'}
140 for key, value in mp4meta.items():
141 if type(value) == list:
142 value = value[0]
143 if key == 'stik':
144 metadata['isEpisode'] = ['false', 'true'][value == 'TV Show']
145 elif key in keys:
146 metadata[keys[key]] = value
147 # These keys begin with the copyright symbol \xA9
148 elif key == '\xa9day':
149 if len(value) == 4:
150 value += '-01-01T16:00:00Z'
151 metadata['originalAirDate'] = value
152 #metadata['time'] = value
153 elif key in ['\xa9gen', 'gnre']:
154 for k in ('vProgramGenre', 'vSeriesGenre'):
155 if k in metadata:
156 metadata[k].append(value)
157 else:
158 metadata[k] = [value]
159 elif key == '\xa9nam':
160 if 'tvsh' in mp4meta:
161 metadata['episodeTitle'] = value
162 else:
163 metadata['title'] = value
165 # Description in desc, cmt, and/or ldes tags. Keep the longest.
166 elif key in ['desc', '\xa9cmt', 'ldes'] and len(value) > len_desc:
167 metadata['description'] = value
168 len_desc = len(value)
170 # A common custom "reverse DNS format" tag
171 elif (key == '----:com.apple.iTunes:iTunEXTC' and
172 ('us-tv' in value or 'mpaa' in value)):
173 rating = value.split("|")[1].upper()
174 if rating in TV_RATINGS and 'us-tv' in value:
175 metadata['tvRating'] = TV_RATINGS[rating]
176 elif rating in MPAA_RATINGS and 'mpaa' in value:
177 metadata['mpaaRating'] = MPAA_RATINGS[rating]
179 # Actors, directors, producers, AND screenwriters may be in a long
180 # embedded XML plist.
181 elif (key == '----:com.apple.iTunes:iTunMOVI' and
182 'plistlib' in sys.modules):
183 items = {'cast': 'vActor', 'directors': 'vDirector',
184 'producers': 'vProducer', 'screenwriters': 'vWriter'}
185 try:
186 data = plistlib.readPlistFromString(value)
187 except:
188 pass
189 else:
190 for item in items:
191 if item in data:
192 metadata[items[item]] = [x['name'] for x in data[item]]
194 mp4_cache[full_path] = metadata
195 return metadata
197 def from_mscore(rawmeta):
198 metadata = {}
199 keys = {'title': ['Title'],
200 'description': ['Description', 'WM/SubTitleDescription'],
201 'episodeTitle': ['WM/SubTitle'],
202 'callsign': ['WM/MediaStationCallSign'],
203 'displayMajorNumber': ['WM/MediaOriginalChannel'],
204 'originalAirDate': ['WM/MediaOriginalBroadcastDateTime'],
205 'rating': ['WM/ParentalRating'],
206 'credits': ['WM/MediaCredits'], 'genre': ['WM/Genre']}
208 for tagname in keys:
209 for tag in keys[tagname]:
210 try:
211 if tag in rawmeta:
212 value = rawmeta[tag][0]
213 if type(value) not in (str, unicode):
214 value = str(value)
215 if value:
216 metadata[tagname] = value
217 except:
218 pass
220 if 'episodeTitle' in metadata and 'title' in metadata:
221 metadata['seriesTitle'] = metadata['title']
222 if 'genre' in metadata:
223 value = metadata['genre'].split(',')
224 metadata['vProgramGenre'] = value
225 metadata['vSeriesGenre'] = value
226 del metadata['genre']
227 if 'credits' in metadata:
228 value = [x.split('/') for x in metadata['credits'].split(';')]
229 if len(value) > 3:
230 metadata['vActor'] = [x for x in (value[0] + value[3]) if x]
231 metadata['vDirector'] = [x for x in value[1] if x]
232 del metadata['credits']
233 if 'rating' in metadata:
234 rating = metadata['rating']
235 if rating in TV_RATINGS:
236 metadata['tvRating'] = TV_RATINGS[rating]
237 del metadata['rating']
239 return metadata
241 def from_dvrms(full_path):
242 if full_path in dvrms_cache:
243 return dvrms_cache[full_path]
245 try:
246 rawmeta = mutagen.File(unicode(full_path, 'utf-8'))
247 assert(rawmeta)
248 except:
249 dvrms_cache[full_path] = {}
250 return {}
252 metadata = from_mscore(rawmeta)
253 dvrms_cache[full_path] = metadata
254 return metadata
256 def from_eyetv(full_path):
257 keys = {'TITLE': 'title', 'SUBTITLE': 'episodeTitle',
258 'DESCRIPTION': 'description', 'YEAR': 'movieYear',
259 'EPISODENUM': 'episodeNumber'}
260 metadata = {}
261 path = os.path.dirname(unicode(full_path, 'utf-8'))
262 eyetvp = [x for x in os.listdir(path) if x.endswith('.eyetvp')][0]
263 eyetvp = os.path.join(path, eyetvp)
264 try:
265 eyetv = plistlib.readPlist(eyetvp)
266 except:
267 return metadata
268 if 'epg info' in eyetv:
269 info = eyetv['epg info']
270 for key in keys:
271 if info[key]:
272 metadata[keys[key]] = info[key]
273 if info['SUBTITLE']:
274 metadata['seriesTitle'] = info['TITLE']
275 if info['ACTORS']:
276 metadata['vActor'] = [x.strip() for x in info['ACTORS'].split(',')]
277 if info['DIRECTOR']:
278 metadata['vDirector'] = [info['DIRECTOR']]
280 for ptag, etag, ratings in [('tvRating', 'TV_RATING', TV_RATINGS),
281 ('mpaaRating', 'MPAA_RATING', MPAA_RATINGS),
282 ('starRating', 'STAR_RATING', STAR_RATINGS)]:
283 x = info[etag].upper()
284 if x and x in ratings:
285 metadata[ptag] = ratings[x]
287 # movieYear must be set for the mpaa/star ratings to work
288 if (('mpaaRating' in metadata or 'starRating' in metadata) and
289 'movieYear' not in metadata):
290 metadata['movieYear'] = eyetv['info']['start'].year
291 return metadata
293 def from_text(full_path):
294 metadata = {}
295 full_path = unicode(full_path, 'utf-8')
296 path, name = os.path.split(full_path)
297 title, ext = os.path.splitext(name)
299 search_paths = []
300 ptmp = full_path
301 while ptmp:
302 parent = os.path.dirname(ptmp)
303 if ptmp != parent:
304 ptmp = parent
305 else:
306 break
307 search_paths.append(os.path.join(ptmp, 'default.txt'))
309 search_paths.append(os.path.join(path, title) + '.properties')
310 search_paths.reverse()
312 search_paths += [full_path + '.txt',
313 os.path.join(path, '.meta', 'default.txt'),
314 os.path.join(path, '.meta', name) + '.txt']
316 for metafile in search_paths:
317 if os.path.exists(metafile):
318 sep = ':='[metafile.endswith('.properties')]
319 for line in file(metafile, 'U'):
320 if line.startswith(BOM):
321 line = line[3:]
322 if line.strip().startswith('#') or not sep in line:
323 continue
324 key, value = [x.strip() for x in line.split(sep, 1)]
325 if not key or not value:
326 continue
327 if key.startswith('v'):
328 if key in metadata:
329 metadata[key].append(value)
330 else:
331 metadata[key] = [value]
332 else:
333 metadata[key] = value
335 for rating, ratings in [('tvRating', TV_RATINGS),
336 ('mpaaRating', MPAA_RATINGS),
337 ('starRating', STAR_RATINGS)]:
338 x = metadata.get(rating, '').upper()
339 if x in ratings:
340 metadata[rating] = ratings[x]
341 else:
342 try:
343 x = int(x)
344 metadata[rating] = x
345 except:
346 pass
348 return metadata
350 def basic(full_path, mtime=None):
351 base_path, name = os.path.split(full_path)
352 title, ext = os.path.splitext(name)
353 if not mtime:
354 mtime = os.path.getmtime(unicode(full_path, 'utf-8'))
355 try:
356 originalAirDate = datetime.utcfromtimestamp(mtime)
357 except:
358 originalAirDate = datetime.utcnow()
360 metadata = {'title': title,
361 'originalAirDate': originalAirDate.isoformat()}
362 ext = ext.lower()
363 if ext in ['.mp4', '.m4v', '.mov']:
364 metadata.update(from_moov(full_path))
365 elif ext in ['.dvr-ms', '.asf', '.wmv']:
366 metadata.update(from_dvrms(full_path))
367 elif 'plistlib' in sys.modules and base_path.endswith('.eyetv'):
368 metadata.update(from_eyetv(full_path))
369 metadata.update(from_nfo(full_path))
370 metadata.update(from_text(full_path))
372 return metadata
374 def from_container(xmldoc):
375 metadata = {}
377 keys = {'title': 'Title', 'episodeTitle': 'EpisodeTitle',
378 'description': 'Description', 'programId': 'ProgramId',
379 'seriesId': 'SeriesId', 'episodeNumber': 'EpisodeNumber',
380 'tvRating': 'TvRating', 'displayMajorNumber': 'SourceChannel',
381 'callsign': 'SourceStation', 'showingBits': 'ShowingBits',
382 'mpaaRating': 'MpaaRating'}
384 details = xmldoc.getElementsByTagName('Details')[0]
386 for key in keys:
387 data = tag_data(details, keys[key])
388 if data:
389 if key == 'description':
390 data = data.replace(TRIBUNE_CR, '')
391 elif key == 'tvRating':
392 data = int(data)
393 elif key == 'displayMajorNumber':
394 if '-' in data:
395 data, metadata['displayMinorNumber'] = data.split('-')
396 metadata[key] = data
398 return metadata
400 def from_details(xml):
401 metadata = {}
403 xmldoc = minidom.parseString(xml)
404 showing = xmldoc.getElementsByTagName('showing')[0]
405 program = showing.getElementsByTagName('program')[0]
407 items = {'description': 'program/description',
408 'title': 'program/title',
409 'episodeTitle': 'program/episodeTitle',
410 'episodeNumber': 'program/episodeNumber',
411 'programId': 'program/uniqueId',
412 'seriesId': 'program/series/uniqueId',
413 'seriesTitle': 'program/series/seriesTitle',
414 'originalAirDate': 'program/originalAirDate',
415 'isEpisode': 'program/isEpisode',
416 'movieYear': 'program/movieYear',
417 'partCount': 'partCount',
418 'partIndex': 'partIndex',
419 'time': 'time'}
421 for item in items:
422 data = tag_data(showing, items[item])
423 if data:
424 if item == 'description':
425 data = data.replace(TRIBUNE_CR, '')
426 metadata[item] = data
428 vItems = ['vActor', 'vChoreographer', 'vDirector',
429 'vExecProducer', 'vProgramGenre', 'vGuestStar',
430 'vHost', 'vProducer', 'vWriter']
432 for item in vItems:
433 data = _vtag_data(program, item)
434 if data:
435 metadata[item] = data
437 sb = showing.getElementsByTagName('showingBits')
438 if sb:
439 metadata['showingBits'] = sb[0].attributes['value'].value
441 #for tag in ['starRating', 'mpaaRating', 'colorCode']:
442 for tag in ['starRating', 'mpaaRating']:
443 value = _tag_value(program, tag)
444 if value:
445 metadata[tag] = value
447 rating = _tag_value(showing, 'tvRating')
448 if rating:
449 metadata['tvRating'] = rating
451 return metadata
453 def _nfo_vitems(source, metadata):
455 vItems = {'vGenre': 'genre',
456 'vWriter': 'credits',
457 'vDirector': 'director',
458 'vActor': 'actor/name'}
460 for key in vItems:
461 data = _vtag_data_alternate(source, vItems[key])
462 if data:
463 metadata.setdefault(key, [])
464 for dat in data:
465 if not dat in metadata[key]:
466 metadata[key].append(dat)
468 if 'vGenre' in metadata:
469 metadata['vSeriesGenre'] = metadata['vProgramGenre'] = metadata['vGenre']
471 return metadata
473 def _parse_nfo(nfo_path, nfo_data=None):
474 # nfo files can contain XML or a URL to seed the XBMC metadata scrapers
475 # It's also possible to have both (a URL after the XML metadata)
476 # pyTivo only parses the XML metadata, but we'll try to stip the URL
477 # from mixed XML/URL files. Returns `None` when XML can't be parsed.
478 if nfo_data is None:
479 nfo_data = [line.strip() for line in file(nfo_path, 'rU')]
480 xmldoc = None
481 try:
482 xmldoc = minidom.parseString(os.linesep.join(nfo_data))
483 except expat.ExpatError, err:
484 if expat.ErrorString(err.code) == expat.errors.XML_ERROR_INVALID_TOKEN:
485 # might be a URL outside the xml
486 while len(nfo_data) > err.lineno:
487 if len(nfo_data[-1]) == 0:
488 nfo_data.pop()
489 else:
490 break
491 if len(nfo_data) == err.lineno:
492 # last non-blank line contains the error
493 nfo_data.pop()
494 return _parse_nfo(nfo_path, nfo_data)
495 return xmldoc
497 def _from_tvshow_nfo(tvshow_nfo_path):
498 if tvshow_nfo_path in nfo_cache:
499 return nfo_cache[tvshow_nfo_path]
501 items = {'description': 'plot',
502 'title': 'title',
503 'seriesTitle': 'showtitle',
504 'starRating': 'rating',
505 'tvRating': 'mpaa'}
507 nfo_cache[tvshow_nfo_path] = metadata = {}
509 xmldoc = _parse_nfo(tvshow_nfo_path)
510 if not xmldoc:
511 return metadata
513 tvshow = xmldoc.getElementsByTagName('tvshow')
514 if tvshow:
515 tvshow = tvshow[0]
516 else:
517 return metadata
519 for item in items:
520 data = tag_data(tvshow, items[item])
521 if data:
522 metadata[item] = data
524 metadata = _nfo_vitems(tvshow, metadata)
526 nfo_cache[tvshow_nfo_path] = metadata
527 return metadata
529 def _from_episode_nfo(nfo_path, xmldoc):
530 metadata = {}
532 items = {'description': 'plot',
533 'episodeTitle': 'title',
534 'seriesTitle': 'showtitle',
535 'originalAirDate': 'aired',
536 'starRating': 'rating',
537 'tvRating': 'mpaa'}
539 # find tvshow.nfo
540 path = nfo_path
541 while True:
542 basepath = os.path.dirname(path)
543 if path == basepath:
544 break
545 path = basepath
546 tv_nfo = os.path.join(path, 'tvshow.nfo')
547 if os.path.exists(tv_nfo):
548 metadata.update(_from_tvshow_nfo(tv_nfo))
549 break
551 episode = xmldoc.getElementsByTagName('episodedetails')
552 if episode:
553 episode = episode[0]
554 else:
555 return metadata
557 metadata['isEpisode'] = 'true'
558 for item in items:
559 data = tag_data(episode, items[item])
560 if data:
561 metadata[item] = data
563 season = tag_data(episode, 'displayseason')
564 if not season or season == "-1":
565 season = tag_data(episode, 'season')
566 if not season:
567 season = 1
569 ep_num = tag_data(episode, 'displayepisode')
570 if not ep_num or ep_num == "-1":
571 ep_num = tag_data(episode, 'episode')
572 if ep_num and ep_num != "-1":
573 metadata['episodeNumber'] = "%d%02d" % (int(season), int(ep_num))
575 if 'originalAirDate' in metadata:
576 metadata['originalAirDate'] += 'T00:00:00Z'
578 metadata = _nfo_vitems(episode, metadata)
580 return metadata
582 def _from_movie_nfo(xmldoc):
583 metadata = {}
585 movie = xmldoc.getElementsByTagName('movie')
586 if movie:
587 movie = movie[0]
588 else:
589 return metadata
591 items = {'description': 'plot',
592 'title': 'title',
593 'movieYear': 'year',
594 'starRating': 'rating',
595 'mpaaRating': 'mpaa'}
597 metadata['isEpisode'] = 'false'
599 for item in items:
600 data = tag_data(movie, items[item])
601 if data:
602 metadata[item] = data
604 metadata['movieYear'] = "%04d" % int(metadata.get('movieYear', 0))
606 metadata = _nfo_vitems(movie, metadata)
607 return metadata
609 def from_nfo(full_path):
610 if full_path in nfo_cache:
611 return nfo_cache[full_path]
613 metadata = nfo_cache[full_path] = {}
615 nfo_path = "%s.nfo" % os.path.splitext(full_path)[0]
616 if not os.path.exists(nfo_path):
617 return metadata
619 xmldoc = _parse_nfo(nfo_path)
620 if not xmldoc:
621 return metadata
623 if xmldoc.getElementsByTagName('episodedetails'):
624 # it's an episode
625 metadata.update(_from_episode_nfo(nfo_path, xmldoc))
626 elif xmldoc.getElementsByTagName('movie'):
627 # it's a movie
628 metadata.update(_from_movie_nfo(xmldoc))
630 # common nfo cleanup
631 if 'starRating' in metadata:
632 # .NFO 0-10 -> TiVo 1-7
633 rating = int(float(metadata['starRating']) * 6 / 10 + 1.5)
634 metadata['starRating'] = rating
636 for key, mapping in [('mpaaRating', MPAA_RATINGS),
637 ('tvRating', TV_RATINGS)]:
638 if key in metadata:
639 rating = mapping.get(metadata[key], None)
640 if rating:
641 metadata[key] = str(rating)
642 else:
643 del metadata[key]
645 nfo_cache[full_path] = metadata
646 return metadata
648 def _tdcat_bin(tdcat_path, full_path, tivo_mak):
649 fname = unicode(full_path, 'utf-8')
650 if mswindows:
651 fname = fname.encode('iso8859-1')
652 tcmd = [tdcat_path, '-m', tivo_mak, '-2', fname]
653 tdcat = subprocess.Popen(tcmd, stdout=subprocess.PIPE)
654 return tdcat.stdout.read()
656 def _tdcat_py(full_path, tivo_mak):
657 xml_data = {}
659 tfile = open(full_path, 'rb')
660 header = tfile.read(16)
661 offset, chunks = struct.unpack('>LH', header[10:])
662 rawdata = tfile.read(offset - 16)
663 tfile.close()
665 count = 0
666 for i in xrange(chunks):
667 chunk_size, data_size, id, enc = struct.unpack('>LLHH',
668 rawdata[count:count + 12])
669 count += 12
670 data = rawdata[count:count + data_size]
671 xml_data[id] = {'enc': enc, 'data': data, 'start': count + 16}
672 count += chunk_size - 12
674 chunk = xml_data[2]
675 details = chunk['data']
676 if chunk['enc']:
677 xml_key = xml_data[3]['data']
679 hexmak = hashlib.md5('tivo:TiVo DVR:' + tivo_mak).hexdigest()
680 key = hashlib.sha1(hexmak + xml_key).digest()[:16] + '\0\0\0\0'
682 turkey = hashlib.sha1(key[:17]).digest()
683 turiv = hashlib.sha1(key).digest()
685 details = turing.Turing(turkey, turiv).crypt(details, chunk['start'])
687 return details
689 def from_tivo(full_path):
690 if full_path in tivo_cache:
691 return tivo_cache[full_path]
693 tdcat_path = config.get_bin('tdcat')
694 tivo_mak = config.get_server('tivo_mak')
695 try:
696 assert(tivo_mak)
697 if tdcat_path:
698 details = _tdcat_bin(tdcat_path, full_path, tivo_mak)
699 else:
700 details = _tdcat_py(full_path, tivo_mak)
701 metadata = from_details(details)
702 tivo_cache[full_path] = metadata
703 except:
704 metadata = {}
706 return metadata
708 def force_utf8(text):
709 if type(text) == str:
710 try:
711 text = text.decode('utf8')
712 except:
713 if sys.platform == 'darwin':
714 text = text.decode('macroman')
715 else:
716 text = text.decode('iso8859-1')
717 return text.encode('utf-8')
719 def dump(output, metadata):
720 for key in metadata:
721 value = metadata[key]
722 if type(value) == list:
723 for item in value:
724 output.write('%s: %s\n' % (key, item.encode('utf-8')))
725 else:
726 if key in HUMAN and value in HUMAN[key]:
727 output.write('%s: %s\n' % (key, HUMAN[key][value]))
728 else:
729 output.write('%s: %s\n' % (key, value.encode('utf-8')))
731 if __name__ == '__main__':
732 if len(sys.argv) > 1:
733 metadata = {}
734 config.init([])
735 logging.basicConfig()
736 fname = force_utf8(sys.argv[1])
737 ext = os.path.splitext(fname)[1].lower()
738 if ext == '.tivo':
739 metadata.update(from_tivo(fname))
740 elif ext in ['.mp4', '.m4v', '.mov']:
741 metadata.update(from_moov(fname))
742 elif ext in ['.dvr-ms', '.asf', '.wmv']:
743 metadata.update(from_dvrms(fname))
744 elif ext == '.wtv':
745 vInfo = plugins.video.transcode.video_info(fname)
746 metadata.update(from_mscore(vInfo['rawmeta']))
747 dump(sys.stdout, metadata)