1

I have a number of videos that overlap in time that were downloaded from an online video stream: 0m00s - 15m10s 14m50s - 30m20s ... The first few seconds (usually around 20 seconds) of a video segment duplicate last few seconds of the previous segment.

I'd like to concat the video segments and form a single video file without duplicate parts. I can do it programmatically using ffmpeg libraries to find the cut points by minimizing error between a frame of the current segment and the last frame of the previous segment.

Is there a way to achieve this with an ffmpeg command?

7
  • If the files have absolute timestamps, it may be possible. But if no, it's not possible in a single command. You can run one command to find join point betweeen two consecutive segments using blend filter/blackframe and then write a concat demuxer text file with the required inpoint/outpoint
    – Gyan
    Commented Aug 20, 2018 at 10:37
  • I just checked with -vf showinfo, and it seems they do not have absolute timestamps, but they do have some checksums (are these useful for finding exact duplicates?) Thanks for the pointer on blend/blackframe - googling it. Commented Aug 20, 2018 at 10:43
  • You need to add -copyts else ffmpeg will remove any starting offset before it gets to the filter.
    – Gyan
    Commented Aug 20, 2018 at 10:46
  • It seems framemd5 matching is enough for my videos. I can make framemd5 files for every video segment and fetch out dts / pts of the starting frame in every segment. Is there a way to concat with dts / pts inpoints instead of timestamps? Commented Aug 20, 2018 at 12:56
  • No, time has to be in seconds.
    – Gyan
    Commented Aug 20, 2018 at 14:23

1 Answer 1

1

I wrote the following script to solve my problem. It uses framemd5 checksums to find the inpoints in time and then uses the concat demuxer (thanks @Gyan for the tip!).

# Usage: ffmpeg $(python3 script.py input1.mp4 input2.mp4) -y -an output.mp4

import os
import sys
import subprocess

ffmpeg_concat_echo, md5_last_frame = '', None

for video_path in map(os.path.abspath, sys.argv[1:]):
        framemd5 = subprocess.check_output(['ffmpeg', '-nostats', '-hide_banner', '-i', video_path, '-an', '-f', 'framemd5', '-c', 'copy', '-'], stderr = open(os.devnull, 'w'))
        tb_num, tb_den = 1, 1
        inpoint = 0.0
        for line in framemd5.split(b'\n'):
                if line.startswith(b'#tb'):
                        tb_num, tb_den = list(map(int, line.split()[-1].split(b'/')))
                elif line and not line.startswith(b'#'):
                        splitted = line.split(b',')
                        md5 = splitted[-1].strip()
                        pts_time = float(splitted[2]) * tb_num / tb_den
                        if md5_last_frame is not None and md5 == md5_last_frame:
                                inpoint = pts_time
        md5_last_frame = md5
        ffmpeg_concat_echo += 'echo "file {}"; echo "inpoint {}";'.format(video_path, inpoint);

print('-nostats -hide_banner -avoid_negative_ts make_zero -fflags +genpts -f concat -safe 0 -i <({}) -c copy'.format(ffmpeg_concat_echo))

Below is another version (source, archived) which supports exact PTS inpoints using concat video filter; computing inpoints using explicit frame differences.

# Usage: ffmpeg $(( echo input1.mp4; echo input2.mp4 ) | python3 concat_overlapping_video_framemd5_demuxer.py concat_file.txt) -y -an output.mp4

import os
import sys
import subprocess

ffmpeg_concat_file, md5_last_frame = open(sys.argv[1], 'w'), None
for video_path in map(os.path.abspath, list(map(str.strip, sys.stdin))):
        framemd5 = subprocess.check_output(['ffmpeg', '-nostats', '-hide_banner', '-i', video_path, '-an', '-f', 'framemd5', '-c', 'copy', '-'], stderr = open(os.devnull, 'w'))
        inpoint, tb_num, tb_den = 0, 1, 1
        for line in framemd5.split(b'\n'):
                if line.startswith(b'#tb'):
                        tb_num, tb_den = list(map(int, line.split()[-1].split(b'/')))
                elif not line.startswith(b'#') and len(line) > 0:
                        splitted = line.split(b',')
                        md5, pts_time = splitted[-1].strip(), float(splitted[2]) * tb_num / tb_den
                        if md5 == md5_last_frame:
                                inpoint = pts_time
        md5_last_frame = md5
        ffmpeg_concat_file.write('file {}\ninpoint {}\n'.format(video_path, inpoint))

print('-nostats -hide_banner -avoid_negative_ts make_zero -fflags +genpts -f concat -safe 0 -i {} -c copy'.format(sys.argv[1]))
6

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.