5 from datetime
import datetime
6 from xml
.dom
import minidom
9 from lrucache
import LRUCache
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
)
24 element
= new_element
[0]
25 if not element
.firstChild
:
27 return element
.firstChild
.data
29 def _vtag_data(element
, tag
):
30 for name
in tag
.split('/'):
31 new_element
= element
.getElementsByTagName(name
)
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
)
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
]
53 mp4meta
= mutagen
.File(full_path
)
56 mp4_cache
[full_path
] = {}
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:
67 metadata
['isEpisode'] = ['false', 'true'][value
== 'TV Show']
69 metadata
[keys
[key
]] = value
70 # These keys begin with the copyright symbol \xA9
71 elif key
== '\xa9day':
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'):
79 metadata
[k
].append(value
)
82 elif key
== '\xa9nam':
84 metadata
['episodeTitle'] = value
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
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
:
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
115 def from_text(full_path
):
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
:
127 key
, value
= [x
.strip() for x
in line
.split(sep
, 1)]
128 if not key
or not value
:
130 if key
.startswith('v'):
132 metadata
[key
].append(value
)
134 metadata
[key
] = [value
]
136 metadata
[key
] = value
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
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
))
155 def from_container(xmldoc
):
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]
166 data
= tag_data(details
, keys
[key
])
168 if key
== 'description':
169 data
= data
.replace(TRIBUNE_CR
, '')
170 elif key
== 'tvRating':
172 elif key
== 'displayMajorNumber':
174 data
, metadata
['displayMinorNumber'] = data
.split('-')
179 def from_details(xmldoc
):
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'}
198 data
= tag_data(showing
, items
[item
])
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']
209 data
= _vtag_data(program
, item
)
211 metadata
[item
] = data
213 sb
= showing
.getElementsByTagName('showingBits')
215 metadata
['showingBits'] = sb
[0].attributes
['value'].value
217 for tag
in ['starRating', 'mpaaRating', 'colorCode']:
218 value
= _tag_value(program
, tag
)
220 metadata
[tag
] = value
222 rating
= _tag_value(showing
, 'tvRating')
224 metadata
['tvRating'] = 'x' + rating
[1]
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
245 if __name__
== '__main__':
247 if len(sys
.argv
) > 1:
249 ext
= os
.path
.splitext(sys
.argv
[1])[1].lower()
252 metadata
.update(from_tivo(sys
.argv
[1]))
253 elif ext
in ['.mp4', '.m4v', '.mov']:
254 metadata
.update(from_moov(sys
.argv
[1]))
256 value
= metadata
[key
]
257 if type(value
) == list:
259 print '%s: %s' % (key
, item
.encode('utf8'))
261 print '%s: %s' % (key
, value
.encode('utf8'))