def test__merge_outputs(): in_ = ffmpeg.input('in.mp4') out1 = in_.output('out1.mp4') out2 = in_.output('out2.mp4') assert ffmpeg.merge_outputs(out1, out2).get_args() == [ '-i', 'in.mp4', 'out1.mp4', 'out2.mp4', ] assert ffmpeg.get_args([out1, out2]) == ['-i', 'in.mp4', 'out2.mp4', 'out1.mp4']
def create_popen(self) -> Popen[bytes]: argument = [ sys.executable, str(Path(__file__).resolve().parent / "windows.py"), str(self.time_to_force_termination), *ffmpeg.get_args(self.stream_spec), ] self.logger.debug(argument) # Reason: This method is instead of ffmpeg.run_async(). pylint: disable=consider-using-with return Popen(argument, creationflags=CREATE_NEW_PROCESS_GROUP, stdout=PIPE, stderr=PIPE)
def test_multi_passthrough(): out1 = ffmpeg.input('in1.mp4').output('out1.mp4') out2 = ffmpeg.input('in2.mp4').output('out2.mp4') out = ffmpeg.merge_outputs(out1, out2) assert ffmpeg.get_args(out) == [ '-i', 'in1.mp4', '-i', 'in2.mp4', 'out1.mp4', '-map', '[1]', # FIXME: this should not be here (see #23) 'out2.mp4' ] assert ffmpeg.get_args([out1, out2]) == [ '-i', 'in2.mp4', '-i', 'in1.mp4', 'out2.mp4', '-map', '[1]', # FIXME: this should not be here (see #23) 'out1.mp4' ]
def test_multi_passthrough(): out1 = ffmpeg.input('in1.mp4').output('out1.mp4') out2 = ffmpeg.input('in2.mp4').output('out2.mp4') out = ffmpeg.merge_outputs(out1, out2) assert ffmpeg.get_args(out) == [ '-i', 'in1.mp4', '-i', 'in2.mp4', 'out1.mp4', '-map', '1', 'out2.mp4', ] assert ffmpeg.get_args([out1, out2]) == [ '-i', 'in2.mp4', '-i', 'in1.mp4', 'out2.mp4', '-map', '1', 'out1.mp4', ]
def test_get_args_complex_filter(): out = _get_complex_filter_example() args = ffmpeg.get_args(out) assert args == [ '-i', TEST_INPUT_FILE, '-i', TEST_OVERLAY_FILE, '-filter_complex', '[0]trim=start_frame=10:end_frame=20,setpts=PTS-STARTPTS[v0];' \ '[0]trim=start_frame=30:end_frame=40,setpts=PTS-STARTPTS[v1];' \ '[v0][v1]concat=n=2[v2];' \ '[1]hflip[v3];' \ '[v2][v3]overlay=eof_action=repeat[v4];' \ '[v4]drawbox=50:50:120:120:red:t=5[v5]', '-map', '[v5]', '/Users/karlk/src/ffmpeg_wrapper/ffmpeg/tests/sample_data/dummy2.mp4', '-y' ]
def test_get_args_complex_filter(): out = _get_complex_filter_example() args = ffmpeg.get_args(out) assert args == ['-i', TEST_INPUT_FILE1, '-i', TEST_OVERLAY_FILE, '-filter_complex', '[0]vflip[s0];' \ '[s0]split=2[s1][s2];' \ '[s1]trim=end_frame=20:start_frame=10[s3];' \ '[s2]trim=end_frame=40:start_frame=30[s4];' \ '[s3][s4]concat=n=2[s5];' \ '[1]hflip[s6];' \ '[s5][s6]overlay=eof_action=repeat[s7];' \ '[s7]drawbox=50:50:120:120:red:t=5[s8]', '-map', '[s8]', TEST_OUTPUT_FILE1, '-y' ]
def start(self): Logger.LOGGER.log(Logger.TYPE_INFO, 'Starting Server, output to: {}'.format(self.output)) in1 = ffmpeg.input('pipe:') v1 = ffmpeg.drawtext(in1['v'], '%{localtime:%R}', x=c.SERV_DRAWTEXT_X, y=c.SERV_DRAWTEXT_Y, escape_text=False, shadowcolor=c.SERV_DRAWTEXT_SHADOW_COLOR, shadowx=c.SERV_DRAWTEXT_SHADOW_X, shadowy=c.SERV_DRAWTEXT_SHADOW_Y, fontsize=c.SERV_DRAWTEXT_FONT_SIZE, fontfile=c.SERV_DRAWTEXT_FONT_FILE, fontcolor=c.SERV_DRAWTEXT_FONT_COLOR ) v1 = ffmpeg.overlay(v1, self.overlay_file, x=c.OVERLAY_X, y=c.OVERLAY_Y) v1 = ffmpeg.overlay(v1, self.overlay_file_outline, x=c.OVERLAY_X, y=c.OVERLAY_Y) a1 = in1['a'] joined = ffmpeg.concat(v1, a1, v=1, a=1) self.ff = ffmpeg.output(joined, self.output, vcodec='h264', aspect=c.SERV_OUTPUT_ASPECT, acodec=c.SERV_OUTPUT_ACODEC, crf=c.SERV_OUTPUT_CRF, preset=c.SERV_OUTPUT_PRESET, format='flv', pix_fmt='yuv420p' ) self.cmd = ['ffmpeg', '-re']+ffmpeg.get_args(self.ff) self.process = subprocess.Popen(self.cmd, stdin=subprocess.PIPE, stdout=devnull, stderr=( None if SERVER_DEBUG else devnull)) Logger.LOGGER.log(Logger.TYPE_INFO, 'Server Process Created') return self.process
def play(self): output_stream = None if self.media_type == "upnext": Logger.LOGGER.log( Logger.TYPE_INFO, 'Playing upnext v:{} a:{} (Duration: {})'.format( self.media_item.video_path, self.media_item.audio_path, self.media_item.duration_readable)) in1 = ffmpeg.input(self.media_item.video_path) in2 = ffmpeg.input(self.media_item.audio_path) v1 = ffmpeg.filter(in1['v'], 'scale', c.CLIENT_VIDEO_SCALE) v1 = ffmpeg.drawtext(v1, '{}'.format(self.media_item.overlay_text), x=c.CLIENT_DRAWTEXT_X, y=c.CLIENT_DRAWTEXT_Y, escape_text=False, shadowcolor=c.CLIENT_DRAWTEXT_SHADOW_COLOR, shadowx=c.CLIENT_DRAWTEXT_SHADOW_X, shadowy=c.CLIENT_DRAWTEXT_SHADOW_Y, fontsize=c.CLIENT_DRAWTEXT_FONT_SIZE, fontfile=c.CLIENT_DRAWTEXT_FONT_FILE, fontcolor=c.CLIENT_DRAWTEXT_FONT_COLOR) a1 = in1['a'] a2 = in2['a'] audio_join = ffmpeg.filter([a1, a2], 'amix', duration="first") output_stream = ffmpeg.concat(v1, audio_join, v=1, a=1) else: Logger.LOGGER.log( Logger.TYPE_INFO, 'Playing v:{} (Duration: {})'.format( self.media_item, self.media_item.duration_readable)) in1 = ffmpeg.input(self.media_item.video_path) v1 = ffmpeg.filter(in1['v'], 'scale', c.CLIENT_VIDEO_SCALE) a1 = in1['a'] output_stream = ffmpeg.concat(v1, a1, v=1, a=1) self.ff = ffmpeg.output(output_stream, 'pipe:', vcodec=c.CLIENT_VCODEC, aspect=c.CLIENT_ASPECT, flags=c.CLIENT_FLAGS, g=c.CLIENT_G, acodec=c.CLIENT_ACODEC, strict=c.CLIENT_STRICT, ab=c.CLIENT_AUDIO_BITRATE, ar=c.CLIENT_AUDIO_RATE, preset=c.CLIENT_PRESET, hls_allow_cache=c.CLIENT_HLS_ALLOW_CACHE, hls_list_size=c.CLIENT_HLS_LIST_SIZE, hls_time=c.CLIENT_HLS_TIME, format=c.CLIENT_FORMAT, pix_fmt=c.CLIENT_PIX_FMT) self.cmd = ['ffmpeg'] + ffmpeg.get_args(self.ff) self.process = subprocess.Popen( self.cmd, stdout=self.server.stdin, stderr=(None if CLIENT_DEBUG else devnull)) try: flex = c.CLIENT_FLEX # Number of seconds of extra time before timeout timeout = (self.media_item.duration / 1000 ) # Content length in seconds self.process.wait(timeout=timeout + flex) except subprocess.TimeoutExpired: Logger.LOGGER.log( Logger.TYPE_ERROR, 'Taking longer to play than expected, killing current item') kill(self.process.pid) self.process.returncode = 0 return self.process.returncode # returncode 0 if process exited without problems, 1 for general error
def ffmpeg_condense_audio(audiofile, sub_times, quality: Union[int, None], to_mono: bool, outfile=None): if outfile is None: outfile = "condensed.flac" # logging.info(f"saving condensed audio to {outfile}") # get samples in audio file audio_info = ffmpeg.probe(audiofile, cmd='ffprobe') sps = int(audio_info['streams'][0]['time_base'].split('/') [1]) # audio samples per second, inverse of sampling frequency # samples = audio_info['streams'][0]['duration_ts'] # total samples in audio track stream = ffmpeg.input(audiofile) clips = list() for time in sub_times: # times are in milliseconds start = int(time[0] * sps / 1000) # convert to sample index end = int(time[1] * sps / 1000) # use start_pts for sample/millisecond level precision clips.append( stream.audio.filter('atrim', start_pts=start, end_pts=end).filter('asetpts', 'PTS-STARTPTS')) combined = ffmpeg.concat(*clips, a=1, v=0) kwargs = {} if Path(outfile).suffix.lower() == ".mp3": if quality is None: kwargs['audio_bitrate'] = "320k" else: kwargs['audio_bitrate'] = f"{quality}k" if to_mono: kwargs['ac'] = 1 combined = ffmpeg.output(combined, outfile, **kwargs) combined = ffmpeg.overwrite_output(combined) logging.debug( f"ffmpeg_condense_audio: ffmpeg arguments: {' '.join(ffmpeg.get_args(combined))}" ) args = ffmpeg.get_args(combined) if len("ffmpeg " + " ".join(args)) > 32766 and os.name == 'nt': logging.info( "Arguments passed to ffmpeg exceeds 32767 characters while running on a Windows system. " "Will try using a temporary file to pass filter_complex arguments to ffmpeg." ) idx = args.index("-filter_complex") + 1 complex_filter = str(args[idx]) # write complex_filter to a temporary file fp = tempfile.NamedTemporaryFile( delete=False ) # don't delete b/c can't open file again when it's already open in windows fp.write(complex_filter.encode(encoding="utf-8")) fp.close() args[idx] = fp.name args[idx - 1] = "-filter_complex_script" args = ["ffmpeg"] + args # ffmpeg.run(combined, quiet=logging.getLogger().getEffectiveLevel() >= logging.WARNING) pipe_stdin = False pipe_stdout = False pipe_stderr = False quiet = logging.getLogger().getEffectiveLevel() >= logging.WARNING stdin_stream = subprocess.PIPE if pipe_stdin else None stdout_stream = subprocess.PIPE if pipe_stdout or quiet else None stderr_stream = subprocess.PIPE if pipe_stderr or quiet else None process = subprocess.Popen(args, stdin=stdin_stream, stdout=stdout_stream, stderr=stderr_stream) out, err = process.communicate(input) retcode = process.poll() if retcode: raise Error('ffmpeg', out, err)
def ffmpeg_condense_video(audiofile: str, videofile: str, subfile: str, sub_times, outfile): logging.info(f"saving condensed video to {outfile}") # get samples in audio file audio_info = ffmpeg.probe(audiofile, cmd='ffprobe') sps = int(audio_info['streams'][0]['time_base'].split('/') [1]) # audio samples per second, inverse of sampling frequency # samples = audio_info['streams'][0]['duration_ts'] # total samples in audio track audiostream = ffmpeg.input(audiofile) videostream = ffmpeg.input(videofile) substream = ffmpeg.input(subfile) vid = videostream.video.filter_multi_output('split') # sub = videostream['s'].filter_multi_output('split') aud = audiostream.audio.filter_multi_output('asplit') clips = [] for idx, time in enumerate(sub_times): # times are in milliseconds # start = int(time[0] * sps / 1000) # convert to sample index # end = int(time[1] * sps / 1000) start = time[0] / 1000 end = time[1] / 1000 # use start_pts for sample/millisecond level precision a = aud[idx].filter('atrim', start=start, end=end).filter('asetpts', expr='PTS-STARTPTS') v = vid[idx].trim(start=start, end=end).setpts('PTS-STARTPTS') # s = sub[idx].trim(start=start, end=end).setpts('PTS-STARTPTS') clips.extend((v, a)) out = ffmpeg.concat(*clips, v=1, a=1).output(substream, outfile) # output = ffmpeg.output(joined[0], joined[1], outfile) out = ffmpeg.overwrite_output(out) logging.debug( f"ffmpeg_condense_video: ffmpeg arguments: {' '.join(ffmpeg.get_args(out))}" ) args = ffmpeg.get_args(out) if len("ffmpeg " + " ".join(args)) > 32766 and os.name == 'nt': logging.info( "Arguments passed to ffmpeg exceeds 32767 characters while running on a Windows system. " "Will try using a temporary file to pass filter_complex arguments to ffmpeg." ) idx = args.index("-filter_complex") + 1 complex_filter = str(args[idx]) # write complex_filter to a temporary file fp = tempfile.NamedTemporaryFile( delete=False ) # don't delete b/c can't open file again when it's already open in windows fp.write(complex_filter.encode(encoding="utf-8")) fp.close() args[idx] = fp.name args[idx - 1] = "-filter_complex_script" args = ["ffmpeg"] + args # ffmpeg.run(out, quiet=logging.getLogger().getEffectiveLevel() >= logging.WARNING) pipe_stdin = False pipe_stdout = False pipe_stderr = False quiet = logging.getLogger().getEffectiveLevel() >= logging.WARNING stdin_stream = subprocess.PIPE if pipe_stdin else None stdout_stream = subprocess.PIPE if pipe_stdout or quiet else None stderr_stream = subprocess.PIPE if pipe_stderr or quiet else None process = subprocess.Popen(args, stdin=stdin_stream, stdout=stdout_stream, stderr=stderr_stream) out, err = process.communicate(input) retcode = process.poll() if retcode: raise Error('ffmpeg', out, err)