13 from plugin
import GetPlugin
16 logger
= logging
.getLogger('pyTivo.video.transcode')
18 info_cache
= lrucache
.LRUCache(1000)
20 GOOD_MPEG_FPS
= ['23.98', '24.00', '25.00', '29.97',
21 '30.00', '50.00', '59.94', '60.00']
24 # subprocess is broken for me on windows so super hack
25 def patchSubprocess():
26 o
= subprocess
.Popen
._make
_inheritable
28 def _make_inheritable(self
, handle
):
29 if not handle
: return subprocess
.GetCurrentProcess()
30 return o(self
, handle
)
32 subprocess
.Popen
._make
_inheritable
= _make_inheritable
33 mswindows
= (sys
.platform
== "win32")
37 def output_video(inFile
, outFile
, tsn
='', mime
=''):
38 if tivo_compatible(inFile
, tsn
, mime
)[0]:
39 logger
.debug('%s is tivo compatible' % inFile
)
40 f
= open(inFile
, 'rb')
42 if mime
== 'video/mp4':
43 qtfaststart
.fast_start(f
, outFile
)
45 shutil
.copyfileobj(f
, outFile
)
46 except Exception, msg
:
50 logger
.debug('%s is not tivo compatible' % inFile
)
51 transcode(False, inFile
, outFile
, tsn
)
52 logger
.debug("Finished outputing video")
54 def transcode(isQuery
, inFile
, outFile
, tsn
=''):
55 settings
= {'video_codec': select_videocodec(inFile
, tsn
),
56 'video_br': select_videobr(inFile
, tsn
),
57 'video_fps': select_videofps(inFile
, tsn
),
58 'max_video_br': select_maxvideobr(tsn
),
59 'buff_size': select_buffsize(tsn
),
60 'aspect_ratio': ' '.join(select_aspect(inFile
, tsn
)),
61 'audio_br': select_audiobr(tsn
),
62 'audio_fr': select_audiofr(inFile
, tsn
),
63 'audio_ch': select_audioch(tsn
),
64 'audio_codec': select_audiocodec(isQuery
, inFile
, tsn
),
65 'audio_lang': select_audiolang(inFile
, tsn
),
66 'ffmpeg_pram': select_ffmpegprams(tsn
),
67 'format': select_format(tsn
)}
72 cmd_string
= config
.getFFmpegTemplate(tsn
) % settings
74 cmd
= [config
.ffmpeg_path(), '-i', inFile
] + cmd_string
.split()
75 logging
.debug('transcoding to tivo model ' + tsn
[:3] +
76 ' using ffmpeg command:')
77 logging
.debug(' '.join(cmd
))
78 ffmpeg
= subprocess
.Popen(cmd
, bufsize
=(512 * 1024),
79 stdout
=subprocess
.PIPE
)
81 shutil
.copyfileobj(ffmpeg
.stdout
, outFile
)
85 def select_audiocodec(isQuery
, inFile
, tsn
=''):
86 vInfo
= video_info(inFile
)
87 codectype
= vInfo
['vCodec']
88 codec
= config
.getAudioCodec(tsn
)
90 # Default, compatible with all TiVo's
92 if vInfo
['aCodec'] in ('ac3', 'liba52', 'mp2'):
93 aKbps
= vInfo
['aKbps']
96 aKbps
= audio_check(inFile
, tsn
)
99 if aKbps
!= None and int(aKbps
) <= config
.getMaxAudioBR(tsn
):
100 # compatible codec and bitrate, do not reencode audio
102 copy_flag
= config
.getCopyTS(tsn
)
104 if ((codec
== 'copy' and codectype
== 'mpeg2video' and not copy_flag
) or
105 (copy_flag
and copy_flag
.lower() == 'false')):
107 return '-acodec ' + codec
+ copyts
109 def select_audiofr(inFile
, tsn
):
110 freq
= '48000' #default
111 vInfo
= video_info(inFile
)
112 if not vInfo
['aFreq'] == None and vInfo
['aFreq'] in ('44100', '48000'):
113 # compatible frequency
114 freq
= vInfo
['aFreq']
115 if config
.getAudioFR(tsn
) != None:
116 freq
= config
.getAudioFR(tsn
)
119 def select_audioch(tsn
):
120 ch
= config
.getAudioCH(tsn
)
125 def select_audiolang(inFile
, tsn
):
126 vInfo
= video_info(inFile
)
127 if config
.getAudioLang(tsn
) != None and vInfo
['mapVideo'] != None:
128 stream
= vInfo
['mapAudio'][0][0]
130 for lang
in config
.getAudioLang(tsn
).replace(' ','').lower().split(','):
131 for s
, l
in vInfo
['mapAudio']:
132 if lang
in s
+ l
.replace(' ','').lower():
138 return '-map ' + vInfo
['mapVideo'] + ' -map ' + stream
141 def select_videofps(inFile
, tsn
):
142 vInfo
= video_info(inFile
)
143 fps
= '-r 29.97' # default
144 if config
.isHDtivo(tsn
) and vInfo
['vFps'] in GOOD_MPEG_FPS
:
146 if config
.getVideoFPS(tsn
) != None:
147 fps
= '-r ' + config
.getVideoFPS(tsn
)
150 def select_videocodec(inFile
, tsn
):
151 vInfo
= video_info(inFile
)
152 if tivo_compatible_video(vInfo
, tsn
)[0]:
155 codec
= 'mpeg2video' # default
156 return '-vcodec ' + codec
158 def select_videobr(inFile
, tsn
):
159 return '-b ' + str(select_videostr(inFile
, tsn
) / 1000) + 'k'
161 def select_videostr(inFile
, tsn
):
162 vInfo
= video_info(inFile
)
163 if tivo_compatible_video(vInfo
, tsn
)[0]:
164 video_str
= int(vInfo
['kbps'])
166 video_str
-= int(vInfo
['aKbps'])
169 video_str
= config
.strtod(config
.getVideoBR(tsn
))
170 if config
.isHDtivo(tsn
):
171 if vInfo
['kbps'] != None and config
.getVideoPCT(tsn
) > 0:
172 video_percent
= (int(vInfo
['kbps']) * 10 *
173 config
.getVideoPCT(tsn
))
174 video_str
= max(video_str
, video_percent
)
175 video_str
= int(min(config
.strtod(config
.getMaxVideoBR(tsn
)) * 0.95,
179 def select_audiobr(tsn
):
180 return '-ab ' + config
.getAudioBR(tsn
)
182 def select_maxvideobr(tsn
):
183 return '-maxrate ' + config
.getMaxVideoBR(tsn
)
185 def select_buffsize(tsn
):
186 return '-bufsize ' + config
.getBuffSize(tsn
)
188 def select_ffmpegprams(tsn
):
189 params
= config
.getFFmpegPrams(tsn
)
194 def select_format(tsn
):
196 return '-f %s -' % fmt
198 def select_aspect(inFile
, tsn
= ''):
199 TIVO_WIDTH
= config
.getTivoWidth(tsn
)
200 TIVO_HEIGHT
= config
.getTivoHeight(tsn
)
202 vInfo
= video_info(inFile
)
204 logging
.debug('tsn: %s' % tsn
)
206 aspect169
= config
.get169Setting(tsn
)
208 logging
.debug('aspect169: %s' % aspect169
)
210 optres
= config
.getOptres(tsn
)
212 logging
.debug('optres: %s' % optres
)
215 optHeight
= config
.nearestTivoHeight(vInfo
['vHeight'])
216 optWidth
= config
.nearestTivoWidth(vInfo
['vWidth'])
217 if optHeight
< TIVO_HEIGHT
:
218 TIVO_HEIGHT
= optHeight
219 if optWidth
< TIVO_WIDTH
:
220 TIVO_WIDTH
= optWidth
222 d
= gcd(vInfo
['vHeight'], vInfo
['vWidth'])
223 ratio
= vInfo
['vWidth'] * 100 / vInfo
['vHeight']
224 rheight
, rwidth
= vInfo
['vHeight'] / d
, vInfo
['vWidth'] / d
226 logger
.debug(('File=%s vCodec=%s vWidth=%s vHeight=%s vFps=%s ' +
227 'millisecs=%s ratio=%s rheight=%s rwidth=%s ' +
228 'TIVO_HEIGHT=%s TIVO_WIDTH=%s') % (inFile
,
229 vInfo
['vCodec'], vInfo
['vWidth'], vInfo
['vHeight'],
230 vInfo
['vFps'], vInfo
['millisecs'], ratio
, rheight
,
231 rwidth
, TIVO_HEIGHT
, TIVO_WIDTH
))
233 multiplier16by9
= (16.0 * TIVO_HEIGHT
) / (9.0 * TIVO_WIDTH
)
234 multiplier4by3
= (4.0 * TIVO_HEIGHT
) / (3.0 * TIVO_WIDTH
)
236 if config
.isHDtivo(tsn
) and not optres
:
237 if config
.getPixelAR(0) or vInfo
['par']:
238 if vInfo
['par2'] == None:
240 npar
= float(vInfo
['par'])
242 npar
= config
.getPixelAR(1)
246 # adjust for pixel aspect ratio, if set, because TiVo
247 # expects square pixels
250 return ['-s', str(vInfo
['vWidth']) + 'x' +
251 str(int(math
.ceil(vInfo
['vHeight'] / npar
)))]
253 # FFMPEG expects width to be a multiple of two
255 return ['-s', str(int(math
.ceil(vInfo
['vWidth'] * npar
/
256 2.0) * 2)) + 'x' + str(vInfo
['vHeight'])]
258 if vInfo
['vHeight'] <= TIVO_HEIGHT
:
259 # pass all resolutions to S3, except heights greater than
262 # else, resize video.
264 if (rwidth
, rheight
) in [(1, 1)] and vInfo
['par1'] == '8:9':
265 logger
.debug('File + PAR is within 4:3.')
266 return ['-aspect', '4:3', '-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
268 elif ((rwidth
, rheight
) in [(4, 3), (10, 11), (15, 11), (59, 54),
269 (59, 72), (59, 36), (59, 54)] or
270 vInfo
['dar1'] == '4:3'):
271 logger
.debug('File is within 4:3 list.')
272 return ['-aspect', '4:3', '-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
274 elif (((rwidth
, rheight
) in [(16, 9), (20, 11), (40, 33), (118, 81),
275 (59, 27)] or vInfo
['dar1'] == '16:9')
276 and (aspect169
or config
.get169Letterbox(tsn
))):
277 logger
.debug('File is within 16:9 list and 16:9 allowed.')
279 if config
.get169Blacklist(tsn
) or (aspect169
and
280 config
.get169Letterbox(tsn
)):
281 return ['-aspect', '4:3', '-s', '%sx%s' %
282 (TIVO_WIDTH
, TIVO_HEIGHT
)]
284 return ['-aspect', '16:9', '-s', '%sx%s' %
285 (TIVO_WIDTH
, TIVO_HEIGHT
)]
289 # If video is wider than 4:3 add top and bottom padding
291 if ratio
> 133: # Might be 16:9 file, or just need padding on
294 if aspect169
and ratio
> 135: # If file would fall in 4:3
295 # assume it is supposed to be 4:3
297 if ratio
> 177: # too short needs padding top and bottom
298 endHeight
= int(((TIVO_WIDTH
* vInfo
['vHeight']) /
299 vInfo
['vWidth']) * multiplier16by9
)
300 settings
.append('-aspect')
301 if (config
.get169Blacklist(tsn
) or
302 config
.get169Letterbox(tsn
)):
303 settings
.append('4:3')
305 settings
.append('16:9')
308 if endHeight
< TIVO_HEIGHT
* 0.99:
309 topPadding
= (TIVO_HEIGHT
- endHeight
) / 2
312 bottomPadding
= (TIVO_HEIGHT
- endHeight
) - topPadding
313 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, endHeight
),
314 '-padtop', str(topPadding
),
315 '-padbottom', str(bottomPadding
)]
316 else: # if only very small amount of padding
317 # needed, then just stretch it
318 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
320 logger
.debug(('16:9 aspect allowed, file is wider ' +
321 'than 16:9 padding top and bottom\n%s') %
324 else: # too skinny needs padding on left and right.
325 endWidth
= int((TIVO_HEIGHT
* vInfo
['vWidth']) /
326 (vInfo
['vHeight'] * multiplier16by9
))
327 settings
.append('-aspect')
328 if (config
.get169Blacklist(tsn
) or
329 config
.get169Letterbox(tsn
)):
330 settings
.append('4:3')
332 settings
.append('16:9')
335 if endWidth
< (TIVO_WIDTH
- 10):
336 leftPadding
= (TIVO_WIDTH
- endWidth
) / 2
339 rightPadding
= (TIVO_WIDTH
- endWidth
) - leftPadding
340 settings
+= ['-s', '%sx%s' % (endWidth
, TIVO_HEIGHT
),
341 '-padleft', str(leftPadding
),
342 '-padright', str(rightPadding
)]
343 else: # if only very small amount of padding needed,
344 # then just stretch it
345 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
346 logger
.debug(('16:9 aspect allowed, file is narrower ' +
347 'than 16:9 padding left and right\n%s') %
349 else: # this is a 4:3 file or 16:9 output not allowed
350 multiplier
= multiplier4by3
351 settings
.append('-aspect')
352 if ratio
> 135 and config
.get169Letterbox(tsn
):
353 settings
.append('16:9')
354 multiplier
= multiplier16by9
356 settings
.append('4:3')
357 endHeight
= int(((TIVO_WIDTH
* vInfo
['vHeight']) /
358 vInfo
['vWidth']) * multiplier
)
361 if endHeight
< TIVO_HEIGHT
* 0.99:
362 topPadding
= (TIVO_HEIGHT
- endHeight
) / 2
365 bottomPadding
= (TIVO_HEIGHT
- endHeight
) - topPadding
366 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, endHeight
),
367 '-padtop', str(topPadding
),
368 '-padbottom', str(bottomPadding
)]
369 else: # if only very small amount of padding needed,
370 # then just stretch it
371 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
372 logging
.debug(('File is wider than 4:3 padding ' +
373 'top and bottom\n%s') % ' '.join(settings
))
377 # If video is taller than 4:3 add left and right padding, this
378 # is rare. All of these files will always be sent in an aspect
379 # ratio of 4:3 since they are so narrow.
382 endWidth
= int((TIVO_HEIGHT
* vInfo
['vWidth']) /
383 (vInfo
['vHeight'] * multiplier4by3
))
384 settings
+= ['-aspect', '4:3']
387 if endWidth
< (TIVO_WIDTH
* 0.99):
388 leftPadding
= (TIVO_WIDTH
- endWidth
) / 2
391 rightPadding
= (TIVO_WIDTH
- endWidth
) - leftPadding
392 settings
+= ['-s', '%sx%s' % (endWidth
, TIVO_HEIGHT
),
393 '-padleft', str(leftPadding
),
394 '-padright', str(rightPadding
)]
395 else: # if only very small amount of padding needed, then
397 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
399 logger
.debug('File is taller than 4:3 padding left and right\n%s'
400 % ' '.join(settings
))
404 def tivo_compatible_video(vInfo
, tsn
, mime
=''):
407 codec
= vInfo
['vCodec']
408 if mime
== 'video/mp4':
410 message
= (False, 'vCodec %s not compatible' % codec
)
414 if mime
== 'video/bif':
416 message
= (False, 'vCodec %s not compatible' % codec
)
420 if codec
not in ('mpeg2video', 'mpeg1video'):
421 message
= (False, 'vCodec %s not compatible' % codec
)
424 if vInfo
['kbps'] != None:
425 abit
= max('0', vInfo
['aKbps'])
426 if (int(vInfo
['kbps']) - int(abit
) >
427 config
.strtod(config
.getMaxVideoBR(tsn
)) / 1000):
428 message
= (False, '%s kbps exceeds max video bitrate' %
432 message
= (False, '%s kbps not supported' % vInfo
['kbps'])
435 if config
.isHDtivo(tsn
):
436 if vInfo
['par2'] != 1.0:
437 if config
.getPixelAR(0):
438 if vInfo
['par2'] != None or config
.getPixelAR(1) != 1.0:
439 message
= (False, '%s not correct PAR' % vInfo
['par2'])
441 # HD Tivo detected, skipping remaining tests.
444 if not vInfo
['vFps'] == '29.97':
445 message
= (False, '%s vFps, should be 29.97' % vInfo
['vFps'])
448 if ((config
.get169Blacklist(tsn
) and not config
.get169Setting(tsn
))
449 or (config
.get169Letterbox(tsn
) and config
.get169Setting(tsn
))):
450 if vInfo
['dar1'] == None or not vInfo
['dar1'] in ('4:3', '8:9',
452 message
= (False, ('DAR %s not supported ' +
453 'by BLACKLIST_169 tivos') % vInfo
['dar1'])
456 mode
= (vInfo
['vWidth'], vInfo
['vHeight'])
457 if mode
not in [(720, 480), (704, 480), (544, 480),
458 (528, 480), (480, 480), (352, 480), (352, 240)]:
459 message
= (False, '%s x %s not in supported modes' % mode
)
464 def tivo_compatible_audio(vInfo
, inFile
, tsn
, mime
=''):
467 codec
= vInfo
['aCodec']
468 if mime
== 'video/mp4':
469 if codec
not in ('mpeg4aac', 'libfaad', 'mp4a', 'aac',
471 message
= (False, 'aCodec %s not compatible' % codec
)
475 if mime
== 'video/bif':
477 message
= (False, 'aCodec %s not compatible' % codec
)
481 if codec
not in ('ac3', 'liba52', 'mp2'):
482 message
= (False, 'aCodec %s not compatible' % codec
)
485 if (not vInfo
['aKbps'] or
486 int(vInfo
['aKbps']) > config
.getMaxAudioBR(tsn
)):
487 message
= (False, '%s kbps exceeds max audio bitrate' %
491 if config
.getAudioLang(tsn
):
492 if vInfo
['mapAudio'][0][0] != select_audiolang(inFile
, tsn
)[-3:]:
493 message
= (False, '%s preferred audio track exists' %
494 config
.getAudioLang(tsn
))
499 def tivo_compatible_container(vInfo
, mime
=''):
501 container
= vInfo
['container']
502 if ((mime
== 'video/mp4' and container
!= 'mov') or
503 (mime
== 'video/bif' and container
!= 'asf') or
504 (mime
in ['video/mpeg', ''] and
505 (container
!= 'mpeg' or vInfo
['vCodec'] == 'mpeg1video'))):
506 message
= (False, 'container %s not compatible' % container
)
510 def tivo_compatible(inFile
, tsn
='', mime
=''):
511 vInfo
= video_info(inFile
)
513 message
= (True, 'all compatible')
515 if inFile
[-5:].lower() == '.tivo':
516 e
= 'ends with .tivo'
517 if mime
in ['video/mp4', 'video/bif']:
523 vmessage
= tivo_compatible_video(vInfo
, tsn
, mime
)
528 amessage
= tivo_compatible_audio(vInfo
, inFile
, tsn
, mime
)
533 cmessage
= tivo_compatible_container(vInfo
, mime
)
539 logger
.debug('TRANSCODE=%s, %s, %s' % (['YES', 'NO'][message
[0]],
543 def video_info(inFile
, cache
=True):
545 mtime
= os
.stat(inFile
).st_mtime
547 if inFile
in info_cache
and info_cache
[inFile
][0] == mtime
:
548 logging
.debug('CACHE HIT! %s' % inFile
)
549 return info_cache
[inFile
][1]
551 vInfo
['Supported'] = True
553 if inFile
[-5:].lower() == '.tivo':
554 vInfo
['millisecs'] = 0
556 info_cache
[inFile
] = (mtime
, vInfo
)
557 logger
.debug('VALID, ends in .tivo. %s' % inFile
)
560 cmd
= [config
.ffmpeg_path(), '-i', inFile
]
561 # Windows and other OS buffer 4096 and ffmpeg can output more than that.
562 err_tmp
= tempfile
.TemporaryFile()
563 ffmpeg
= subprocess
.Popen(cmd
, stderr
=err_tmp
, stdout
=subprocess
.PIPE
,
564 stdin
=subprocess
.PIPE
)
566 # wait configured # of seconds: if ffmpeg is not back give up
567 wait
= config
.getFFmpegWait()
569 'starting ffmpeg, will wait %s seconds for it to complete' % wait
)
570 for i
in xrange(wait
* 20):
572 if not ffmpeg
.poll() == None:
575 if ffmpeg
.poll() == None:
577 vInfo
['Supported'] = False
579 info_cache
[inFile
] = (mtime
, vInfo
)
583 output
= err_tmp
.read()
585 logging
.debug('ffmpeg output=%s' % output
)
587 rezre
= re
.compile(r
'Input #0, ([^,]+),')
588 x
= rezre
.search(output
)
590 vInfo
['container'] = x
.group(1)
592 vInfo
['container'] = ''
593 vInfo
['Supported'] = False
594 logger
.debug('failed at container')
596 rezre
= re
.compile(r
'.*Video: ([^,]+),.*')
597 x
= rezre
.search(output
)
599 vInfo
['vCodec'] = x
.group(1)
602 vInfo
['Supported'] = False
603 logger
.debug('failed at vCodec')
605 rezre
= re
.compile(r
'.*Video: .+, (\d+)x(\d+)[, ].*')
606 x
= rezre
.search(output
)
608 vInfo
['vWidth'] = int(x
.group(1))
609 vInfo
['vHeight'] = int(x
.group(2))
612 vInfo
['vHeight'] = ''
613 vInfo
['Supported'] = False
614 logger
.debug('failed at vWidth/vHeight')
616 rezre
= re
.compile(r
'.*Video: .+, (.+) (?:fps|tb).*')
617 x
= rezre
.search(output
)
619 vInfo
['vFps'] = x
.group(1)
621 # Allow override only if it is mpeg2 and frame rate was doubled
624 if vInfo
['vCodec'] == 'mpeg2video' and vInfo
['vFps'] != '29.97':
625 # First look for the build 7215 version
626 rezre
= re
.compile(r
'.*film source: 29.97.*')
627 x
= rezre
.search(output
.lower())
629 logger
.debug('film source: 29.97 setting vFps to 29.97')
630 vInfo
['vFps'] = '29.97'
633 rezre
= re
.compile(r
'.*frame rate differs from container ' +
634 r
'frame rate: 29.97.*')
635 logger
.debug('Bug in VideoReDo')
636 x
= rezre
.search(output
.lower())
638 vInfo
['vFps'] = '29.97'
641 vInfo
['Supported'] = False
642 logger
.debug('failed at vFps')
644 durre
= re
.compile(r
'.*Duration: ([0-9]+):([0-9]+):([0-9]+)\.([0-9]+),')
645 d
= durre
.search(output
)
648 vInfo
['millisecs'] = ((int(d
.group(1)) * 3600 +
649 int(d
.group(2)) * 60 +
650 int(d
.group(3))) * 1000 +
651 int(d
.group(4)) * (10 ** (3 - len(d
.group(4)))))
653 vInfo
['millisecs'] = 0
655 # get bitrate of source for tivo compatibility test.
656 rezre
= re
.compile(r
'.*bitrate: (.+) (?:kb/s).*')
657 x
= rezre
.search(output
)
659 vInfo
['kbps'] = x
.group(1)
661 # Fallback method of getting video bitrate
662 # Sample line: Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p,
663 # 720x480 [PAR 32:27 DAR 16:9], 9800 kb/s, 59.94 tb(r)
664 rezre
= re
.compile(r
'.*Stream #0\.0\[.*\]: Video: mpeg2video, ' +
665 r
'\S+, \S+ \[.*\], (\d+) (?:kb/s).*')
666 x
= rezre
.search(output
)
668 vInfo
['kbps'] = x
.group(1)
671 logger
.debug('failed at kbps')
673 # get audio bitrate of source for tivo compatibility test.
674 rezre
= re
.compile(r
'.*Audio: .+, (.+) (?:kb/s).*')
675 x
= rezre
.search(output
)
677 vInfo
['aKbps'] = x
.group(1)
679 vInfo
['aKbps'] = None
680 logger
.debug('failed at aKbps')
682 # get audio codec of source for tivo compatibility test.
683 rezre
= re
.compile(r
'.*Audio: ([^,]+),.*')
684 x
= rezre
.search(output
)
686 vInfo
['aCodec'] = x
.group(1)
688 vInfo
['aCodec'] = None
689 logger
.debug('failed at aCodec')
691 # get audio frequency of source for tivo compatibility test.
692 rezre
= re
.compile(r
'.*Audio: .+, (.+) (?:Hz).*')
693 x
= rezre
.search(output
)
695 vInfo
['aFreq'] = x
.group(1)
697 vInfo
['aFreq'] = None
698 logger
.debug('failed at aFreq')
701 rezre
= re
.compile(r
'.*Video: .+PAR ([0-9]+):([0-9]+) DAR [0-9:]+.*')
702 x
= rezre
.search(output
)
703 if x
and x
.group(1) != "0" and x
.group(2) != "0":
704 vInfo
['par1'] = x
.group(1) + ':' + x
.group(2)
705 vInfo
['par2'] = float(x
.group(1)) / float(x
.group(2))
707 vInfo
['par1'], vInfo
['par2'] = None, None
710 rezre
= re
.compile(r
'.*Video: .+DAR ([0-9]+):([0-9]+).*')
711 x
= rezre
.search(output
)
712 if x
and x
.group(1) != "0" and x
.group(2) != "0":
713 vInfo
['dar1'] = x
.group(1) + ':' + x
.group(2)
717 # get Video Stream mapping.
718 rezre
= re
.compile(r
'([0-9]+\.[0-9]+).*: Video:.*')
719 x
= rezre
.search(output
)
721 vInfo
['mapVideo'] = x
.group(1)
723 vInfo
['mapVideo'] = None
724 logger
.debug('failed at mapVideo')
726 # get Audio Stream mapping.
727 rezre
= re
.compile(r
'([0-9]+\.[0-9]+)(.*): Audio:.*')
728 x
= rezre
.search(output
)
731 for x
in rezre
.finditer(output
):
732 amap
.append(x
.groups())
734 amap
.append(('', ''))
735 logger
.debug('failed at mapAudio')
736 vInfo
['mapAudio'] = amap
739 videoPlugin
= GetPlugin('video')
740 metadata
= videoPlugin
.getMetadataFromTxt(inFile
)
743 if key
.startswith('Override_'):
744 vInfo
['Supported'] = True
745 if key
.startswith('Override_mapAudio'):
746 audiomap
= dict(vInfo
['mapAudio'])
747 stream
= key
.replace('Override_mapAudio', '').strip()
748 if stream
in audiomap
:
749 newaudiomap
= (stream
, metadata
[key
])
750 audiomap
.update([newaudiomap
])
751 vInfo
['mapAudio'] = sorted(audiomap
.items(),
752 key
=lambda (k
,v
): (k
,v
))
753 elif key
.startswith('Override_millisecs'):
754 vInfo
[key
.replace('Override_', '')] = int(metadata
[key
])
756 vInfo
[key
.replace('Override_', '')] = metadata
[key
]
759 info_cache
[inFile
] = (mtime
, vInfo
)
760 logger
.debug("; ".join(["%s=%s" % (k
, v
) for k
, v
in vInfo
.items()]))
763 def audio_check(inFile
, tsn
):
764 cmd_string
= ('-y -vcodec mpeg2video -r 29.97 -b 1000k -acodec copy ' +
765 select_audiolang(inFile
, tsn
) + ' -t 00:00:01 -f vob -')
766 cmd
= [config
.ffmpeg_path(), '-i', inFile
] + cmd_string
.split()
767 ffmpeg
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
768 fd
, testname
= tempfile
.mkstemp()
769 testfile
= os
.fdopen(fd
, 'wb')
771 shutil
.copyfileobj(ffmpeg
.stdout
, testfile
)
778 aKbps
= video_info(testname
, False)['aKbps']
782 def supported_format(inFile
):
783 if video_info(inFile
)['Supported']:
786 logger
.debug('FALSE, file not supported %s' % inFile
)
790 logger
.debug('killing pid=%s' % str(popen
.pid
))
796 logger
.debug('sending SIGTERM to pid: %s' % popen
.pid
)
797 os
.kill(popen
.pid
, signal
.SIGTERM
)
799 if popen
.poll() is not None:
800 logger
.debug('process %s has exited' % popen
.pid
)
803 while popen
.poll() is None:
804 logger
.debug('sending SIGKILL to pid: %s' % popen
.pid
)
805 os
.kill(popen
.pid
, signal
.SIGKILL
)
810 handle
= ctypes
.windll
.kernel32
.OpenProcess(1, False, pid
)
811 ctypes
.windll
.kernel32
.TerminateProcess(handle
, -1)
812 ctypes
.windll
.kernel32
.CloseHandle(handle
)