90

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.

Oliver Salzburg
  • 86,445
  • 63
  • 260
  • 306
Matias
  • 1,209
  • 1
  • 10
  • 12
  • possible duplicate of [Splitting video in multiple episodes with ffmpeg](http://superuser.com/questions/606575/splitting-video-in-multiple-episodes-with-ffmpeg), also perhaps check out [Using FFMpeg to cut a video into 2 minute clips](http://superuser.com/questions/308306/using-ffmpeg-to-cut-a-video-into-2-minute-clips?rq=1) – Ƭᴇcʜιᴇ007 Nov 28 '13 at 20:18
  • 3
    That 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 Nov 29 '13 at 15:21
  • @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 Nov 29 '13 at 16:47
  • 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 Nov 29 '13 at 17:10
  • @Synetech That wasn't me, it was Tog who must have missunderstood. – Matias Nov 29 '13 at 17:47
  • 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 Nov 29 '13 at 17:56
  • @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 Nov 29 '13 at 18:08
  • 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](http://ubuntuforums.org/showthread.php?t=2123014) [pages](http://ffmpeg-users.933282.n4.nabble.com/Cut-out-middle-of-a-video-td4174531.html) where this was asked, but they ended up cutting and joining. `:-(` Also, it was suggested to try mencoder. – Synetech Nov 29 '13 at 18:51
  • 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 Nov 30 '13 at 07:42

10 Answers10

77

Well, you still can use the trim filter for that. Here is an example, lets assume that you want to cut out three segments at first and end of the video as well as in the middle:

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, leaving 30-40 sec (10 sec) and 50-80 sec (30 sec).

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
xpt
  • 8,261
  • 38
  • 102
  • 156
ptQa
  • 2,079
  • 15
  • 14
  • Great one! Why do we need setpts? Could you add the explanation? – Rajib Dec 01 '13 at 04:32
  • Ok, see updated answer. – ptQa Dec 02 '13 at 07:41
  • 5
    +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 Dec 17 '13 at 10:19
  • 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 Feb 13 '14 at 22:35
  • 2
    How would you go about maintaining subtitle data when you're skipping frames? – Andrew Scagnelli Mar 02 '15 at 14:46
  • @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 Mar 09 '16 at 04:57
  • 3
    According 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 Apr 02 '17 at 22:08
  • 7
    This is very unhuman. – golopot Apr 29 '17 at 09:06
  • 1
    @Wutaz When I ran into that issue, I used the [`fifo` filter](https://ffmpeg.org/ffmpeg-filters.html#fifo_002c-afifo) as mentioned in [this answer to *FFMPEG “buffer queue overflow, dropping” with trim and atrim filters*](https://stackoverflow.com/a/40746988/691711). – zero298 Jun 29 '18 at 21:27
  • 1
    Nice but the usage of `-filter_complex` is arcane and deserves an explanation (as a seasoned `ffmpeg` user I was able to infer the meaning of this code, but still). **More important:** you concatenate N parts by doing Tmp12 ≔ Part1 ++ Part2, then Tmp123 ≔ Tmp12 ++ Part3, and so on. This looks *terribly* inefficient (naive N-ary concatenation, quadratic time, bites us hard because we are concatenating video streams ie. huge data). Perhaps even it is why some people experience a “buffer queue overflow” (I’m no expert on that)? @shawblais’ answer does this right (only one N-ary concatenation). – Maëlan Apr 18 '20 at 21:41
  • 3
    On my zsh I had to quote the outcome streams. Namely ```-map '[outv]' -map '[outa]'``` – superk Nov 14 '20 at 15:01
  • Hi everyone, I did the edit to the best of my understanding of OP's original meaning, but having tried that, I found the effect to be the opposite of what my understanding is. Anybody got that conclusion as well? I think what @Maëlan explained on Apr 18 '20 is actually what is really happening. – xpt Sep 11 '21 at 18:39
  • No idea what I am doing wrong, but I applied this and end up with a file with is 40s shorter than the input (2:43), and the start of the video is still the same. – bers Sep 30 '21 at 17:58
39

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
  • 551
  • 6
  • 8
  • 9
    this one is very understandable for newbies as me, thanks – sites Apr 19 '15 at 21:48
  • 9
    Note 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 Mar 31 '16 at 19:40
  • 1
    This was really helpful but I had to remove the single quotes from around file names or it didn't work. – tchaymore Jan 17 '18 at 18:08
  • 1
    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' Feb 18 '19 at 00:34
  • 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 Feb 19 '19 at 15:58
  • I’m pretty sure this is exactly what I need for my projects use case. Will be excitedly trying tomorrow! – technoplato Oct 01 '19 at 04:35
  • 2
    Thanks, @bufh, I was wondering how to do that! Just a note that may help others: as @pkSML explained [below](https://superuser.com/questions/681885/how-can-i-remove-multiple-segments-from-a-video-using-ffmpeg#comment2240760_719610), 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 Oct 08 '19 at 09:53
  • i wonder if ffmpeg allows support for providing an input file a la how the `concat` command works except for splitting the file – ipatch Dec 04 '19 at 17:00
  • This is also a lot faster as it doesn't require decoding all the frames, and as a bonus, it does seem to cut at keyframes. – Tod Jun 04 '21 at 20:55
28

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=20,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:

ffmpeg -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
Hashim Aziz
  • 11,898
  • 35
  • 98
  • 166
shawnblais
  • 381
  • 3
  • 3
10

Edit: This approach will notacibly descync audio and video if the number of segements gets too large (>5-ish). For few segments, it's fine.

This will do it:

 ffmpeg -i input.avi -vf "select='1-between(t,20,25)', setpts=N/FRAME_RATE/TB" -af "aselect='1-between(t,20,25)', asetpts=N/SR/TB" output.avi

This command will discard the segment from 20 seconds to 25 seconds and keep everything else. Instead of an exclusion list, you can also do an inclusion list like this:

ffmpeg -i input.avi -vf "select='lt(t,20)+between(t,790,810)+gt(t,1440)', setpts=N/FRAME_RATE/TB" -af "aselect='lt(t,20)+between(t,790,810)+gt(t,1440)', asetpts=N/SR/TB" output.avi

This will keep 3 segments of the original video (0-20 seconds, 790-810 seconds, and 1440-end seconds), while discarding anything else.

The key parts (of the second example, since it is more complicated):

  • -vf is the option for a video filter expression. We use the select filter, which will take an expression, evaluate it for each input frame, and keep the frame if the expression is 1, or discard it if the expression is 0. (There is actually a bit more nuance to this, but it isn't really relevant to your problem. Look here if you want to know the full story.)

  • lt(t,20) is the first part of the select filter expression. lt stands for less-than and t is the timestamp of the frame in seconds that is evaluated, so this will evaluate to 1 for any frame with t<20s

  • between(t,790,810) will evaluate to 1 for any frame between 790s and 810s

  • gt(t, 1440) (greater-than) will become 1 for any frame after 1440s

  • We then add all the conditions, to logically or them.

  • setpts=N/FRAME_RATE/TB is needed to provide us with the t value that we use in the logical expression. ffmpeg seems to think in samples/frames here, not in seconds, so we use this expression to convert to seconds.

  • -af is the option for an audio filter expression. The audio filter works analogous to the video filter, except that to convert to seconds, we use the sample rate SR of the audio, and not the FRAME_RATE of the video.

You can go crazy with the filter expression and add new segments by adding a between(t,...,...) term and combine terms as you need. The exclusion list you wanted simply utilizes the fact that we can invert our filter expression by subtracting it from 1, so everything discarded will now be kept, and vice versa.

Ensure that you have the same select filter for audio and video, or they will get out of sync!

Note that you won't be able to use -codec copy with this solution, since copy tells ffmpeg to not decode the video. Since the video filter is evaluated for decoded frames, we must decode first.

pulp_user
  • 211
  • 2
  • 5
  • This `between` syntax is really cool, but somehow even with exactly the same select expression, the audio and video seem to be going out of sync. – ShreevatsaR Jan 07 '22 at 02:24
  • @ShreevatsaR You're right, it seems like the audio drifts a little every time there is a cut. I tried running the command without ", setpts=N/FRAME_RATE/TB" and ", asetpts=N/SR/TB", and then it worked perfectly. I think those were necessary in the past (?) but apparently aren't anymore. Can you try that on your video and reply here? If it works for you I will edit the answer. – pulp_user Jan 08 '22 at 18:04
  • Thanks for the reply! Removing the `setpts` and `asetpts` gave an interesting result: the sync is ok in the video, but there was an error message / warning saying "More than 1000 frames duplicated", and in the output, the video freezes (with no audio) for the duration of the removed part. – ShreevatsaR Jan 08 '22 at 19:02
  • @ShreevatsaR So I actually hopped on the ffmpeg mailing list to ask about this, and the gist is that there is no concise way to do it. You either need a script or a BIG command line string, but this approach unfortunately desyncs the audio and video with an increasing number of cuts :/. I'll update the answer. – pulp_user Feb 01 '22 at 16:22
9

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.

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
rianoc
  • 191
  • 1
  • 2
  • 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 Sep 23 '19 at 22:05
  • I really loved the script the way it interacts, but it is showing following error can you fix it (I tried twice both time it failed, start is 40 end is 120, but didn't ask video filename ): Unrecognized option 'part1.'. Error splitting the argument list: Option not found – justnajm Jul 06 '23 at 18:32
5

Converting ABCDEFG to ACDFG is essentially cutting three segments from the video, and then joining those three segments. If all segments will be cut from the same video, and there is no need to change the video's encoding (resolution, bit rate, frame rate, etc.), then re-encoding isn't necessary, and will degrade quality for lossy formats. However, without re-encoding, the cut positions will be not exact but the key frames nearest to the specified times.

The below examples assume that vidfull.mp4 is ABCDEFG, where each letter represents a five minute long segment.

Without re-encoding, the process is almost instantaneous, but cutting needs to be done explicitly, where temporary files are created, and joining can be done either with the concatenation protocol (i.e. file level concatenation), and the .ts container as the intermediate format, which supports concatenation at the file level:

ffmpeg \
  -to 5:0 -i vidfull.mp4 \
  -ss 10:0 -to 20:0 -i vidfull.mp4 \
  -ss 25:0 -i vidfull.mp4 \
  -map 0:v -map 0:a -c copy part1.ts \
  -map 1:v -map 1:a -c copy part2.ts \
  -map 2:v -map 2:a -c copy part3.ts
ffmpeg -i 'concat:part1.ts|part2.ts|part3.ts' -c copy vidcut.ts
ffmpeg -i vidcut.ts -c copy vidcut.mp4
rm -f part{1..3}.ts vidcut.ts

or with the concatenation script demuxer:

ffmpeg \
  -to 5:0 -i vidfull.mp4 \
  -ss 10:0 -to 20:0 -i vidfull.mp4 \
  -ss 25:0 -i vidfull.mp4 \
  -map 0:v -map 0:a -c copy -avoid_negative_ts make_non_negative part1.mp4 \
  -map 1:v -map 1:a -c copy -avoid_negative_ts make_non_negative part2.mp4 \
  -map 2:v -map 2:a -c copy -avoid_negative_ts make_non_negative part3.mp4
cat <<- eof > script.txt
    ffconcat version 1.0
    file '/path to/part1.mp4'
    file '/path to/part2.mp4'
    file '/path to/part3.mp4'
eof
ffmpeg -f concat -safe 0 -i script.txt -c copy vidcut.mp4
rm -f part{1..3}.mp4 script.txt

With re-encoding, the process is slow, but cutting and joining is done with a single command by using the same video as the input to a complex concat filter multiple times after seeking to different positions:

ffmpeg \
  -to 5:0 -i vidfull.mp4 \
  -ss 10:0 -to 20:0 -i vidfull.mp4 \
  -ss 25:0 -i vidfull.mp4 \
  -lavfi concat=n=3:a=1 vidcut.mp4

No need to label input or output pads to the concat filter since there isn't any other filter than concat in the filter chain to use a specific stream from a specific pad, no stream is intended to be excluded from the output, and all streams from unlabelled pads are added to the first output file automatically (the output format needs to support all stream types to avoid a fatal error). And, among the options of the concat filter, n is the number of input segments, and needs to be set to 3 since it is 2 by default, and a is the number of output audio streams, and needs to be set to 1 since it is 0 by default.

detic
  • 63
  • 1
  • 5
3

Shawnblais’ answer works nicely, but I noticed it was very slow when extracting short fragments from a long video. I suspect ffmpeg decodes the entire video, and applies the trim by discarding frames.

An alternative to using a single input file with multiple trim filters, is to use the same file as input multiple times. Then we can use -ss and -to as input options, which allow for a fast seek. For -ss and -to to be input options, they need to go before the -i that they apply to. The concat filter can then reference them by file number, 0:v, 1:v, etc.

Example concatenating video without audio:

ffmpeg \
  -ss 10.0 -to 15.0 -i in.mkv \
  -ss 50.0 -to 60.0 -i in.mkv \
  -filter_complex '[0:v][1:v]concat=n=2:v=1[outv]' \
  -map '[outv]' \
  out.mkv
Ruud
  • 311
  • 4
  • 15
  • 1
    I prefer this answer compared to some others since it does everything with 1 step. However, this current version does not transfer the audio. Here is a version that will (you may need to adjust if there is more than 1 audio stream): `ffmpeg -ss 10.0 -to 15.0 -i in.mkv -ss 50.0 -to 60.0 -i in.mkv -filter_complex '[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[outv][outa]' -map '[outv]' -map '[outa]' out.mkv` – LemmeTestThat Dec 30 '22 at 08:37
2

Continueing from shawnblais's answer, here's a Python function to help you construct the trim commands given start and end times

def construct_ffmpeg_trim_cmd(timepairs, inpath, outpath):
  cmd = f'ffmpeg -i "{inpath}" -y -filter_complex '
  cmd += '"'
  for i, (start, end) in enumerate(timepairs):
    cmd += (f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS,format=yuv420p[{i}v]; " +
            f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[{i}a]; ")
  for i, (start, end) in enumerate(timepairs):
    cmd += f"[{i}v][{i}a]"
  cmd += f'concat=n={len(timepairs)}:v=1:a=1[outv][outa]'
  cmd += '"'
  cmd += f' -map [outv] -map [outa] "{outpath}"'
  return cmd

GitHub gist: https://gist.github.com/FarisHijazi/eff7a7979440faa84a63657e085ec504

FarisHijazi
  • 131
  • 3
2

Although the answer provided by ptQa seems to work, I have developed another solution which has proved 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 result is the same as what I tried first--but which 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 disappear.

Glenn Slayden
  • 1,465
  • 12
  • 20
Matias
  • 1,209
  • 1
  • 10
  • 12
0

Having gone through this using ffmpeg which does a good, if complicated, job I realised ffmpeg seems to be set up well to deal with .mp4 and .ts (transport stream format) files to work losslessly and avoid reencoding.

Looking at dealing with mkv/webm format files which I prefer, I realized there is a great open source package called MKVtoolNix which includes mkvmerge, and as long as you are using a single video file or multiple files with similar formats, it simply defaults to doing the piping and mapping properly, and it accepts very simple syntax to take multiple segments of video and concatenate them perfectly, even supporting chapter information quite easily.

And MKVtoolNix apparently does no transcoding or reencoding so it's inherently very fast and lossless. Output files seem small and optimized. Subtitles and many intricacies of this process are handled by default or with ease. I would keep around ffmpeg for doing a quick cleanup of seek indexing via a "-c copy" command option though.

MKVtoolNix may have some ability to deal with other formats as well, and a GUI version of mkvmerge which looks very impressive.

fieldlab
  • 1
  • 2