1

Let's say I have input.mp4 which is 12 minutes long. I'd like to take 4 times 2.5 seconds from the input to one file, output.webm.

So, first 2.5 seconds at 2.5 minute mark, second 2.5 seconds at 5 minute mark, then the same at 7.5 second mark and finally last 2.5 seconds on the 10 minute mark. Then output it all to same output.webm file.

And if this is possible, is this also possible so that if the input.mp4 length changes, I'd get those 4 parts from based to the length instead of them always being standard 2.5, 5, 7.5 and 10?

bertieb
  • 7,543
EmJeiEn
  • 113
  • 2

1 Answers1

1

Creating a compilation clip based on cuts in a video using ffmpeg

Please note the following is based on ptQa's answer with modifications, so it deserves an upvote if this answer is useful.

The following fffmpeg incantation will give you a 10 second VP8 webm with cuts at the 2.5,5 7.5 and 10 minute mark:

ffmpeg -hide_banner -i "input.mp4" -filter_complex " \
[0:v]trim=start=150:duration=2.5,setpts=PTS-STARTPTS[av];\
[0:a]atrim=start=150:duration=2.5,asetpts=PTS-STARTPTS[aa];\
[0:v]trim=start=300:duration=2.5,setpts=PTS-STARTPTS[bv];\
[0:a]atrim=start=300:duration=2.5,asetpts=PTS-STARTPTS[ba];\
[0:v]trim=start=450:duration=2.5,setpts=PTS-STARTPTS[cv];\
[0:a]atrim=start=450:duration=2.5,asetpts=PTS-STARTPTS[ca];\
[0:v]trim=start=900:duration=2.5,setpts=PTS-STARTPTS[dv];\
[0:a]atrim=start=900:duration=2.5,asetpts=PTS-STARTPTS[da];\
[av][aa][bv][ba][cv][ca][dv][da]concat=n=4:v=1:a=1[outv][outa]" \
-map [outv] -map [outa] -c:v libvpx -crf 10 -b:v 1M -c:a libvorbis  out.webm

This makes four uses each of trim and atrim filters, and finally combines the subclips using the concat filter.

The encoding options are what are suggested by the libvpx encoding guide from the FFmpeg wiki.

Bonus: variable trims based on input length

This is doable with a bit of scripting, using ffprobe to get the duration, eg:

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4

This returns the clip length in seconds, which can be divided by 5 and then multiplied by 1, 2 3 and 4 to give positions of four equally-spaced cuts respectively.

Note on performance

While generating the clip this way is neat in that it exactly excises the subclips from the input video, it is slow.

A quicker approach would be to use input seeking and a stream copy to make the subclips, before joining those together and encoding.

However, if you need exact durations, or performance isn't an issue, using a bunch of trim filters will do the job just fine.

Script to generate the clip using trim for any input length and any number of cuts/subclips

#!/bin/bash
# clip.sh - create a clip of short sequences of a passed-in media file
# takes one argument, the input file
# can customise number of cuts, total length of clip
# clips will be evenly-spaced in input file

SUBCLIPS=4 # number of clips to make up clip
OUTPUTLENGTH=10 # output file length, in seconds
OUTPUTFILE=out.webm # name

cliplength=$(echo "$OUTPUTLENGTH/$SUBCLIPS"| bc -l)

if [ -e "$1" ]; then
        videofile="$1"
else
        echo "file not found: $1"
        exit 1
fi

ffmpeg_preamble="ffmpeg -hide_banner -i '$videofile' -filter_complex \""

duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$videofile")

cutfilters=""
concatfilter=""

for cutno in $( seq 1 $SUBCLIPS ); do
    cut=$(echo "(1 / ($SUBCLIPS+1)) * $cutno" | bc -l)
    let cutchar=$cutno+96 # ascii value so we get max 26 cuts rather than 10
    cutletter=$(printf "\x$(printf %x $cutchar)")
    cutpos=$(echo "$duration * $cut" |bc)
    cutfilters=$(printf "%s[0:v]trim=start=%f:duration=%f,setpts=PTS-STARTPTS[%sv];" "$cutfilters" "$cutpos" "$cliplength" "$cutletter")
    cutfilters=$(printf "%s[0:a]atrim=start=%f:duration=%f,asetpts=PTS-STARTPTS[%sa];" "$cutfilters" "$cutpos" "$cliplength" "$cutletter")
    concatfilter=$(printf "%s[%sv][%sa]" "$concatfilter" "$cutletter" "$cutletter")
done

# concat the cuts together

concatfilter=$(printf "%sconcat=n=%s:v=1:a=1[outv][outa]" "$concatfilter" "$SUBCLIPS")

ffmpeg_webmopts=" -c:v libvpx -crf 10 -b:v 1M -c:a libvorbis "
ffmpeg_postscript="\" -map [outv] -map [outa] $ffmpeg_webmopts '$OUTPUTFILE'"

# Chance to cancel before we start

printf "***(hit ctrl+c to cancel)***\n\nExecuting: %s%s%s%s\n" "$ffmpeg_preamble" "$cutfilters" "$concatfilter" "$ffmpeg_postscript"
sleep 3
eval $(echo "$ffmpeg_preamble $cutfilters $concatfilter $ffmpeg_postscript")
bertieb
  • 7,543