-
-
Save royshil/369e175960718b5a03e40f279b131788 to your computer and use it in GitHub Desktop.
| #!/usr/local/bin/python3 | |
| import argparse | |
| import subprocess | |
| import itertools | |
| parser = argparse.ArgumentParser(description='Concatenate videos with FFMPEG, add "xfade" between segments.') | |
| parser.add_argument('--segments_file', '-f', metavar='Segments file', type=str, nargs=1, | |
| help='Segments text file for concatenating. e.g. "segments.txt"') | |
| parser.add_argument('--output', '-o', dest='output_filename', type=str, | |
| default='ffmpeg_concat_fade_out.mp4', | |
| help='output filename to provide to ffmpeg. default="ffmpeg_concat_fade_out.mp4"') | |
| parser.add_argument('segments', nargs='+') | |
| args = parser.parse_args() | |
| if args.segments_file: | |
| with open(args.segments_file[0], 'r') as seg_file: | |
| # cut the `file '` prefix and `'` postfix | |
| segments = [line[6:-2] for line in seg_file.readlines() if len(line.strip()) > 0 and line[0] != "#"] | |
| else: | |
| segments = args.segments | |
| # Get the lengths of the videos in seconds | |
| file_lengths = [ | |
| float(subprocess.run(['/usr/local/bin/ffprobe', | |
| '-v', 'error', | |
| '-show_entries', 'format=duration', | |
| '-of', 'default=noprint_wrappers=1:nokey=1', | |
| f], | |
| capture_output=True).stdout.splitlines()[0]) | |
| for f in segments | |
| ] | |
| width = int(subprocess.run(['/usr/local/bin/ffprobe', '-v', 'error', '-select_streams', 'v', '-show_entries', | |
| 'stream=width', '-of', 'default=nw=1:nk=1', segments[0]], | |
| capture_output=True).stdout.splitlines()[0]) | |
| height = int(subprocess.run(['/usr/local/bin/ffprobe', '-v', 'error', '-select_streams', 'v', '-show_entries', | |
| 'stream=height', '-of', 'default=nw=1:nk=1', segments[0]], | |
| capture_output=True).stdout.splitlines()[0]) | |
| # File inputs from the list | |
| files_input = [['-i', f] for f in segments] | |
| # Prepare the filter graph | |
| video_fades = "" | |
| audio_fades = "" | |
| last_fade_output = "0v" | |
| last_audio_output = "0:a" | |
| video_length = 0 | |
| normalizer = "" | |
| scaler_default = f",scale=w={width}:h={height}:force_original_aspect_ratio=1,pad={width}:{height}:(ow-iw)/2:(oh-ih)/2" | |
| for i in range(len(segments)): | |
| # Videos normalizer | |
| scaler = scaler_default if i > 0 else "" | |
| normalizer += f"[{i}:v]settb=AVTB,setsar=sar=1,fps=30{scaler}[{i}v];" | |
| if i == 0: | |
| continue | |
| # Video graph: chain the xfade operator together | |
| video_length += file_lengths[i - 1] - 0.25 | |
| next_fade_output = "v%d%d" % (i - 1, i) | |
| video_fades += "[%s][%dv]xfade=duration=0.5:offset=%.3f[%s];" % \ | |
| (last_fade_output, i, video_length - 1, next_fade_output) | |
| last_fade_output = next_fade_output | |
| # Audio graph: | |
| next_audio_output = "a%d%d" % (i - 1, i) | |
| audio_fades += f"[{last_audio_output}][{i}:a]acrossfade=d=1[{next_audio_output}];" | |
| last_audio_output = next_audio_output | |
| video_fades += f"[{last_fade_output}]format=pix_fmts=yuv420p[final];" | |
| # Assemble the FFMPEG command arguments | |
| ffmpeg_args = ['/usr/local/bin/ffmpeg', | |
| *itertools.chain(*files_input), | |
| '-filter_complex', normalizer + video_fades + audio_fades[:-1], | |
| '-map', '[final]', | |
| '-map', f"[{last_audio_output}]", | |
| '-y', | |
| args.output_filename] | |
| print(" ".join(ffmpeg_args)) | |
| # Run FFMPEG | |
| subprocess.run(ffmpeg_args) |
Hi please help me where to change duration and offset, let's says duration=4 offset=2
thanks
Hi please help me where to change duration and offset, let's says duration=4 offset=2
thanks
This is where you determine duration: https://gist.github.com/royshil/369e175960718b5a03e40f279b131788#file-ffmpeg_concat_xfade-py-L65 (line 65 - it's currently 0.5)
and the offset is calculated video_length - 1 in line 66, just replace 1 to be a different number
Do I need to change the offset and the acrossfade duration?
yes you should double the number from xfade into acrossfade=d=NN on line 71
What is the usage of a segment file? Is it for passing multiple video locations in a single file?
@federicopalumbo can you give me the full corrected script please ? because the one above doesn't seems to work, with 9 videos to concats I have a big delay between video and audio on the last videos...
Thanks
@federicopalumbo can you give me the full corrected script please ? because the one above doesn't seems to work, with 9 videos to concats I have a big delay between video and audio on the last videos... Thanks
Little difficult to paste all the code onto the comment section. But I made 3 simple changes to made it works better:
1. add a fade duration constant in the beginning.
#constants
FADE_TIME=1
2. apply the constant under video graph section.
#Video graph: chain the xfade operator together
video_offset += file_lengths[i - 1] - FADE_TIME
next_fade_output = "v%d%d" % (i - 1, i)
video_fades += "[%s][%dv]xfade=duration=%d:offset=%.5f[%s];" % (last_fade_output, i, FADE_TIME, video_offset, next_fade_output)
last_fade_output = next_fade_output
3. apply constant at the audio graph as well.
#Audio graph:
next_audio_output = "a%d%d" % (i - 1, i)
audio_fades += f"[{last_audio_output}][{i}:a]acrossfade=d=%d[{next_audio_output}];" % (FADE_TIME)
last_audio_output = next_audio_output
Running:
The script will handle videos of different sizes or frame-rates as well.