How can I remove multiple segments from a video using FFmpeg?

54

20

I am trying to delete a few sections of a video using FFmpeg.

For example, imagine if you recorded a show on television and wanted to cut out the commercials. This is simple with a GUI video-editor; you just mark the beginning and ending of each clip to be removed, and select delete. I am trying to do the same thing from the command-line with FFmpeg.

I know how to cut a single segment to a new video like so:

ffmpeg -i input.avi -ss 00:00:20 -t 00:00:05 -map 0 -codec copy output.avi

This cuts a five-second clip and saves it as a new video file, but how can I do the opposite and save the whole video without the specified clip, and how I can I specify multiple clips to be removed?

For example, if my video could be represented by ABCDEFG, I would like to create a new one that would consist of ACDFG.

Matias

Posted 2013-11-28T20:09:08.170

Reputation: 843

possible duplicate of Splitting video in multiple episodes with ffmpeg, also perhaps check out Using FFMpeg to cut a video into 2 minute clips

– Ƭᴇcʜιᴇ007 – 2013-11-28T20:18:08.703

2That is not what I am asking, I would like to get it all in one "episode", but this would have different sections from the original video. – Matias – 2013-11-29T15:21:56.717

@Matias, if you were asking how to cut a few clips out of the video and leave the rest as is, that would be one thing, but you want to take a few clips from it and combine them with clips from other videos which makes this not a separate, unique question. You have to do what the other questions asked to get the separate segments, then combine them. – Synetech – 2013-11-29T16:47:01.627

1@Synetech thanks for your answers. I do not want to combine them with clips from other videos. I just want to remove some parts from the videos. For example, if my video could be represented by ABCDEFG, I would like to create a new one that would consist of ACDFG. – Matias – 2013-11-29T17:10:03.010

@Synetech That wasn't me, it was Tog who must have missunderstood. – Matias – 2013-11-29T17:47:01.890

Okay then, that makes sense now. I’ve edited the question to clarify what you want to do, and it is indeed a good question. It’s easy to do that with a GUI editor, but doing it with FFmpeg will probably be a challenge (if possible at all). You may end up having to do what I said and cut each section to a separate file then combine them. :-( – Synetech – 2013-11-29T17:56:03.010

@Synetech I cannot use a GUI editor because I am using FFmpeg in order to execute in a C# application. I have already tried creating various cuts and the joining them, but I had sync problems. Thanks! – Matias – 2013-11-29T18:08:34.200

I’ve had that problem even with a GUI editor. Maybe someone who knows FFmpeg well will know of a way. I tried finding one, but all I can find are people asking how to extract a clip to a new file, not delete it. This is really odd because removing part of a video is a perfectly normal, common task—it must not be possible. All I could find were two pages where this was asked, but they ended up cutting and joining. :-( Also, it was suggested to try mencoder.

– Synetech – 2013-11-29T18:51:28.580

ffmpeg is essentially an encoder. You can choose to not re-encode, but that still does not make it cutting as in a NLE. For what you wish to achieve, I believe the only way is to have a temporary storage(s) which will again be joined (concatenated). In other words, no direct delete portion. – Rajib – 2013-11-30T07:42:31.793

Answers

3

Although the answer provided by ptQa seems to work, I have developed an other solution which has prooved to work fine.

Essentially, what I do is to cut one video for each part of the original video that I want to include on my result. Later, I concatenate them with the Concat Demuxer explained here.

The solution is the same that waht I have tried first and presented sync problems. What I have added is the command -avoid_negative_ts 1 when generating the different videos. With this solution, the sync problems dissapear.

Matias

Posted 2013-11-28T20:09:08.170

Reputation: 843

48

Well, you still can use the trim filter for that. Here is an example, lets assume that you want to cut out segments 30-40 sec (10 sec) and 50-80 sec (30 sec):

ffmpeg -i in.ts -filter_complex \
"[0:v]trim=duration=30[a]; \
 [0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[b]; \
 [a][b]concat[c]; \
 [0:v]trim=start=80,setpts=PTS-STARTPTS[d]; \
 [c][d]concat[out1]" -map [out1] out.ts

What I did here? I trimmed first 30 sec, 40-50 sec and 80 sec to end, and then combined them into stream out1 with the concat filter.

About setpts: we need this because trim does not modify picture display time, and when we cut out 10 sec decoder counter does not see any frames for this 10 sec.

If you want to have audio too, You have to do the same for audio streams. So the command should be:

ffmpeg -i utv.ts -filter_complex \
"[0:v]trim=duration=30[av];[0:a]atrim=duration=30[aa];\
 [0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[bv];\
 [0:a]atrim=start=40:end=50,asetpts=PTS-STARTPTS[ba];\
 [av][bv]concat[cv];[aa][ba]concat=v=0:a=1[ca];\
 [0:v]trim=start=80,setpts=PTS-STARTPTS[dv];\
 [0:a]atrim=start=80,asetpts=PTS-STARTPTS[da];\
 [cv][dv]concat[outv];[ca][da]concat=v=0:a=1[outa]" -map [outv] -map [outa] out.ts

ptQa

Posted 2013-11-28T20:09:08.170

Reputation: 1 599

1How would you go about maintaining subtitle data when you're skipping frames? – Andrew Scagnelli – 2015-03-02T14:46:16.263

@AndrewScagnelli, I have been looking at the v3.0 ffmpeg code, and there at least, there are only trim methods defined for audio and video streams. Nothing for data and/or subtitle types. – Jameson – 2016-03-09T04:57:28.033

1According to ffmpeg doc, you should concatenate video and audio in a single filter to avoid sync issues. ie. [av][bv]concat[cv];[aa][ba]concat=v=0:a=1[ca] should be [av][aa][bv][ba]concat=a=1[cv][ca] Same for the second concat. – Olivier – 2017-04-02T22:08:17.797

1This is very unhuman. – golopot – 2017-04-29T09:06:22.890

@Wutaz When I ran into that issue, I used the fifo filter as mentioned in this answer to FFMPEG “buffer queue overflow, dropping” with trim and atrim filters.

– zero298 – 2018-06-29T21:27:03.293

Great one! Why do we need setpts? Could you add the explanation? – Rajib – 2013-12-01T04:32:22.247

Ok, see updated answer. – ptQa – 2013-12-02T07:41:38.597

4+1 This really should be selected as the answer. It is more suited for "multiple segments" and removes the necessity to perform multiple commands one after another. (unless another way is more speed efficient) – Knossos – 2013-12-17T10:19:24.367

For the record, when I tried this with serveral segments (about 8) I got an error message about a "buffer queue overflow" and the program crashed. I don't know which versions are affected by this, but I wouldn't want to use this method for that reason. – Wutaz – 2014-02-13T22:35:01.457

17

I can never get ptQa's solution to work, mostly because I can never figure out what the errors from the filters mean or how to fix them. My solution seems a little clunkier because it can leave behind a mess, but if you're throwing it into a script, the clean up can be automated. I also like this approach because if something goes wrong on step 4, you end up with completed steps 1-3 so recovering from errors is a little more efficient.

The basic strategy is using -t and -ss to get videos of each segment you want, then join together all the parts for your final version.

Say you have 6 segments ABCDEF each 5 seconds long and you want A (0-5 seconds), C (10-15 seconds) and E (20-25 seconds) you'd do this:

ffmpeg -i abcdef.tvshow -t 5 a.tvshow -ss 10 -t 5 c.tvshow -ss 20 -t 5 e.tvshow

or

ffmpeg -i abcdef.tvshow -t 0:00:05 a.tvshow -ss 0:00:10 -t 0:00:05 c.tvshow -ss 0:00:20 -t 0:00:05 e.tvshow

That will make files a.tvshow, c.tvshow and e.tvshow. The -t says how long each clip is, so if c is 30 seconds long you could pass in 30 or 0:00:30. The -ss option says how far to skip into the source video, so it's always relative to the start of the file.

Then once you have a bunch of video files I make a file ace-files.txt like this:

file 'a.tvshow'
file 'c.tvshow'
file 'e.tvshow'

Note the "file" at the beginning and the escaped file name after that.

Then the command:

ffmpeg -f concat -i ace-files.txt -c copy ace.tvshow

That concats all the files in abe-files.txt together, copying their audio and video codecs and makes a file ace.tvshow which should just be sections a, c and e. Then just remember to delete ace-files.txt, a.tvshow, c.tvshow and e.tvshow.

Disclaimer: I have no idea how (in)efficient this is compared to the other approaches in terms of ffmpeg but for my purposes it works better. Hope it helps someone.

xbakesx

Posted 2013-11-28T20:09:08.170

Reputation: 331

4this one is very understandable for newbies as me, thanks – sites – 2015-04-19T21:48:02.190

5Note if you are using bash, you can avoid the creation of a file (ace-files.txt in your example) with: ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow – bufh – 2016-03-31T19:40:20.737

1This was really helpful but I had to remove the single quotes from around file names or it didn't work. – tchaymore – 2018-01-17T18:08:26.973

When concatenating videos, there is a short black video and silenced audio between them, about 25ms. Any ideas to get rid of that? – Antonio Bonifati 'Farmboy' – 2019-02-18T00:34:17.530

Hmm... I'd just check your math on the time offsets and skips, and make sure you aren't leaving a 25ms gap... I haven't experienced that. – xbakesx – 2019-02-19T15:58:53.033

I’m pretty sure this is exactly what I need for my projects use case. Will be excitedly trying tomorrow! – lustig – 2019-10-01T04:35:40.737

1

Thanks, @bufh, I was wondering how to do that! Just a note that may help others: as @pkSML explained below, you will also need to add -safe 0 to the ffmpeg command if using absolute file paths, for this to work. Alternatively, you can remove the $(pwd)/ part of the bash subcommand.

– waldyrious – 2019-10-08T09:53:18.953

i wonder if ffmpeg allows support for providing an input file a la how the concat command works except for splitting the file – ipatch – 2019-12-04T17:00:18.453

5

I made a script to speed up editing recorded TV. The script asks you for the beginning and end times of segments you want to keep and splits them out into files. It gives you options, you can:

  • Take one or multiple segments.
  • You can combine the segments into one resulting file.
  • After joining you can keep or delete the part files.
  • You can keep the original file or replace it with your new file.

Video of it in action: here

Let me know what you think.

 #!/bin/bash
/bin/date >>segmenter.log

function segment (){
while true; do
    echo "Would you like to cut out a segment ?"
    echo -e "1) Yes\n2) No\n3) Quit"
    read CHOICE
    if [ "$CHOICE" == "3" ]; then
        exit
    elif [ "$CHOICE" == "2" ]; then
        clear
        break
    elif [ "$CHOICE" == "1" ]; then
        clear
        ((segments++))
        echo "What time does segment $segments start ?"
        read SEGSTART
        clear
        echo -e "Segment $segments start set to $SEGSTART\n"  
        echo "What time does segment $segments end ?"
        read SEGEND
        clear
        echo -e "Segment $segments end set to $SEGEND\n"
        break
    else
        clear
        echo -e "Bad option"
        segment "$segments"
    fi
done
if [ "$CHOICE" == "1" ]; then
    echo "Cutting file $file video segment $segments starting at $SEGSTART and ending at $SEGEND"
    ffmpeg -i "$file" -ss $SEGSTART -to  $SEGEND -map 0:0 -map 0:1 -c:a copy -c:v copy  "$filename-part$segments.$extension"  >> segmenter.log 2>&1
    clear
    echo -e "Cut file $filename-part$segments.$extension starting at $SEGSTART and ending at $SEGEND\n"                             
    segment "$segments"
fi
}

file="$1"
filename="${file%.*}"
extension="${file##*.}"
clear
segments=0
segment "$segments"
clear
if (("$segments"==1)); then
mv $filename"-part1."$extension "$filename-segmented.$extension"
elif (("$segments">1)); then
echo "Would you like to join the segments into one file ?"      
       OPTIONS="Yes No Quit"
       select opt in $OPTIONS; do
       clear
        if [ "$opt" == "Quit" ]; then
            exit
        elif [ "$opt" == "Yes" ]; then
            clear
            echo "Joining segments"
            ffmpeg -f concat -i <(for f in $filename"-part"*$extension;         do echo "file '$(pwd)/$f'"; done) -c:a copy -c:v copy "$filename-segmented.$extension" >>         segmenter.log 2>&1
            clear
            echo "Would you like to delete the part files ?"
            select opt in $OPTIONS; do
            clear
            if [ "$opt" == "Quit" ]; then
                exit
            elif [ "$opt" == "Yes" ]; then
                for f in $filename"-part"*$extension; do rm $f; done
                break
            elif [ "$opt" == "No" ]; then
                break
            else
                clear
                echo -e "Bad option\n"
            fi
            done
            break
        clear
        elif [ "$opt" == "No" ]; then
            exit
        else
            clear
            echo -e "Bad option\n"
        fi
    done
fi
echo "Would you like to replace the original file with the result of your changes ?"
OPTIONS="Yes No Quit"
select opt in $OPTIONS; do
    clear
    if [ "$opt" == "Quit" ]; then
        exit
    elif [ "$opt" == "Yes" ]; then
        rm $file
        mv "$filename-segmented.$extension" $file
        break
    elif [ "$opt" == "No" ]; then
        break
    else
        clear
        echo -e "Bad option\n"
    fi
done

rocuinneagain

Posted 2013-11-28T20:09:08.170

Reputation: 51

2

This script is great! Just one modification since you're using absolute paths for concatenation: add safe=0, i.e. ffmpeg -f concat -safe 0 -i ... See https://ffmpeg.org/ffmpeg-all.html#Options-36

– pkSML – 2019-09-23T22:05:40.257

2

For those having trouble following ptQa's approach, there's a slightly more streamlined way to go about it. Rather than concat each step of the way, just do them all at the end.

For each input, define a A/V pair:

//Input1:
[0:v]trim=start=10:end=20,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10:end=10,asetpts=PTS-STARTPTS[0a];
//Input2:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[1a];
//Input3:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[2v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[2a];

Define as many pairs as you need, then concat them all in one pass, where n=total input count.

[0v][0a][1v][1a][2v][2a]concat=n=3:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4

This can easily be constructed in a loop.

A complete command that uses 2 inputs might look like this:

 -y -i in.mp4 -filter_complex 
[0:v]trim=start=10.0:end=15.0,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10.0:end=15.0,asetpts=PTS-STARTPTS[0a];
[0:v]trim=start=65.0:end=70.0,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=65.0:end=70.0,asetpts=PTS-STARTPTS[1a];[0v][0a][1v]
[1a]concat=n=2:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4

shawnblais

Posted 2013-11-28T20:09:08.170

Reputation: 21

Thank you. I was able to create a working command from your answer. I find @ptQa's answer a bit confusing. – Neonit – 2019-12-11T19:54:16.893