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 key
.startswith('v'):
130 metadata
[key
].append(value
)
132 metadata
[key
] = [value
]
134 metadata
[key
] = value
137 def basic(full_path
):
138 base_path
, name
= os
.path
.split(full_path
)
139 title
, ext
= os
.path
.splitext(name
)
140 mtime
= os
.stat(full_path
).st_mtime
143 originalAirDate
= datetime
.fromtimestamp(mtime
)
145 metadata
= {'title': title
,
146 'originalAirDate': originalAirDate
.isoformat()}
147 if ext
.lower() in ['.mp4', '.m4v', '.mov']:
148 metadata
.update(from_moov(full_path
))
149 metadata
.update(from_text(full_path
))
153 def from_container(xmldoc
):
156 keys
= {'title': 'Title', 'episodeTitle': 'EpisodeTitle',
157 'description': 'Description', 'seriesId': 'SeriesId',
158 'episodeNumber': 'EpisodeNumber', 'tvRating': 'TvRating',
159 'displayMajorNumber': 'SourceChannel', 'callsign': 'SourceStation'}
161 details
= xmldoc
.getElementsByTagName('Details')[0]
164 data
= tag_data(details
, keys
[key
])
166 if key
== 'description':
167 data
= data
.replace(TRIBUNE_CR
, '')
168 elif key
== 'tvRating':
170 elif key
== 'displayMajorNumber':
172 data
, metadata
['displayMinorNumber'] = data
.split('-')
177 def from_details(xmldoc
):
180 showing
= xmldoc
.getElementsByTagName('showing')[0]
181 program
= showing
.getElementsByTagName('program')[0]
183 items
= {'description': 'program/description',
184 'title': 'program/title',
185 'episodeTitle': 'program/episodeTitle',
186 'episodeNumber': 'program/episodeNumber',
187 'seriesId': 'program/series/uniqueId',
188 'seriesTitle': 'program/series/seriesTitle',
189 'originalAirDate': 'program/originalAirDate',
190 'isEpisode': 'program/isEpisode',
191 'movieYear': 'program/movieYear',
192 'partCount': 'partCount',
193 'partIndex': 'partIndex'}
196 data
= tag_data(showing
, items
[item
])
198 if item
== 'description':
199 data
= data
.replace(TRIBUNE_CR
, '')
200 metadata
[item
] = data
202 vItems
= ['vActor', 'vChoreographer', 'vDirector',
203 'vExecProducer', 'vProgramGenre', 'vGuestStar',
204 'vHost', 'vProducer', 'vWriter']
207 data
= _vtag_data(program
, item
)
209 metadata
[item
] = data
211 sb
= showing
.getElementsByTagName('showingBits')
213 metadata
['showingBits'] = sb
[0].attributes
['value'].value
215 for tag
in ['starRating', 'mpaaRating', 'colorCode']:
216 value
= _tag_value(program
, tag
)
218 metadata
[tag
] = value
220 rating
= _tag_value(showing
, 'tvRating')
222 metadata
['tvRating'] = 'x' + rating
[1]
226 def from_tivo(full_path
):
227 if full_path
in tivo_cache
:
228 return tivo_cache
[full_path
]
230 tdcat_path
= config
.get_bin('tdcat')
231 tivo_mak
= config
.get_server('tivo_mak')
232 if tdcat_path
and tivo_mak
:
233 tcmd
= [tdcat_path
, '-m', tivo_mak
, '-2', full_path
]
234 tdcat
= subprocess
.Popen(tcmd
, stdout
=subprocess
.PIPE
)
235 xmldoc
= minidom
.parse(tdcat
.stdout
)
236 metadata
= from_details(xmldoc
)
237 tivo_cache
[full_path
] = metadata
243 if __name__
== '__main__':
245 if len(sys
.argv
) > 1:
247 ext
= os
.path
.splitext(sys
.argv
[1])[1].lower()
250 metadata
.update(from_tivo(sys
.argv
[1]))
251 elif ext
in ['.mp4', '.m4v', '.mov']:
252 metadata
.update(from_moov(sys
.argv
[1]))
254 value
= metadata
[key
]
255 if type(value
) == list:
257 print '%s: %s' % (key
, item
.encode('utf8'))
259 print '%s: %s' % (key
, value
.encode('utf8'))