How I Completely Automated My YouTube Editing Using FFmpeg

If you've been watching my videos from the posts I make on here (thank you if you have), I got a dirty little secret...

I haven't done any manual editing on the past three videos.

Now, that doesn't mean there isn't any editing, there is a little but that will grow in time.

No, instead a single FFmpeg command does all the editing for me.

So what exactly is it doing you ask? So far it does the following:

  • Delaying and overlaying a sub animation over the main video (usually just a recording)
  • Adding a fade-in effect to the start of the video
  • Adding a fade-out effect before the outro
  • Appends the outro to the end of the video

Oh, what's that? Do you want to see the magic? I got you bud.

Script

#!/usr/bin/env sh

IN=$1
OUT=$2
OVER=$3
OVER_START=$4
OUTRO=$5
DURATION=$(get_vid_duration $IN)
FADE_OUT_DURATION=$6
FADE_IN_DURATION=$7
FADE_OUT_START=$(bc -l <<< "$DURATION - $FADE_OUT_DURATION")
MILLI=${OVER_START}000

ffmpeg -i $IN -i $OUTRO -filter_complex \
    "[0:v]setpts=PTS-STARTPTS[v0];
    movie=$OVER:s=dv+da[overv][overa];
    [overv]setpts=PTS-STARTPTS+$OVER_START/TB[v1];
    [v0][v1]overlay=-600:0:eof_action=pass,fade=t=in:st=0:d=$FADE_IN_DURATION,fade=t=out:st=$FADE_OUT_START:d=$FADE_OUT_DURATION[mainv];
    [overa]adelay=$MILLI|$MILLI,volume=0.5[a1];
    [0:a:0][0:a:1][a1]amix=inputs=3:duration=longest:dropout_transition=0:weights=3 3 1[maina];
    [mainv][maina][1:v][1:a]concat=n=2:v=1:a=1[outv][outa]" \
    -map "[outv]" -map "[outa]" $OUT

That's a chunky boy, so let me break down what exactly is happening.

Arguments

IN=$1
OUT=$2
OVER=$3
OVER_START=$4
OUTRO=$5
DURATION=$(get_vid_duration $IN)
FADE_OUT_DURATION=$6
FADE_IN_DURATION=$7
FADE_OUT_START=$(bc -l <<< "$DURATION - $FADE_OUT_DURATION")
MILLI=${OVER_START}000

These are all the arguments I'm passing into the script to build the FFmpeg command.

IN=$1 - this is the path to the main video that I want to use, probably a recording I did earlier in the day.

OUT=$2 - this is the path I want to save the final video to.

OVER=$3 - this is the file path to the subscription animation I started using. I thought it better to pass this in, since I may change what animation I'm using at some point.

OVER_START=$4 - the timestamp, in seconds, to start playing the subscription animation in the main video. It is needed to offset the animation's video frame timestamps and delay the audio.

DURATION=$(get_vid_duration $IN) - I'm using another script to get the duration, in seconds, of the main video. It's using FFprobe to grab the metadata in a specific format.

Here is the get_vid_duration script for reference:

#!/usr/bin/env sh

IN=$1

ffprobe -i $IN -show_entries format=duration -v quiet -of csv="p=0"

FADE_OUT_DURATION=$6 - the duration in seconds of the fade-out effect. It is also used to calculate the starting time of the fade-out effect.

FADE_IN_DURATION=$7 - same as last but for the fade-in effect.

FADE_OUT_START=$(bc -l <<< "$DURATION - $FADE_OUT_DURATION") - uses the duration and fade-out duration to calculate the exact second to start the fade-out effect. Passed into a terminal calculator program called bc.

MILLI=${OVER_START}000 - The millisecond version of the overlay animation duration. One of the filters I use needs milliseconds instead of seconds.

Filtergraph

"[0:v]setpts=PTS-STARTPTS[v0];
movie=$OVER:s=dv+da[overv][overa];
[overv]setpts=PTS-STARTPTS+$OVER_START/TB[v1];
[v0][v1]overlay=-600:0:eof_action=pass,fade=t=in:st=0:d=$FADE_IN_DURATION,fade=t=out:st=$FADE_OUT_START:d=$FADE_OUT_DURATION[mainv];
[overa]adelay=$MILLI|$MILLI,volume=0.5[a1];
[0:a:0][0:a:1][a1]amix=inputs=3:duration=longest:dropout_transition=0:weights=3 3 1[maina];
[mainv][maina][1:v][1:a]concat=n=2:v=1:a=1[outv][outa]"

[0:v]setpts=PTS-STARTPTS[v0]; - this is making sure that the main video's video stream is starting at the same 00:00:00 timestamp as the animation for proper offsetting. This might not be necessary but I'd rather make sure.

movie=$OVER:s=dv+da[overv][overa]; - loading in the sub animation's video and audio stream to be available for use in the rest of the filter graph.

[overv]setpts=PTS-STARTPTS+$OVER_START/TB[v1]; - offset the sub animation's timestamps by the OVER_START argument.

[v0][v1]overlay=-600:0:eof_action=pass - overlay the sub animation's video stream over the main video stream with an offset on the x position of -600 (bumps it over to the left).

fade=t=in:st=0:d=$FADE_IN_DURATION - adds a fade-in effect at the start of the video stream, with a duration of FADE_IN_DURATION.

fade=t=out:st=$FADE_OUT_START:d=$FADE_OUT_DURATION[mainv]; - adds a fade-out effect at the end of the video stream, starting at FADE_OUT_START and lasting FADE_OUT_DURATION.

[overa]adelay=$MILLI|$MILLI - adds a delay of MILLI milliseconds to the audio of the sub animation's audio, to sync it up with the video stream that was offset.

volume=0.5[a1]; - the sub animation's little ding sound is kinda loud, so I cut its volume in half.

[0:a:0][0:a:1][a1]amix=inputs=3:duration=longest:dropout_transition=0:weights=3 3 1[maina]; - we mix in both audio streams from the main video and the audio stream from the sub animation together into one stream. Duration says to set the combined stream's length to the length of the stream with the longest input. Dropout transition and weights are used to offset the increase in volume that occurs when the sub animation sound ends. It's not perfect but it helps.

[mainv][maina][1:v][1:a]concat=n=2:v=1:a=1[outv][outa] - finally, we take processed video and audio streams, and concat on the end of them, the video and audio streams of the outro passed into the script. I just use a blank screen with some music playing for now.

Output

-map "[outv]" -map "[outa]" $OUT

Finally, we map the fully processed video and audio stream to the output file. This way, FFmpeg will write those streams out to the file, instead of the unprocessed streams straight from the input files.

With that, we have successfully:

  • Overlaid the sub animation, at the desired time, in the main video.
  • Added a fade-in effect to the start of the video.
  • Added a fade-out effect to the end of the video.
  • Concatenated the outro to the end of the video after the fade-out effect.

Things I would like to add:

  • Color correction - Hard to do right now since I don't have consistent lighting in my office.
  • Better Outro - Something instead of a blank screen with music.
  • Get an Intro - Get a decent intro to add to the start of the video.