17 logger
= logging
.getLogger('pyTivo.video.transcode')
19 info_cache
= lrucache
.LRUCache(1000)
23 GOOD_MPEG_FPS
= ['23.98', '24.00', '25.00', '29.97',
24 '30.00', '50.00', '59.94', '60.00']
26 BLOCKSIZE
= 512 * 1024
31 # subprocess is broken for me on windows so super hack
32 def patchSubprocess():
33 o
= subprocess
.Popen
._make
_inheritable
35 def _make_inheritable(self
, handle
):
36 if not handle
: return subprocess
.GetCurrentProcess()
37 return o(self
, handle
)
39 subprocess
.Popen
._make
_inheritable
= _make_inheritable
40 mswindows
= (sys
.platform
== "win32")
47 msg
= msg
.decode('utf8')
49 if sys
.platform
== 'darwin':
50 msg
= msg
.decode('macroman')
52 msg
= msg
.decode('iso8859-1')
55 def transcode(isQuery
, inFile
, outFile
, tsn
=''):
56 settings
= {'video_codec': select_videocodec(inFile
, tsn
),
57 'video_br': select_videobr(inFile
, tsn
),
58 'video_fps': select_videofps(inFile
, tsn
),
59 'max_video_br': select_maxvideobr(tsn
),
60 'buff_size': select_buffsize(tsn
),
61 'aspect_ratio': ' '.join(select_aspect(inFile
, tsn
)),
62 'audio_br': select_audiobr(tsn
),
63 'audio_fr': select_audiofr(inFile
, tsn
),
64 'audio_ch': select_audioch(tsn
),
65 'audio_codec': select_audiocodec(isQuery
, inFile
, tsn
),
66 'audio_lang': select_audiolang(inFile
, tsn
),
67 'ffmpeg_pram': select_ffmpegprams(tsn
),
68 'format': select_format(tsn
)}
73 ffmpeg_path
= config
.get_bin('ffmpeg')
74 cmd_string
= config
.getFFmpegTemplate(tsn
) % settings
76 if inFile
[-5:].lower() == '.tivo':
77 tivodecode_path
= config
.get_bin('tivodecode')
78 tivo_mak
= config
.get_server('tivo_mak')
79 tcmd
= [tivodecode_path
, '-m', tivo_mak
, inFile
]
80 tivodecode
= subprocess
.Popen(tcmd
, stdout
=subprocess
.PIPE
,
82 if tivo_compatible(inFile
, tsn
)[0]:
86 cmd
= [ffmpeg_path
, '-i', '-'] + cmd_string
.split()
87 ffmpeg
= subprocess
.Popen(cmd
, stdin
=tivodecode
.stdout
,
88 stdout
=subprocess
.PIPE
,
91 cmd
= [ffmpeg_path
, '-i', inFile
] + cmd_string
.split()
92 ffmpeg
= subprocess
.Popen(cmd
, bufsize
=(512 * 1024),
93 stdout
=subprocess
.PIPE
)
96 debug('transcoding to tivo model ' + tsn
[:3] + ' using ffmpeg command:')
99 ffmpeg_procs
[inFile
] = {'process': ffmpeg
, 'start': 0, 'end': 0,
100 'last_read': time
.time(), 'blocks': []}
102 transfer_blocks(inFile
, outFile
)
104 def is_resumable(inFile
, offset
):
105 if inFile
in ffmpeg_procs
:
106 proc
= ffmpeg_procs
[inFile
]
107 if proc
['start'] <= offset
< proc
['end']:
111 kill(proc
['process'])
114 def resume_transfer(inFile
, outFile
, offset
):
115 proc
= ffmpeg_procs
[inFile
]
116 offset
-= proc
['start']
118 for block
in proc
['blocks']:
122 block
= block
[offset
:]
123 outFile
.write('%x\r\n' % len(block
))
125 outFile
.write('\r\n')
128 except Exception, msg
:
131 proc
['start'] = proc
['end']
134 transfer_blocks(inFile
, outFile
)
136 def transfer_blocks(inFile
, outFile
):
137 proc
= ffmpeg_procs
[inFile
]
138 blocks
= proc
['blocks']
142 block
= proc
['process'].stdout
.read(BLOCKSIZE
)
143 proc
['last_read'] = time
.time()
144 except Exception, msg
:
147 kill(proc
['process'])
153 except Exception, msg
:
160 proc
['end'] += len(block
)
161 if len(blocks
) > MAXBLOCKS
:
162 proc
['start'] += len(blocks
[0])
166 outFile
.write('%x\r\n' % len(block
))
168 outFile
.write('\r\n')
169 except Exception, msg
:
173 def reap_process(inFile
):
174 if inFile
in ffmpeg_procs
:
175 proc
= ffmpeg_procs
[inFile
]
176 if proc
['last_read'] + TIMEOUT
< time
.time():
177 del ffmpeg_procs
[inFile
]
179 kill(proc
['process'])
181 reaper
= threading
.Timer(TIMEOUT
, reap_process
, (inFile
,))
182 reapers
[inFile
] = reaper
186 del ffmpeg_procs
[inFile
]
187 reapers
[inFile
].cancel()
190 def select_audiocodec(isQuery
, inFile
, tsn
=''):
191 if inFile
[-5:].lower() == '.tivo':
192 return '-acodec copy'
193 vInfo
= video_info(inFile
)
194 codectype
= vInfo
['vCodec']
195 codec
= config
.get_tsn('audio_codec', tsn
)
197 # Default, compatible with all TiVo's
199 if vInfo
['aCodec'] in ('ac3', 'liba52', 'mp2'):
200 aKbps
= vInfo
['aKbps']
203 aKbps
= audio_check(inFile
, tsn
)
206 if aKbps
!= None and int(aKbps
) <= config
.getMaxAudioBR(tsn
):
207 # compatible codec and bitrate, do not reencode audio
209 copy_flag
= config
.get_tsn('copy_ts', tsn
)
211 if ((codec
== 'copy' and codectype
== 'mpeg2video' and not copy_flag
) or
212 (copy_flag
and copy_flag
.lower() == 'false')):
214 return '-acodec ' + codec
+ copyts
216 def select_audiofr(inFile
, tsn
):
217 freq
= '48000' #default
218 vInfo
= video_info(inFile
)
219 if not vInfo
['aFreq'] == None and vInfo
['aFreq'] in ('44100', '48000'):
220 # compatible frequency
221 freq
= vInfo
['aFreq']
222 audio_fr
= config
.get_tsn('audio_fr', tsn
)
227 def select_audioch(tsn
):
228 ch
= config
.get_tsn('audio_ch', tsn
)
233 def select_audiolang(inFile
, tsn
):
234 vInfo
= video_info(inFile
)
235 audio_lang
= config
.get_tsn('audio_lang', tsn
)
236 if audio_lang
!= None and vInfo
['mapVideo'] != None:
237 stream
= vInfo
['mapAudio'][0][0]
239 for lang
in audio_lang
.replace(' ','').lower().split(','):
240 for s
, l
in vInfo
['mapAudio']:
241 if lang
in s
+ l
.replace(' ','').lower():
247 return '-map ' + vInfo
['mapVideo'] + ' -map ' + stream
250 def select_videofps(inFile
, tsn
):
251 vInfo
= video_info(inFile
)
252 fps
= '-r 29.97' # default
253 if config
.isHDtivo(tsn
) and vInfo
['vFps'] in GOOD_MPEG_FPS
:
255 video_fps
= config
.get_tsn('video_fps', tsn
)
256 if video_fps
!= None:
257 fps
= '-r ' + video_fps
260 def select_videocodec(inFile
, tsn
):
261 vInfo
= video_info(inFile
)
262 if tivo_compatible_video(vInfo
, tsn
)[0]:
265 codec
= 'mpeg2video' # default
266 return '-vcodec ' + codec
268 def select_videobr(inFile
, tsn
):
269 return '-b ' + str(select_videostr(inFile
, tsn
) / 1000) + 'k'
271 def select_videostr(inFile
, tsn
):
272 vInfo
= video_info(inFile
)
273 if tivo_compatible_video(vInfo
, tsn
)[0]:
274 video_str
= int(vInfo
['kbps'])
276 video_str
-= int(vInfo
['aKbps'])
279 video_str
= config
.strtod(config
.getVideoBR(tsn
))
280 if config
.isHDtivo(tsn
):
281 if vInfo
['kbps'] != None and config
.getVideoPCT(tsn
) > 0:
282 video_percent
= (int(vInfo
['kbps']) * 10 *
283 config
.getVideoPCT(tsn
))
284 video_str
= max(video_str
, video_percent
)
285 video_str
= int(min(config
.strtod(config
.getMaxVideoBR(tsn
)) * 0.95,
289 def select_audiobr(tsn
):
290 return '-ab ' + config
.getAudioBR(tsn
)
292 def select_maxvideobr(tsn
):
293 return '-maxrate ' + config
.getMaxVideoBR(tsn
)
295 def select_buffsize(tsn
):
296 return '-bufsize ' + config
.getBuffSize(tsn
)
298 def select_ffmpegprams(tsn
):
299 params
= config
.getFFmpegPrams(tsn
)
304 def select_format(tsn
):
306 return '-f %s -' % fmt
308 def select_aspect(inFile
, tsn
= ''):
309 TIVO_WIDTH
= config
.getTivoWidth(tsn
)
310 TIVO_HEIGHT
= config
.getTivoHeight(tsn
)
312 vInfo
= video_info(inFile
)
314 debug('tsn: %s' % tsn
)
316 aspect169
= config
.get169Setting(tsn
)
318 debug('aspect169: %s' % aspect169
)
320 optres
= config
.getOptres(tsn
)
322 debug('optres: %s' % optres
)
325 optHeight
= config
.nearestTivoHeight(vInfo
['vHeight'])
326 optWidth
= config
.nearestTivoWidth(vInfo
['vWidth'])
327 if optHeight
< TIVO_HEIGHT
:
328 TIVO_HEIGHT
= optHeight
329 if optWidth
< TIVO_WIDTH
:
330 TIVO_WIDTH
= optWidth
332 d
= gcd(vInfo
['vHeight'], vInfo
['vWidth'])
333 ratio
= vInfo
['vWidth'] * 100 / vInfo
['vHeight']
334 rheight
, rwidth
= vInfo
['vHeight'] / d
, vInfo
['vWidth'] / d
336 debug(('File=%s vCodec=%s vWidth=%s vHeight=%s vFps=%s ' +
337 'millisecs=%s ratio=%s rheight=%s rwidth=%s ' +
338 'TIVO_HEIGHT=%s TIVO_WIDTH=%s') % (inFile
,
339 vInfo
['vCodec'], vInfo
['vWidth'], vInfo
['vHeight'],
340 vInfo
['vFps'], vInfo
['millisecs'], ratio
, rheight
,
341 rwidth
, TIVO_HEIGHT
, TIVO_WIDTH
))
343 multiplier16by9
= (16.0 * TIVO_HEIGHT
) / (9.0 * TIVO_WIDTH
)
344 multiplier4by3
= (4.0 * TIVO_HEIGHT
) / (3.0 * TIVO_WIDTH
)
346 if config
.isHDtivo(tsn
) and not optres
:
347 if config
.getPixelAR(0) or vInfo
['par']:
348 if vInfo
['par2'] == None:
350 npar
= float(vInfo
['par'])
352 npar
= config
.getPixelAR(1)
356 # adjust for pixel aspect ratio, if set, because TiVo
357 # expects square pixels
360 return ['-s', str(vInfo
['vWidth']) + 'x' +
361 str(int(math
.ceil(vInfo
['vHeight'] / npar
)))]
363 # FFMPEG expects width to be a multiple of two
365 return ['-s', str(int(math
.ceil(vInfo
['vWidth'] * npar
/
366 2.0) * 2)) + 'x' + str(vInfo
['vHeight'])]
368 if vInfo
['vHeight'] <= TIVO_HEIGHT
:
369 # pass all resolutions to S3, except heights greater than
372 # else, resize video.
374 if (rwidth
, rheight
) in [(1, 1)] and vInfo
['par1'] == '8:9':
375 debug('File + PAR is within 4:3.')
376 return ['-aspect', '4:3', '-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
378 elif ((rwidth
, rheight
) in [(4, 3), (10, 11), (15, 11), (59, 54),
379 (59, 72), (59, 36), (59, 54)] or
380 vInfo
['dar1'] == '4:3'):
381 debug('File is within 4:3 list.')
382 return ['-aspect', '4:3', '-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
384 elif (((rwidth
, rheight
) in [(16, 9), (20, 11), (40, 33), (118, 81),
385 (59, 27)] or vInfo
['dar1'] == '16:9')
386 and (aspect169
or config
.get169Letterbox(tsn
))):
387 debug('File is within 16:9 list and 16:9 allowed.')
389 if config
.get169Blacklist(tsn
) or (aspect169
and
390 config
.get169Letterbox(tsn
)):
391 return ['-aspect', '4:3', '-s', '%sx%s' %
392 (TIVO_WIDTH
, TIVO_HEIGHT
)]
394 return ['-aspect', '16:9', '-s', '%sx%s' %
395 (TIVO_WIDTH
, TIVO_HEIGHT
)]
399 # If video is wider than 4:3 add top and bottom padding
401 if ratio
> 133: # Might be 16:9 file, or just need padding on
404 if aspect169
and ratio
> 135: # If file would fall in 4:3
405 # assume it is supposed to be 4:3
407 if ratio
> 177: # too short needs padding top and bottom
408 endHeight
= int(((TIVO_WIDTH
* vInfo
['vHeight']) /
409 vInfo
['vWidth']) * multiplier16by9
)
410 settings
.append('-aspect')
411 if (config
.get169Blacklist(tsn
) or
412 config
.get169Letterbox(tsn
)):
413 settings
.append('4:3')
415 settings
.append('16:9')
418 if endHeight
< TIVO_HEIGHT
* 0.99:
419 topPadding
= (TIVO_HEIGHT
- endHeight
) / 2
422 bottomPadding
= (TIVO_HEIGHT
- endHeight
) - topPadding
423 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, endHeight
),
424 '-padtop', str(topPadding
),
425 '-padbottom', str(bottomPadding
)]
426 else: # if only very small amount of padding
427 # needed, then just stretch it
428 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
430 debug(('16:9 aspect allowed, file is wider ' +
431 'than 16:9 padding top and bottom\n%s') %
434 else: # too skinny needs padding on left and right.
435 endWidth
= int((TIVO_HEIGHT
* vInfo
['vWidth']) /
436 (vInfo
['vHeight'] * multiplier16by9
))
437 settings
.append('-aspect')
438 if (config
.get169Blacklist(tsn
) or
439 config
.get169Letterbox(tsn
)):
440 settings
.append('4:3')
442 settings
.append('16:9')
445 if endWidth
< (TIVO_WIDTH
- 10):
446 leftPadding
= (TIVO_WIDTH
- endWidth
) / 2
449 rightPadding
= (TIVO_WIDTH
- endWidth
) - leftPadding
450 settings
+= ['-s', '%sx%s' % (endWidth
, TIVO_HEIGHT
),
451 '-padleft', str(leftPadding
),
452 '-padright', str(rightPadding
)]
453 else: # if only very small amount of padding needed,
454 # then just stretch it
455 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
456 debug(('16:9 aspect allowed, file is narrower ' +
457 'than 16:9 padding left and right\n%s') %
459 else: # this is a 4:3 file or 16:9 output not allowed
460 multiplier
= multiplier4by3
461 settings
.append('-aspect')
462 if ratio
> 135 and config
.get169Letterbox(tsn
):
463 settings
.append('16:9')
464 multiplier
= multiplier16by9
466 settings
.append('4:3')
467 endHeight
= int(((TIVO_WIDTH
* vInfo
['vHeight']) /
468 vInfo
['vWidth']) * multiplier
)
471 if endHeight
< TIVO_HEIGHT
* 0.99:
472 topPadding
= (TIVO_HEIGHT
- endHeight
) / 2
475 bottomPadding
= (TIVO_HEIGHT
- endHeight
) - topPadding
476 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, endHeight
),
477 '-padtop', str(topPadding
),
478 '-padbottom', str(bottomPadding
)]
479 else: # if only very small amount of padding needed,
480 # then just stretch it
481 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
482 debug(('File is wider than 4:3 padding ' +
483 'top and bottom\n%s') % ' '.join(settings
))
487 # If video is taller than 4:3 add left and right padding, this
488 # is rare. All of these files will always be sent in an aspect
489 # ratio of 4:3 since they are so narrow.
492 endWidth
= int((TIVO_HEIGHT
* vInfo
['vWidth']) /
493 (vInfo
['vHeight'] * multiplier4by3
))
494 settings
+= ['-aspect', '4:3']
497 if endWidth
< (TIVO_WIDTH
* 0.99):
498 leftPadding
= (TIVO_WIDTH
- endWidth
) / 2
501 rightPadding
= (TIVO_WIDTH
- endWidth
) - leftPadding
502 settings
+= ['-s', '%sx%s' % (endWidth
, TIVO_HEIGHT
),
503 '-padleft', str(leftPadding
),
504 '-padright', str(rightPadding
)]
505 else: # if only very small amount of padding needed, then
507 settings
+= ['-s', '%sx%s' % (TIVO_WIDTH
, TIVO_HEIGHT
)]
509 debug('File is taller than 4:3 padding left and right\n%s'
510 % ' '.join(settings
))
514 def tivo_compatible_video(vInfo
, tsn
, mime
=''):
517 codec
= vInfo
['vCodec']
518 if mime
== 'video/mp4':
520 message
= (False, 'vCodec %s not compatible' % codec
)
524 if mime
== 'video/bif':
526 message
= (False, 'vCodec %s not compatible' % codec
)
530 if codec
not in ('mpeg2video', 'mpeg1video'):
531 message
= (False, 'vCodec %s not compatible' % codec
)
534 if vInfo
['kbps'] != None:
535 abit
= max('0', vInfo
['aKbps'])
536 if (int(vInfo
['kbps']) - int(abit
) >
537 config
.strtod(config
.getMaxVideoBR(tsn
)) / 1000):
538 message
= (False, '%s kbps exceeds max video bitrate' %
542 message
= (False, '%s kbps not supported' % vInfo
['kbps'])
545 if config
.isHDtivo(tsn
):
546 if vInfo
['par2'] != 1.0:
547 if config
.getPixelAR(0):
548 if vInfo
['par2'] != None or config
.getPixelAR(1) != 1.0:
549 message
= (False, '%s not correct PAR' % vInfo
['par2'])
551 # HD Tivo detected, skipping remaining tests.
554 if not vInfo
['vFps'] in ['29.97', '59.94']:
555 message
= (False, '%s vFps, should be 29.97' % vInfo
['vFps'])
558 if ((config
.get169Blacklist(tsn
) and not config
.get169Setting(tsn
))
559 or (config
.get169Letterbox(tsn
) and config
.get169Setting(tsn
))):
560 if vInfo
['dar1'] and vInfo
['dar1'] not in ('4:3', '8:9', '880:657'):
561 message
= (False, ('DAR %s not supported ' +
562 'by BLACKLIST_169 tivos') % vInfo
['dar1'])
565 mode
= (vInfo
['vWidth'], vInfo
['vHeight'])
566 if mode
not in [(720, 480), (704, 480), (544, 480),
567 (528, 480), (480, 480), (352, 480), (352, 240)]:
568 message
= (False, '%s x %s not in supported modes' % mode
)
573 def tivo_compatible_audio(vInfo
, inFile
, tsn
, mime
=''):
576 codec
= vInfo
['aCodec']
577 if mime
== 'video/mp4':
578 if codec
not in ('mpeg4aac', 'libfaad', 'mp4a', 'aac',
580 message
= (False, 'aCodec %s not compatible' % codec
)
584 if mime
== 'video/bif':
586 message
= (False, 'aCodec %s not compatible' % codec
)
590 if inFile
[-5:].lower() == '.tivo':
593 if codec
not in ('ac3', 'liba52', 'mp2'):
594 message
= (False, 'aCodec %s not compatible' % codec
)
597 if (not vInfo
['aKbps'] or
598 int(vInfo
['aKbps']) > config
.getMaxAudioBR(tsn
)):
599 message
= (False, '%s kbps exceeds max audio bitrate' %
603 audio_lang
= config
.get_tsn('audio_lang', tsn
)
605 if vInfo
['mapAudio'][0][0] != select_audiolang(inFile
, tsn
)[-3:]:
606 message
= (False, '%s preferred audio track exists' %
612 def tivo_compatible_container(vInfo
, mime
=''):
614 container
= vInfo
['container']
615 if ((mime
== 'video/mp4' and container
!= 'mov') or
616 (mime
== 'video/bif' and container
!= 'asf') or
617 (mime
in ['video/mpeg', ''] and
618 (container
!= 'mpeg' or vInfo
['vCodec'] == 'mpeg1video'))):
619 message
= (False, 'container %s not compatible' % container
)
623 def tivo_compatible(inFile
, tsn
='', mime
=''):
624 vInfo
= video_info(inFile
)
626 message
= (True, 'all compatible')
627 if not config
.get_bin('ffmpeg'):
628 if mime
not in ['video/x-tivo-mpeg', 'video/mpeg', '']:
629 message
= (False, 'no ffmpeg')
633 vmessage
= tivo_compatible_video(vInfo
, tsn
, mime
)
638 amessage
= tivo_compatible_audio(vInfo
, inFile
, tsn
, mime
)
643 cmessage
= tivo_compatible_container(vInfo
, mime
)
649 debug('TRANSCODE=%s, %s, %s' % (['YES', 'NO'][message
[0]],
653 def video_info(inFile
, cache
=True):
655 mtime
= os
.stat(inFile
).st_mtime
657 if inFile
in info_cache
and info_cache
[inFile
][0] == mtime
:
658 debug('CACHE HIT! %s' % inFile
)
659 return info_cache
[inFile
][1]
661 vInfo
['Supported'] = True
663 ffmpeg_path
= config
.get_bin('ffmpeg')
665 if os
.path
.splitext(inFile
)[1].lower() not in ['.mpg', '.mpeg',
667 vInfo
['Supported'] = False
668 vInfo
.update({'millisecs': 0, 'vWidth': 704, 'vHeight': 480})
670 info_cache
[inFile
] = (mtime
, vInfo
)
673 cmd
= [ffmpeg_path
, '-i', inFile
]
674 # Windows and other OS buffer 4096 and ffmpeg can output more than that.
675 err_tmp
= tempfile
.TemporaryFile()
676 ffmpeg
= subprocess
.Popen(cmd
, stderr
=err_tmp
, stdout
=subprocess
.PIPE
,
677 stdin
=subprocess
.PIPE
)
679 # wait configured # of seconds: if ffmpeg is not back give up
680 wait
= config
.getFFmpegWait()
682 'starting ffmpeg, will wait %s seconds for it to complete' % wait
)
683 for i
in xrange(wait
* 20):
685 if not ffmpeg
.poll() == None:
688 if ffmpeg
.poll() == None:
690 vInfo
['Supported'] = False
692 info_cache
[inFile
] = (mtime
, vInfo
)
696 output
= err_tmp
.read()
698 debug('ffmpeg output=%s' % output
)
700 rezre
= re
.compile(r
'Input #0, ([^,]+),')
701 x
= rezre
.search(output
)
703 vInfo
['container'] = x
.group(1)
705 vInfo
['container'] = ''
706 vInfo
['Supported'] = False
707 debug('failed at container')
709 rezre
= re
.compile(r
'.*Video: ([^,]+),.*')
710 x
= rezre
.search(output
)
712 vInfo
['vCodec'] = x
.group(1)
715 vInfo
['Supported'] = False
716 debug('failed at vCodec')
718 rezre
= re
.compile(r
'.*Video: .+, (\d+)x(\d+)[, ].*')
719 x
= rezre
.search(output
)
721 vInfo
['vWidth'] = int(x
.group(1))
722 vInfo
['vHeight'] = int(x
.group(2))
725 vInfo
['vHeight'] = ''
726 vInfo
['Supported'] = False
727 debug('failed at vWidth/vHeight')
729 rezre
= re
.compile(r
'.*Video: .+, (.+) (?:fps|tb\(r\)|tbr).*')
730 x
= rezre
.search(output
)
732 vInfo
['vFps'] = x
.group(1)
734 # Allow override only if it is mpeg2 and frame rate was doubled
737 if vInfo
['vCodec'] == 'mpeg2video' and vInfo
['vFps'] != '29.97':
738 # First look for the build 7215 version
739 rezre
= re
.compile(r
'.*film source: 29.97.*')
740 x
= rezre
.search(output
.lower())
742 debug('film source: 29.97 setting vFps to 29.97')
743 vInfo
['vFps'] = '29.97'
746 rezre
= re
.compile(r
'.*frame rate differs from container ' +
747 r
'frame rate: 29.97.*')
748 debug('Bug in VideoReDo')
749 x
= rezre
.search(output
.lower())
751 vInfo
['vFps'] = '29.97'
754 vInfo
['Supported'] = False
755 debug('failed at vFps')
757 durre
= re
.compile(r
'.*Duration: ([0-9]+):([0-9]+):([0-9]+)\.([0-9]+),')
758 d
= durre
.search(output
)
761 vInfo
['millisecs'] = ((int(d
.group(1)) * 3600 +
762 int(d
.group(2)) * 60 +
763 int(d
.group(3))) * 1000 +
764 int(d
.group(4)) * (10 ** (3 - len(d
.group(4)))))
766 vInfo
['millisecs'] = 0
768 # get bitrate of source for tivo compatibility test.
769 rezre
= re
.compile(r
'.*bitrate: (.+) (?:kb/s).*')
770 x
= rezre
.search(output
)
772 vInfo
['kbps'] = x
.group(1)
774 # Fallback method of getting video bitrate
775 # Sample line: Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p,
776 # 720x480 [PAR 32:27 DAR 16:9], 9800 kb/s, 59.94 tb(r)
777 rezre
= re
.compile(r
'.*Stream #0\.0\[.*\]: Video: mpeg2video, ' +
778 r
'\S+, \S+ \[.*\], (\d+) (?:kb/s).*')
779 x
= rezre
.search(output
)
781 vInfo
['kbps'] = x
.group(1)
784 debug('failed at kbps')
786 # get audio bitrate of source for tivo compatibility test.
787 rezre
= re
.compile(r
'.*Audio: .+, (.+) (?:kb/s).*')
788 x
= rezre
.search(output
)
790 vInfo
['aKbps'] = x
.group(1)
792 vInfo
['aKbps'] = None
793 debug('failed at aKbps')
795 # get audio codec of source for tivo compatibility test.
796 rezre
= re
.compile(r
'.*Audio: ([^,]+),.*')
797 x
= rezre
.search(output
)
799 vInfo
['aCodec'] = x
.group(1)
801 vInfo
['aCodec'] = None
802 debug('failed at aCodec')
804 # get audio frequency of source for tivo compatibility test.
805 rezre
= re
.compile(r
'.*Audio: .+, (.+) (?:Hz).*')
806 x
= rezre
.search(output
)
808 vInfo
['aFreq'] = x
.group(1)
810 vInfo
['aFreq'] = None
811 debug('failed at aFreq')
814 rezre
= re
.compile(r
'.*Video: .+PAR ([0-9]+):([0-9]+) DAR [0-9:]+.*')
815 x
= rezre
.search(output
)
816 if x
and x
.group(1) != "0" and x
.group(2) != "0":
817 vInfo
['par1'] = x
.group(1) + ':' + x
.group(2)
818 vInfo
['par2'] = float(x
.group(1)) / float(x
.group(2))
820 vInfo
['par1'], vInfo
['par2'] = None, None
823 rezre
= re
.compile(r
'.*Video: .+DAR ([0-9]+):([0-9]+).*')
824 x
= rezre
.search(output
)
825 if x
and x
.group(1) != "0" and x
.group(2) != "0":
826 vInfo
['dar1'] = x
.group(1) + ':' + x
.group(2)
830 # get Video Stream mapping.
831 rezre
= re
.compile(r
'([0-9]+\.[0-9]+).*: Video:.*')
832 x
= rezre
.search(output
)
834 vInfo
['mapVideo'] = x
.group(1)
836 vInfo
['mapVideo'] = None
837 debug('failed at mapVideo')
839 # get Audio Stream mapping.
840 rezre
= re
.compile(r
'([0-9]+\.[0-9]+)(.*): Audio:.*')
841 x
= rezre
.search(output
)
844 for x
in rezre
.finditer(output
):
845 amap
.append(x
.groups())
847 amap
.append(('', ''))
848 debug('failed at mapAudio')
849 vInfo
['mapAudio'] = amap
853 data
= metadata
.from_text(inFile
)
855 if key
.startswith('Override_'):
856 vInfo
['Supported'] = True
857 if key
.startswith('Override_mapAudio'):
858 audiomap
= dict(vInfo
['mapAudio'])
859 stream
= key
.replace('Override_mapAudio', '').strip()
860 if stream
in audiomap
:
861 newaudiomap
= (stream
, data
[key
])
862 audiomap
.update([newaudiomap
])
863 vInfo
['mapAudio'] = sorted(audiomap
.items(),
864 key
=lambda (k
,v
): (k
,v
))
865 elif key
.startswith('Override_millisecs'):
866 vInfo
[key
.replace('Override_', '')] = int(data
[key
])
868 vInfo
[key
.replace('Override_', '')] = data
[key
]
871 info_cache
[inFile
] = (mtime
, vInfo
)
872 debug("; ".join(["%s=%s" % (k
, v
) for k
, v
in vInfo
.items()]))
875 def audio_check(inFile
, tsn
):
876 cmd_string
= ('-y -vcodec mpeg2video -r 29.97 -b 1000k -acodec copy ' +
877 select_audiolang(inFile
, tsn
) + ' -t 00:00:01 -f vob -')
878 cmd
= [config
.get_bin('ffmpeg'), '-i', inFile
] + cmd_string
.split()
879 ffmpeg
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
880 fd
, testname
= tempfile
.mkstemp()
881 testfile
= os
.fdopen(fd
, 'wb')
883 shutil
.copyfileobj(ffmpeg
.stdout
, testfile
)
890 aKbps
= video_info(testname
, False)['aKbps']
894 def supported_format(inFile
):
895 if video_info(inFile
)['Supported']:
898 debug('FALSE, file not supported %s' % inFile
)
902 debug('killing pid=%s' % str(popen
.pid
))
908 debug('sending SIGTERM to pid: %s' % popen
.pid
)
909 os
.kill(popen
.pid
, signal
.SIGTERM
)
911 if popen
.poll() is not None:
912 debug('process %s has exited' % popen
.pid
)
915 while popen
.poll() is None:
916 debug('sending SIGKILL to pid: %s' % popen
.pid
)
917 os
.kill(popen
.pid
, signal
.SIGKILL
)
922 handle
= ctypes
.windll
.kernel32
.OpenProcess(1, False, pid
)
923 ctypes
.windll
.kernel32
.TerminateProcess(handle
, -1)
924 ctypes
.windll
.kernel32
.CloseHandle(handle
)