def testInputSkipStreams(self): """ Проверка корректности наименования входных потоков в фильтрах.""" ff = FFMPEG() ff < SourceFile('/tmp/input.jpg', audio_streams=0) ff < SourceFile('/tmp/input.mp4') fc = ff.init_filter_complex() overlay = filters.Overlay(0, 0) fc.video | filters.Scale(640, 360) | overlay fc.video | filters.Scale(1280, 720) | overlay fc.audio | filters.Volume(-20) | fc.get_audio_dest(0) overlay | fc.get_video_dest(0) cv0 = VideoCodec(vcodec='libx264', vbitrate='700000', size='640x360') ca0 = AudioCodec(acodec='aac', abitrate='128000') out0 = SimpleMuxer('flv')('/tmp/out.flv') ff.add_output(out0, cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.jpg', '-i', '/tmp/input.mp4', '-filter_complex', ( '[0:v]scale=640x360[v:scale0];' '[v:scale0][v:overlay0]overlay=x=0:y=0[vout0];' '[1:v]scale=1280x720[v:overlay0];' '[1:a]volume=-20.00[aout0]'), '-map', '[vout0]', '-c:v', 'libx264', '-b:v', '700000', '-map', '[aout0]', '-c:a', 'aac', '-b:a', '128000', '-f', 'flv', '/tmp/out.flv' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testCodecCopy(self): """ Проверяется корректность использования vcodec=copy совместно с фильтрами для аудио.""" ff = FFMPEG(inputfile=SourceFile('/tmp/input.mp4')) fc = ff.init_filter_complex() fc.audio | filters.Volume(20) | fc.get_audio_dest(0) cv0 = VideoCodec(vcodec='copy') ca0 = AudioCodec(acodec='aac', abitrate='128000') out0 = SimpleMuxer('flv')('/tmp/out.flv') ff.add_output(out0, cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-filter_complex', '[0:a]volume=20.00[aout0]', '-map', '0:v', '-c:v', 'copy', '-map', '[aout0]', '-c:a', 'aac', '-b:a', '128000', '-f', 'flv', '/tmp/out.flv' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testLavfiSources(self): vsrc = LavfiSource('testsrc', VIDEO, duration=5.3, rate=10) asrc = LavfiSource('sine', AUDIO, d=5, b=4) ff = FFMPEG(inputfile=[vsrc, asrc]) ff.add_output(SimpleMuxer('null')('/dev/null')) expected = [ 'ffmpeg', '-f', 'lavfi', '-i', 'testsrc=duration=5.3:rate=10', '-f', 'lavfi', '-i', 'sine=b=4:d=5', '-f', 'null', '/dev/null' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testFFMPEG(self): """ Проверка работоспособности и демонстрация возможностей.""" ff = FFMPEG() ff < SourceFile('/tmp/input.mp4') fc = ff.init_filter_complex() asplit = fc.audio | filters.AudioSplit() fc.video | filters.Scale(640, 360) | fc.get_video_dest(0) asplit.connect_dest(fc.get_audio_dest(0)) asplit.connect_dest(fc.get_audio_dest(1)) out0 = SimpleMuxer('flv')('/tmp/out.flv') out1 = SimpleMuxer('mp3')('/tmp/out.mp3') cv0 = VideoCodec(vcodec='libx264', vbitrate='700000', size='640x360') ca0 = AudioCodec(acodec='aac', abitrate='128000') ca1 = AudioCodec(acodec='libmp3lame', abitrate='394000') ff.add_output(out0, cv0, ca0) ff.add_output(out1, ca1) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-filter_complex', '[0:v]scale=640x360[vout0];[0:a]asplit[aout0][aout1]', '-map', '[vout0]', '-c:v', 'libx264', '-b:v', '700000', '-map', '[aout0]', '-c:a', 'aac', '-b:a', '128000', '-f', 'flv', '/tmp/out.flv', '-map', '[aout1]', '-c:a', 'libmp3lame', '-b:a', '394000', '-f', 'mp3', '/tmp/out.mp3' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testCodecCopyWithSplit(self): """ Проверяется корректность использования vcodec=copy совместно фильтрами для видео.""" ff = FFMPEG(inputfile=SourceFile('/tmp/input.mp4')) fc = ff.init_filter_complex() fc.video | filters.Scale(640, 360) | fc.get_video_dest(0) cv0 = VideoCodec(map='0:v', vcodec='copy') ca0 = AudioCodec(map='0:a', acodec='copy') out0 = SimpleMuxer('flv')('/tmp/copy.flv') ff.add_output(out0, cv0, ca0) cv1 = VideoCodec(vcodec='libx264') ca1 = AudioCodec(acodec='aac') out1 = SimpleMuxer('flv')('/tmp/out.flv') ff.add_output(out1, cv1, ca1) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-filter_complex', '[0:v]scale=640x360[vout0]', '-map', '0:v', '-c:v', 'copy', '-map', '0:a', '-c:a', 'copy', '-f', 'flv', '/tmp/copy.flv', '-map', '[vout0]', '-c:v', 'libx264', '-map', '0:a', '-c:a', 'aac', '-f', 'flv', '/tmp/out.flv' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testBypassWithoutFilterComplex(self): """ Проверка работы в режиме bypass, когда вообще нет filter_complex.""" ff = FFMPEG(inputfile=SourceFile('/tmp/input.mp4')) cv0 = VideoCodec(vcodec='libx264', vbitrate='700000', size='640x360') ca0 = AudioCodec(acodec='aac', abitrate='128000') out0 = SimpleMuxer('flv')('/tmp/out.flv') ff.add_output(out0, cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-map', '0:v', '-c:v', 'libx264', '-b:v', '700000', '-map', '0:a', '-c:a', 'aac', '-b:a', '128000', '-f', 'flv', '/tmp/out.flv' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testHLSMuxer(self): ff = FFMPEG(inputfile=SourceFile('/tmp/input.mp4')) cv0 = VideoCodec(vcodec='libx264') ca0 = AudioCodec(acodec='aac') out0 = HLSMuxer('http://ya.ru/out.m3u8', segment_size=4, manifest_size=5, method='PUT') ff.add_output(out0, cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-map', '0:v', '-c:v', 'libx264', '-map', '0:a', '-c:a', 'aac', '-f', 'hls', '-method', 'PUT', '-hls_time', '4', '-hls_list_size', '5', 'http://ya.ru/out.m3u8' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def run(self, ff: FFMPEG) -> None: """ Starts ffmpeg process and captures errors from it's logs""" return_code, error = ff.run() if error or return_code != 0: # Check return code and error messages error = error or f"invalid ffmpeg return code {return_code}" raise TranscodeError(error)
def testTeeMuxer(self): ff = FFMPEG(inputfile=SourceFile('/tmp/input.mp4')) cv0 = VideoCodec(vcodec='libx264') ca0 = AudioCodec(acodec='aac') out0 = HLSMuxer('http://ya.ru/1.m3u8', segment_size=2) out1 = HLSMuxer('http://ya.ru/2.m3u8', manifest_size=5) ff.add_output(TeeMuxer(out0, out1), cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-map', '0:v', '-c:v', 'libx264', '-map', '0:a', '-c:a', 'aac', '-f', 'tee', '[f=hls:hls_time=2]http://ya.ru/1.m3u8|' '[f=hls:hls_list_size=5]http://ya.ru/2.m3u8' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def transcode(self) -> None: """ Transcodes video * checks source mediainfo * runs `ffmpeg` * validates result """ # Get source mediainfo to use in validation source_media_info = self.get_media_info(self.source) # Common ffmpeg flags ff = FFMPEG(overwrite=True, loglevel='repeat+level+info') # Init source file ff < SourceFile(self.source) # Scaling fc = ff.init_filter_complex() fc.video | Scale(**TRANSCODING_OPTIONS[SCALE]) | fc.get_video_dest(0) # set group of pixels length to segment size gop = math.floor(source_media_info[VIDEO_FRAME_RATE] * GOP_DURATION) # preserve source audio sampling rate arate = source_media_info[AUDIO_SAMPLING_RATE] # preserve original video FPS vrate = source_media_info[VIDEO_FRAME_RATE] # codecs, muxer and output path cv0 = VideoCodec(gop=gop, vrate=vrate, **TRANSCODING_OPTIONS[VIDEO_CODEC]) ca0 = AudioCodec(arate=arate, **TRANSCODING_OPTIONS[AUDIO_CODEC]) out0 = Muxer(self.destination, format='mp4') # Add output file to ffmpeg ff.add_output(out0, cv0, ca0) # Run ffmpeg self.run(ff) # Get result mediainfo dest_media_info = self.get_media_info(self.destination) # Validate ffmpeg result self.validate(source_media_info, dest_media_info)
def testReuseInputFiles(self): """ Проверяет что входные файлы можно использовать несколько раз.""" ff = FFMPEG(inputfile=SourceFile('/tmp/input.mp4')) cv0 = VideoCodec(map='0:v', vcodec='copy') ca0 = AudioCodec(map='0:a', acodec='copy') out0 = SimpleMuxer('flv')('/tmp/out0.flv') ff.add_output(out0, cv0, ca0) cv1 = VideoCodec(map='0:v', vcodec='copy') ca1 = AudioCodec(map='0:a', acodec='copy') out1 = SimpleMuxer('flv')('/tmp/out1.flv') ff.add_output(out1, cv1, ca1) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-map', '0:v', '-c:v', 'copy', '-map', '0:a', '-c:a', 'copy', '-f', 'flv', '/tmp/out0.flv', '-map', '0:v', '-c:v', 'copy', '-map', '0:a', '-c:a', 'copy', '-f', 'flv', '/tmp/out1.flv', ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testBypass(self): """ Проверка работы в режиме bypass, когда аудиопоток не проходит ни через один фильтр.""" ff = FFMPEG(inputfile='/tmp/input.mp4') fc = FilterComplex(audio_inputs=0, audio_outputs=0) fc.video | filters.Scale(640, 360) | fc.get_video_dest(0) ff.filter_complex = fc cv0 = VideoCodec(vcodec='libx264', vbitrate='700000', size='640x360') ca0 = AudioCodec(acodec='aac', abitrate='128000') out0 = Muxer('flv', '/tmp/out.flv') ff.add_output(out0, cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-filter_complex', '[0:v]scale=640x360[vout0]', '-f', 'flv', '-map', '[vout0]', '-c:v', 'libx264', '-b:v', '700000', '-map', '0:a', '-c:a', 'aac', '-b:a', '128000', '/tmp/out.flv' ] self.assertEqual(ff.get_args(), ensure_binary(expected)) print(ff.get_cmd())
def testInputSkipStreams(self): """ Проверка корректности наименования входных потоков в фильтрах.""" ff = FFMPEG() ff < SourceFile('/tmp/input.jpg', audio_streams=0) ff < SourceFile('/tmp/input.mp4') fc = ff.init_filter_complex() overlay = filters.Overlay(0, 0) fc.video | filters.Scale(640, 360) | overlay fc.video | filters.Scale(1280, 720) | overlay fc.audio | filters.Volume(-20) | fc.get_audio_dest(0) overlay | fc.get_video_dest(0) cv0 = VideoCodec(vcodec='libx264', vbitrate='700000', size='640x360') ca0 = AudioCodec(acodec='aac', abitrate='128000') out0 = SimpleMuxer('flv')('/tmp/out.flv') ff.add_output(out0, cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.jpg', '-i', '/tmp/input.mp4', '-filter_complex', ('[0:v]scale=640x360[v:scale0];' '[v:scale0][v:overlay0]overlay=x=0:y=0[vout0];' '[1:v]scale=1280x720[v:overlay0];' '[1:a]volume=-20.00[aout0]'), '-map', '[vout0]', '-c:v', 'libx264', '-b:v', '700000', '-map', '[aout0]', '-c:a', 'aac', '-b:a', '128000', '-f', 'flv', '/tmp/out.flv' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def testBypassWithFilterComplex(self): """ Проверка работы в режиме bypass, когда аудиопоток не проходит ни через один фильтр.""" ff = FFMPEG(inputfile=SourceFile('/tmp/input.mp4')) fc = ff.init_filter_complex() fc.video | filters.Scale(640, 360) | fc.get_video_dest(0) cv0 = VideoCodec(vcodec='libx264', vbitrate='700000', size='640x360') ca0 = AudioCodec(acodec='aac', abitrate='128000') out0 = SimpleMuxer('flv')('/tmp/out.flv') ff.add_output(out0, cv0, ca0) expected = [ 'ffmpeg', '-i', '/tmp/input.mp4', '-filter_complex', '[0:v]scale=640x360[vout0]', '-map', '[vout0]', '-c:v', 'libx264', '-b:v', '700000', '-map', '0:a', '-c:a', 'aac', '-b:a', '128000', '-f', 'flv', '/tmp/out.flv' ] self.assertEqual(ff.get_args(), ensure_binary(expected))
def __call__(self, **kwargs: Any) -> FFMPEG: return FFMPEG(**kwargs)