Fixed the pyTivo.conf.dist to say fast_listing vs fast_index.
[pyTivo/TheBayer.git] / metadata.py
blob3f1b8485ca1ac6d9606aa7471274655f0d3d6ee9
1 #!/usr/bin/env python
3 import os
4 import subprocess
5 from datetime import datetime
6 from xml.dom import minidom
8 import mutagen
9 from lrucache import LRUCache
11 import config
13 # Something to strip
14 TRIBUNE_CR = ' Copyright Tribune Media Services, Inc.'
16 tivo_cache = LRUCache(50)
17 mp4_cache = LRUCache(50)
19 def tag_data(element, tag):
20 for name in tag.split('/'):
21 new_element = element.getElementsByTagName(name)
22 if not new_element:
23 return ''
24 element = new_element[0]
25 if not element.firstChild:
26 return ''
27 return element.firstChild.data
29 def _vtag_data(element, tag):
30 for name in tag.split('/'):
31 new_element = element.getElementsByTagName(name)
32 if not new_element:
33 return []
34 element = new_element[0]
35 elements = element.getElementsByTagName('element')
36 return [x.firstChild.data for x in elements if x.firstChild]
38 def _tag_value(element, tag):
39 item = element.getElementsByTagName(tag)
40 if item:
41 value = item[0].attributes['value'].value
42 name = item[0].firstChild.data
43 return name[0] + value[0]
45 def from_moov(full_path):
46 if full_path in mp4_cache:
47 return mp4_cache[full_path]
49 metadata = {}
50 len_desc = 0
52 try:
53 mp4meta = mutagen.File(full_path)
54 assert(mp4meta)
55 except:
56 mp4_cache[full_path] = {}
57 return {}
59 for key, value in mp4meta.items():
60 # The following 1-to-1 correspondence of atoms to pyTivo
61 # variables is TV-biased
62 keys = {'tvnn': 'callsign', 'tven': 'episodeNumber',
63 'tvsh': 'seriesTitle'}
64 if type(value) == list:
65 value = value[0]
66 if key == 'stik':
67 metadata['isEpisode'] = ['false', 'true'][value == 'TV Show']
68 elif key in keys:
69 metadata[keys[key]] = value
70 # These keys begin with the copyright symbol \xA9
71 elif key == '\xa9day':
72 if len(value) == 4:
73 value += '-01-01T16:00:00Z'
74 metadata['originalAirDate'] = value
75 #metadata['time'] = value
76 elif key in ['\xa9gen', 'gnre']:
77 for k in ('vProgramGenre', 'vSeriesGenre'):
78 if k in metadata:
79 metadata[k].append(value)
80 else:
81 metadata[k] = [value]
82 elif key == '\xa9nam':
83 if 'tvsh' in mp4meta:
84 metadata['episodeTitle'] = value
85 else:
86 metadata['title'] = value
88 # Description in desc, cmt, and/or ldes tags. Keep the longest.
89 elif key in ['desc', '\xa9cmt', 'ldes'] and len(value) > len_desc:
90 metadata['description'] = value
91 len_desc = len(value)
93 # A common custom "reverse DNS format" tag
94 # Possible TV values: TV-Y7 TV-Y TV-G TV-PG TV-14 TV-MA Unrated
95 # Possible MPAA values: G PG PG-13 R NC-17 Unrated
96 elif (key == '----:com.apple.iTunes:iTunEXTC' and
97 ('us-tv' in value or 'mpaa' in value)):
98 ratings = {'TV-Y7': 'x1', 'TV-Y': 'x2', 'TV-G': 'x3',
99 'TV-PG': 'x4', 'TV-14': 'x5', 'TV-MA': 'x6',
100 'Unrated': 'x7', 'G': 'G1', 'PG': 'P2',
101 'PG-13': 'P3', 'R': 'R4', 'NC-17': 'N6'}
102 rating = value.split("|")[1]
103 if rating in ratings:
104 if 'us-tv' in value:
105 metadata['tvRating'] = ratings[rating]
106 elif 'mpaa' in value:
107 metadata['mpaaRating'] = ratings[rating]
109 # Actors, directors, producers, AND screenwriters may be in a long
110 # embedded XML plist, with key '----' and rDNS 'iTunMOVI'. Ughh!
112 mp4_cache[full_path] = metadata
113 return metadata
115 def from_text(full_path):
116 metadata = {}
117 path, name = os.path.split(full_path)
118 title, ext = os.path.splitext(name)
119 for metafile in [os.path.join(path, title) + '.properties',
120 os.path.join(path, 'default.txt'), full_path + '.txt',
121 os.path.join(path, '.meta', name) + '.txt']:
122 if os.path.exists(metafile):
123 sep = ':='[metafile.endswith('.properties')]
124 for line in file(metafile, 'U'):
125 if line.strip().startswith('#') or not sep in line:
126 continue
127 key, value = [x.strip() for x in line.split(sep, 1)]
128 if not key or not value:
129 continue
130 if key.startswith('v'):
131 if key in metadata:
132 metadata[key].append(value)
133 else:
134 metadata[key] = [value]
135 else:
136 metadata[key] = value
137 return metadata
139 def basic(full_path):
140 base_path, name = os.path.split(full_path)
141 title, ext = os.path.splitext(name)
142 mtime = os.stat(full_path).st_mtime
143 if (mtime < 0):
144 mtime = 0
145 originalAirDate = datetime.fromtimestamp(mtime)
147 metadata = {'title': title,
148 'originalAirDate': originalAirDate.isoformat()}
149 if ext.lower() in ['.mp4', '.m4v', '.mov']:
150 metadata.update(from_moov(full_path))
151 metadata.update(from_text(full_path))
153 return metadata
155 def from_container(xmldoc):
156 metadata = {}
158 keys = {'title': 'Title', 'episodeTitle': 'EpisodeTitle',
159 'description': 'Description', 'seriesId': 'SeriesId',
160 'episodeNumber': 'EpisodeNumber', 'tvRating': 'TvRating',
161 'displayMajorNumber': 'SourceChannel', 'callsign': 'SourceStation'}
163 details = xmldoc.getElementsByTagName('Details')[0]
165 for key in keys:
166 data = tag_data(details, keys[key])
167 if data:
168 if key == 'description':
169 data = data.replace(TRIBUNE_CR, '')
170 elif key == 'tvRating':
171 data = 'x' + data
172 elif key == 'displayMajorNumber':
173 if '-' in data:
174 data, metadata['displayMinorNumber'] = data.split('-')
175 metadata[key] = data
177 return metadata
179 def from_details(xmldoc):
180 metadata = {}
182 showing = xmldoc.getElementsByTagName('showing')[0]
183 program = showing.getElementsByTagName('program')[0]
185 items = {'description': 'program/description',
186 'title': 'program/title',
187 'episodeTitle': 'program/episodeTitle',
188 'episodeNumber': 'program/episodeNumber',
189 'seriesId': 'program/series/uniqueId',
190 'seriesTitle': 'program/series/seriesTitle',
191 'originalAirDate': 'program/originalAirDate',
192 'isEpisode': 'program/isEpisode',
193 'movieYear': 'program/movieYear',
194 'partCount': 'partCount',
195 'partIndex': 'partIndex'}
197 for item in items:
198 data = tag_data(showing, items[item])
199 if data:
200 if item == 'description':
201 data = data.replace(TRIBUNE_CR, '')
202 metadata[item] = data
204 vItems = ['vActor', 'vChoreographer', 'vDirector',
205 'vExecProducer', 'vProgramGenre', 'vGuestStar',
206 'vHost', 'vProducer', 'vWriter']
208 for item in vItems:
209 data = _vtag_data(program, item)
210 if data:
211 metadata[item] = data
213 sb = showing.getElementsByTagName('showingBits')
214 if sb:
215 metadata['showingBits'] = sb[0].attributes['value'].value
217 for tag in ['starRating', 'mpaaRating', 'colorCode']:
218 value = _tag_value(program, tag)
219 if value:
220 metadata[tag] = value
222 rating = _tag_value(showing, 'tvRating')
223 if rating:
224 metadata['tvRating'] = 'x' + rating[1]
226 return metadata
228 def from_tivo(full_path):
229 if full_path in tivo_cache:
230 return tivo_cache[full_path]
232 tdcat_path = config.get_bin('tdcat')
233 tivo_mak = config.get_server('tivo_mak')
234 if tdcat_path and tivo_mak:
235 tcmd = [tdcat_path, '-m', tivo_mak, '-2', full_path]
236 tdcat = subprocess.Popen(tcmd, stdout=subprocess.PIPE)
237 xmldoc = minidom.parse(tdcat.stdout)
238 metadata = from_details(xmldoc)
239 tivo_cache[full_path] = metadata
240 else:
241 metadata = {}
243 return metadata
245 if __name__ == '__main__':
246 import sys
247 if len(sys.argv) > 1:
248 metadata = {}
249 ext = os.path.splitext(sys.argv[1])[1].lower()
250 if ext == '.tivo':
251 config.init([])
252 metadata.update(from_tivo(sys.argv[1]))
253 elif ext in ['.mp4', '.m4v', '.mov']:
254 metadata.update(from_moov(sys.argv[1]))
255 for key in metadata:
256 value = metadata[key]
257 if type(value) == list:
258 for item in value:
259 print '%s: %s' % (key, item.encode('utf8'))
260 else:
261 print '%s: %s' % (key, value.encode('utf8'))