I won’t bore you with the details, but after several frustrating sessions of professional video editing, I discovered many of the repetitive tasks a video editor faces can be automated with FFmpeg. I have written a handful of shell scripts which I might share later, but FFmpeg came in clutch once again today for some mindless video processing.

The Situation

For the second week in a row, I was emailed a list of YouTube URL’s and timestamps of video clips to pull from these hour-long live stream recordings. My instant reaction to any repeatable pattern in mundane video editing tasks is to crack open the FFmpeg documentation and get to work on a shell script. While FFmpeg could easily handle the video editing task at hand, I also opted to include yt-dlp in this solution, since I had no local copy of the live stream recording and would need to first download that.

The Solution

Armed with yt-dlp (a fork of the ever popular youtube-dl) and FFmpeg, I set out writing a recipe for my script. Here are the ingredients:

  1. Download the YouTube video provided in the email. (yt-dlp)
  2. Store the full video as a temporary file.
  3. Use FFmpeg to trim the section of video I wish to extract.
  4. Remove the temporary video file.
  5. Preview the video clip with mpv.

A quick look at this list identifies some dependencies. They are as follows:

Now for the fun part… I took my recipe at once to my favorite text editor (or rather the one that came with my desktop environment, Kate), and started chipping away at this problem.

clipper.sh

Note: if you intend to save this to your machine for use, be sure to hit it with a sudo chmod -x clipper.sh to make it executable!

#!/bin/bash

## clipper.sh
## Script to extract best quality clips from YouTube videos. Use: ./clipper.sh [youtube hash] [start timestamp HH:MM:SS.MS] [end timestamp HH:MM:SS.MS]
## ex: ./clipper.sh dQw4w9WgXcQ 00:17.5 00:26

video="$1"
from="$2"
to="$3"
title="$(yt-dlp -e $1)"
title="${title// /_}"

yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio' --merge-output-format mp4 --output "temp.mp4" $video
ffmpeg -i temp.mp4 -ss $from -to $to -c copy -avoid_negative_ts make_zero $title-clip.mp4

rm temp.mp4
mpv --no-border $title-clip.mp4

Variables

The first 3 variables are the inputs to be used with the script. ./clipper.sh dQw4w9WgXcQ 00:17.5 00:26 will reward you with a few seconds section of the YouTube video referenced in the first argument (dQw4w9WgXcQ). You may have noticed the next 2 variables are identical. This was intentional. The first line starting title asks yt-dlp for the title of the YouTube video and saves that as a string. The line just below it replaces all the spaces in that string with underscores. I could have likely made this a one-liner, but opted for readability.

Video Download

The next line downloads the temporary video file to be edited later, aka Step #1 above. There are a few important parameters in here, which made the line sort of long and hard to interperet, so I will clarify what it is doing. The -f flag tells yt-dlp that I would like to download a certain file hosted by YouTube. The text following it selects the best quality video and audio streams that are mp4/m4a respectively. --merge-output-format mp4 muxes these separate files into one MPEG-4 file. The --output "temp.mp4" section simply names this file for use in the next step. Finally, the $video variable is used to tell yt-dlp which YouTube video we are after. Remember, this variable references the first argument of the script.

Video Clip Trimming

FFmpeg provides a very easy way to trim any video file. You simply “seek” to the start point of your clip, then specify either a duration or end point from the original video’s timecode. Since I was provided start and end points for the clips based on the original video’s timecode, that is the option I chose for this script. As an added benefit, if you are trimming a video file and using the same codec as the source, you don’t have to remux/re-encode the video file, so edits on an hour-long video take mere seconds. FFmpeg starts by taking in an input file with the -i flag, so I feed it the temporary file that was downloaded. -ss comes next. This is that handy seek feature that I use here to define the start timecode of the desired clip. -to is pretty self-explanatory, but for the sake of clarity, that is the timecode for the end point of the desired clip. The -c flag here is telling FFmpeg to copy the codec from the source to the output file. This is the time-saving part, but with some potential gotchas I will mention later. I added -avoid_negative_ts make_zero after some opinion-sifting on Stack Exchange, but found that practically, it doesn’t seem to do much. This is related to the aforementioned gotchas. The last argument FFmpeg takes is the file name. Here, I set it to that underscore-rich title variable, with a suffix of -clip.mp4 to round it out.

Clean-up & Review

If you’re at all familiar with a *NIX terminal, you’ll recognize the rm command, which is used here to remove the full length video I downloaded in the first step. To ensure the outputted file from all of this script is what I desired, I call on mpv with the -no-border flag so I can feel 1337 AF. That’s it! If the preview results in some undesired result, try running the script again with slightly tweaked timecode values.

Final Thoughts: The Cost of Speed

Okay, now time for the gotchas. Since this method does not re-encode the video, you’ll notice immediately that timecode references are not respected exactly. Instead, FFmpeg uses the nearest keyframe in the video. Keyframe intervals in video files are quite often lower resolution than what you may desire, so this script might not be the ticket for you. In the future, I might play around with the codec option of FFmpeg for more fine control. It would also be interesting to compare running time on hour-long videos. Remember, one of my goals here was to save a ton of time. It is possible that re-encoding wouldn’t add much time to the process, but I have yet to find out!

Some folks online suggest moving the -ss flag to the start of the FFmpeg command, but the drawback is that it changes the timecode reference for -to, making it act more like the -t flag. There may be a work-around to salvage the original timecode. This is also worth exploring in the future.