How I Completely Automated My YouTube Editing
Tags: linux, productivity, video, ffmpeg
Published: 2020-05-04 21:53:18

{% youtube JbX5vDfiyh0 %}

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, whats that? You want to see the magic? I got you fam.

## Script

```sh
#!/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
```

Thats a chunky boy, so let me break down what exactly is happening.

## Arguments

```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
```

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. Its 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. Its using **ffprobe** to grab the metadata in a specific format.

Here is the **get_vid_duration** script for reference:
```sh
#!/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 milliseconds version of the overlay animation duration. One of the filters I use needs milliseconds instead of seconds.

## Filtergraph

```sh
"[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 filtergraph.

- `[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 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 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. Its 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 script. I just use a blank screen with some music playing for now.

## Output

```sh
-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:

- [x] Overlaid the sub animation, at the desired time, in the main video.
- [x] Added a fade in effect to the start of the video.
- [x] Added a fade out effect to the end of the video.
- [x] 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.