Merge commit 'wmcbrine/master'
[pyTivo/TheBayer.git] / metadata.py
bloba169cfc16ff09dca8348eb05d1ba3a63a17a4bb8
1 #!/usr/bin/env python
3 import os
4 import subprocess
5 import sys
6 from datetime import datetime
7 from xml.dom import minidom
8 try:
9 import plistlib
10 except:
11 pass
13 import mutagen
14 from lrucache import LRUCache
16 import config
18 # Something to strip
19 TRIBUNE_CR = ' Copyright Tribune Media Services, Inc.'
21 tivo_cache = LRUCache(50)
22 mp4_cache = LRUCache(50)
24 def tag_data(element, tag):
25 for name in tag.split('/'):
26 new_element = element.getElementsByTagName(name)
27 if not new_element:
28 return ''
29 element = new_element[0]
30 if not element.firstChild:
31 return ''
32 return element.firstChild.data
34 def _vtag_data(element, tag):
35 for name in tag.split('/'):
36 new_element = element.getElementsByTagName(name)
37 if not new_element:
38 return []
39 element = new_element[0]
40 elements = element.getElementsByTagName('element')
41 return [x.firstChild.data for x in elements if x.firstChild]
43 def _tag_value(element, tag):
44 item = element.getElementsByTagName(tag)
45 if item:
46 value = item[0].attributes['value'].value
47 name = item[0].firstChild.data
48 return name[0] + value[0]
50 def from_moov(full_path):
51 if full_path in mp4_cache:
52 return mp4_cache[full_path]
54 metadata = {}
55 len_desc = 0
57 try:
58 mp4meta = mutagen.File(full_path)
59 assert(mp4meta)
60 except:
61 mp4_cache[full_path] = {}
62 return {}
64 for key, value in mp4meta.items():
65 # The following 1-to-1 correspondence of atoms to pyTivo
66 # variables is TV-biased
67 keys = {'tvnn': 'callsign', 'tven': 'episodeNumber',
68 'tvsh': 'seriesTitle'}
69 if type(value) == list:
70 value = value[0]
71 if key == 'stik':
72 metadata['isEpisode'] = ['false', 'true'][value == 'TV Show']
73 elif key in keys:
74 metadata[keys[key]] = value
75 # These keys begin with the copyright symbol \xA9
76 elif key == '\xa9day':
77 if len(value) == 4:
78 value += '-01-01T16:00:00Z'
79 metadata['originalAirDate'] = value
80 #metadata['time'] = value
81 elif key in ['\xa9gen', 'gnre']:
82 for k in ('vProgramGenre', 'vSeriesGenre'):
83 if k in metadata:
84 metadata[k].append(value)
85 else:
86 metadata[k] = [value]
87 elif key == '\xa9nam':
88 if 'tvsh' in mp4meta:
89 metadata['episodeTitle'] = value
90 else:
91 metadata['title'] = value
93 # Description in desc, cmt, and/or ldes tags. Keep the longest.
94 elif key in ['desc', '\xa9cmt', 'ldes'] and len(value) > len_desc:
95 metadata['description'] = value
96 len_desc = len(value)
98 # A common custom "reverse DNS format" tag
99 # Possible TV values: TV-Y7 TV-Y TV-G TV-PG TV-14 TV-MA Unrated
100 # Possible MPAA values: G PG PG-13 R NC-17 Unrated
101 elif (key == '----:com.apple.iTunes:iTunEXTC' and
102 ('us-tv' in value or 'mpaa' in value)):
103 ratings = {'TV-Y7': 'x1', 'TV-Y': 'x2', 'TV-G': 'x3',
104 'TV-PG': 'x4', 'TV-14': 'x5', 'TV-MA': 'x6',
105 'Unrated': 'x7', 'G': 'G1', 'PG': 'P2',
106 'PG-13': 'P3', 'R': 'R4', 'NC-17': 'N6'}
107 rating = value.split("|")[1]
108 if rating in ratings:
109 if 'us-tv' in value:
110 metadata['tvRating'] = ratings[rating]
111 elif 'mpaa' in value:
112 metadata['mpaaRating'] = ratings[rating]
114 # Actors, directors, producers, AND screenwriters may be in a long
115 # embedded XML plist, with key '----' and rDNS 'iTunMOVI'. Ughh!
117 mp4_cache[full_path] = metadata
118 return metadata
120 def from_eyetv(full_path):
121 ratings = {'TVY7': 'x1', 'TVY': 'x2', 'TVG': 'x3',
122 'TVPG': 'x4', 'TV14': 'x5', 'TVMA': 'x6',
123 'G': 'G1', 'PG': 'P2', 'PG-13': 'P3',
124 'R': 'R4', 'NC-17': 'N6'}
125 keys = {'TITLE': 'title', 'SUBTITLE': 'episodeTitle',
126 'DESCRIPTION': 'description', 'YEAR': 'movieYear',
127 'EPISODENUM': 'episodeNumber'}
128 metadata = {}
129 path, name = os.path.split(full_path)
130 eyetvp = [x for x in os.listdir(path) if x.endswith('.eyetvp')][0]
131 eyetvp = os.path.join(path, eyetvp)
132 eyetv = plistlib.readPlist(eyetvp)
133 if 'epg info' in eyetv:
134 info = eyetv['epg info']
135 for key in keys:
136 if info[key]:
137 metadata[keys[key]] = info[key]
138 if info['SUBTITLE']:
139 metadata['seriesTitle'] = info['TITLE']
140 if info['ACTORS']:
141 metadata['vActor'] = [x.strip() for x in info['ACTORS'].split(',')]
142 if info['DIRECTOR']:
143 metadata['vDirector'] = [info['DIRECTOR']]
144 if info['TV_RATING']:
145 metadata['tvRating'] = ratings[info['TV_RATING']]
146 if info['STAR_RATING']:
147 metadata['starRating'] = 'x%d' % (len(info['STAR_RATING']) * 2 - 1)
148 mpaa = info['MPAA_RATING']
149 if mpaa and mpaa != 'NR':
150 metadata['mpaaRating'] = ratings[mpaa]
151 # movieYear must be set for the mpaa/star ratings to work
152 if (('mpaaRating' in metadata or 'starRating' in metadata) and
153 'movieYear' not in metadata):
154 metadata['movieYear'] = eyetv['info']['start'].year
155 return metadata
157 def from_text(full_path):
158 metadata = {}
159 path, name = os.path.split(full_path)
160 title, ext = os.path.splitext(name)
161 for metafile in [os.path.join(path, title) + '.properties',
162 os.path.join(path, 'default.txt'), full_path + '.txt',
163 os.path.join(path, '.meta', name) + '.txt']:
164 if os.path.exists(metafile):
165 sep = ':='[metafile.endswith('.properties')]
166 for line in file(metafile, 'U'):
167 if line.strip().startswith('#') or not sep in line:
168 continue
169 key, value = [x.strip() for x in line.split(sep, 1)]
170 if not key or not value:
171 continue
172 if key.startswith('v'):
173 if key in metadata:
174 metadata[key].append(value)
175 else:
176 metadata[key] = [value]
177 else:
178 metadata[key] = value
179 return metadata
181 def basic(full_path):
182 base_path, name = os.path.split(full_path)
183 title, ext = os.path.splitext(name)
184 mtime = os.stat(full_path).st_mtime
185 if (mtime < 0):
186 mtime = 0
187 originalAirDate = datetime.fromtimestamp(mtime)
189 metadata = {'title': title,
190 'originalAirDate': originalAirDate.isoformat()}
191 if ext.lower() in ['.mp4', '.m4v', '.mov']:
192 metadata.update(from_moov(full_path))
193 if 'plistlib' in sys.modules and base_path.endswith('.eyetv'):
194 metadata.update(from_eyetv(full_path))
195 metadata.update(from_text(full_path))
197 return metadata
199 def from_container(xmldoc):
200 metadata = {}
202 keys = {'title': 'Title', 'episodeTitle': 'EpisodeTitle',
203 'description': 'Description', 'seriesId': 'SeriesId',
204 'episodeNumber': 'EpisodeNumber', 'tvRating': 'TvRating',
205 'displayMajorNumber': 'SourceChannel', 'callsign': 'SourceStation'}
207 details = xmldoc.getElementsByTagName('Details')[0]
209 for key in keys:
210 data = tag_data(details, keys[key])
211 if data:
212 if key == 'description':
213 data = data.replace(TRIBUNE_CR, '')
214 elif key == 'tvRating':
215 data = 'x' + data
216 elif key == 'displayMajorNumber':
217 if '-' in data:
218 data, metadata['displayMinorNumber'] = data.split('-')
219 metadata[key] = data
221 return metadata
223 def from_details(xmldoc):
224 metadata = {}
226 showing = xmldoc.getElementsByTagName('showing')[0]
227 program = showing.getElementsByTagName('program')[0]
229 items = {'description': 'program/description',
230 'title': 'program/title',
231 'episodeTitle': 'program/episodeTitle',
232 'episodeNumber': 'program/episodeNumber',
233 'seriesId': 'program/series/uniqueId',
234 'seriesTitle': 'program/series/seriesTitle',
235 'originalAirDate': 'program/originalAirDate',
236 'isEpisode': 'program/isEpisode',
237 'movieYear': 'program/movieYear',
238 'partCount': 'partCount',
239 'partIndex': 'partIndex',
240 'time': 'time'}
242 for item in items:
243 data = tag_data(showing, items[item])
244 if data:
245 if item == 'description':
246 data = data.replace(TRIBUNE_CR, '')
247 metadata[item] = data
249 vItems = ['vActor', 'vChoreographer', 'vDirector',
250 'vExecProducer', 'vProgramGenre', 'vGuestStar',
251 'vHost', 'vProducer', 'vWriter']
253 for item in vItems:
254 data = _vtag_data(program, item)
255 if data:
256 metadata[item] = data
258 sb = showing.getElementsByTagName('showingBits')
259 if sb:
260 metadata['showingBits'] = sb[0].attributes['value'].value
262 for tag in ['starRating', 'mpaaRating', 'colorCode']:
263 value = _tag_value(program, tag)
264 if value:
265 metadata[tag] = value
267 rating = _tag_value(showing, 'tvRating')
268 if rating:
269 metadata['tvRating'] = 'x' + rating[1]
271 return metadata
273 def from_tivo(full_path):
274 if full_path in tivo_cache:
275 return tivo_cache[full_path]
277 tdcat_path = config.get_bin('tdcat')
278 tivo_mak = config.get_server('tivo_mak')
279 if tdcat_path and tivo_mak:
280 tcmd = [tdcat_path, '-m', tivo_mak, '-2', full_path]
281 tdcat = subprocess.Popen(tcmd, stdout=subprocess.PIPE)
282 xmldoc = minidom.parse(tdcat.stdout)
283 metadata = from_details(xmldoc)
284 tivo_cache[full_path] = metadata
285 else:
286 metadata = {}
288 return metadata
290 if __name__ == '__main__':
291 if len(sys.argv) > 1:
292 metadata = {}
293 ext = os.path.splitext(sys.argv[1])[1].lower()
294 if ext == '.tivo':
295 config.init([])
296 metadata.update(from_tivo(sys.argv[1]))
297 elif ext in ['.mp4', '.m4v', '.mov']:
298 metadata.update(from_moov(sys.argv[1]))
299 for key in metadata:
300 value = metadata[key]
301 if type(value) == list:
302 for item in value:
303 print '%s: %s' % (key, item.encode('utf8'))
304 else:
305 print '%s: %s' % (key, value.encode('utf8'))