PDF: cut-video.pdf
Paulo Jerônimo in Brasília on December 12, 2022: I created a Bash script for video trimming and compression using FFmpeg and HandBrakeCLI. It can be helpful if you are like me: I use command line tools to do everything in my daily work, including video transformations! ;)
Please, also note that I created this article post to use for recording my first video in English. I am a Brazilian, developing in Bash since the early 2000s, but I never recorded any video (in English) about this. So I decided to create a new Bash script, and a related article, to use as input for recording this video.
1. The cut-video script
The cut-video
script is a 55 lines Bash script that I wrote to do
video cuttings and compression using FFmpeg and HandBrakeCLI.
55 is a "magic" Fibonacci number and I love this kind of numbers! See another project that I developed (in JavaScript/ React): https://finisher.tech/fibonacci-app. |
I will explain the code below. In order to do this, I put some tags inside the code (using comments in the format "# ref-string") and will explain all of them.
#!/usr/bin/env bash
# Author: Paulo Jerônimo (paulojeronimo.com)
# Links:
# - Article | https://paulojeronimo.com/posts/cut-video
# - Video | https://youtube.com/@PauloJeronimo
set -eou pipefail # ref-set
cd "$(dirname "$0")" # ref-cd-to-the-script-location
script_name=$(basename "$0" .sh)
source ./.$script_name 2> /dev/null || { \
source ~/.$script_name 2> /dev/null || :; } # ref-source
clips_file=${clips_file:-clips.csv} # ref-clips_file
video_var=${video_var:-${PWD##*/}} # ref-video_var
mkv_compression=${mkv_compression:-false}
mkv_tool=${mkv_tool:-HandBrakeCLI} #ref-mkv_tool
$mkv_compression && \
echo MKV compression will be made by $mkv_tool! || \
mkv_tool=
go_ahead=true # ref-go_ahead
for t in ffmpeg $mkv_tool
do
command -v $t > /dev/null || { # ref-command-isnt-in-path
echo Command \"$t\" not found ...
go_ahead=false
}
done
for f in "$clips_file" original-video
do
[ -f "$f" ] || { # ref-file-not-found
echo File \"$f\" not found ...
go_ahead=false
}
done
$go_ahead || { # ref-abort
echo Aborting due to the problems reported above!
exit 1
}
log=$script_name.log; > $log # ref-log
while IFS=, read -r ss to video # ref-read-csv
do
gen_file=$(sed "s,\$video,$video_var,g" <<< "$video") #ref-gen_file
echo ffmpeg -nostdin -y -i original-video \
-ss $ss -to $to -c:v copy \"$gen_file\" | tee last-cmd # ref-ffmpeg
sed 's/^/\$ /g' last-cmd >> $log; bash < last-cmd &>> $log
! $mkv_compression || { # ref-mkv_compression
[ -f ./$mkv_tool.$$ ] || echo "#!/usr/bin/env bash" > $mkv_tool.$$
[ "$mkv_tool" = HandBrakeCLI ] && \
cmd="$mkv_tool -i \"$gen_file\" -o \"${gen_file%.*}.mkv\"" || \
cmd="$mkv_tool \"$gen_file\""
echo -e "tee last-cmd <<< '$cmd'\nsed 's/^/\$ /g' last-cmd >> \
$log\nbash < last-cmd &>> $log" >> $mkv_tool.$$
}
done < "$clips_file"
! $mkv_compression || {
mv -f ./$mkv_tool.$$ ./$mkv_tool; chmod +x ./$mkv_tool; ./$mkv_tool; }
# vim: tabstop=2 shiftwidth=2 colorcolumn=72
The more recent version of this script, maybe different and with more
features, is also available in my dotfiles
GitHub repository, here:
https://github.com/paulojeronimo/dotfiles/.scripts/cut-video.
2. Details in the cut-video script
the first line
In most of all Unix scripts, the first line contains an interpreter
directive called [shebang].
It "is like a comment" in the first line but, actually, it is a
character sequence #! …
used by the program loader to interpret the
rest of the script.
The loader executes the specified interpreter, passing it as an argument
to the path initially used when attempting to run the script.
See more details in the references section.
the comment lines in the header
Sometimes I like to write the header of my scripts using another structured text. In this case, it is written in YAML format. You can test this by typing the following command on your terminal (assuming you have yq installed):
yq -o json <(sed -n '2,5p' cut-video | sed 's,^# ,,g')
This will be the output:
{
"Author": "Paulo Jerônimo (paulojeronimo.com)",
"Links": [
"Article | https://paulojeronimo.com/posts/cut-video",
"Video | https://youtube.com/@PauloJeronimo"
]
}
ref-set
TODO → Finish this topic.
ref-cd-to-the-script-location
TODO → Finish this topic.
ref-source
TODO → Finish this topic.
ref-clips_file
The clips_file variable is file name, with a Comma Separated Virgula (CSV) content, that will be used to determine the positions where the file original-file (a link to the file that will be processed) will be cutted.
Here is a sample content for this file:
00:00:00,00:05:33,$video.1 - nothing-else-matters.mp4
00:05:34,00:12:43,$video.2 - master-of-pupets.mp4
00:12:44,00:18:09,$video.3 - fade-to-black.mp4
00:18:10,00:23:18,$video.4 - the-unforgiven.mp4
00:23:19,00:30:20,$video.5 - one.mp4
ref-video_var
The video_var variable will be configured to get last part of the
name of the current directory (indicated by the $PWD
), and will be
used to compose the name of the generated file in the cutting (made by
FFmpeg) and also in the compression (made by HandBrakeCLI).
ref-mkv_tool
TODO → Finish this topic.
ref-go_ahead
The code below this comment will vefify if we have the conditions to run this script satisfied.
The first validation (ref-command-isnt-in-path) will verify if the
required commands (FFmpeg and HandBrakeCLI) are available in the
PATH
.
The second validation (ref-file-not-found) will check if the files
($clips_file
and original-video
) are present on the current
directory.
If one of these conditions aren’t satisfied, the go_ahead
variable
will be false
.
In the ref-abort block, executed if $go_ahead
is false, the script
will print an aborting message and terminate its execution with an error
code 1.
ref-log
TODO → Finish this topic.
ref-read-csv
This while
will loop through the contents of the CSV ($clips_file
).
TODO → Finish this topic.
3. Testing
In order to do the next tests with cut-video, you can copy and paste the following commands directly into your shell. I’ll be using two environments in these tests: Termux and Linux.
3.1. Test 1
To follow the steps that I will show you, you will need the following tools installed:
I will demonstrate the commands below by doing this on my own mobile phone and using Termux.
To replicate the commands inside Termux, like me, first you will need to run the following script on it:
|
Let’s download a video containing
some piano musics that I like (in low quality definition to speed up
our test) and create a link called original-file
to it:
d=/tmp/mettalica-on-the-piano; rm -rf $d; mkdir $d && cd $_ && \
yt-dlp -f 160 'https://www.youtube.com/watch?v=he_o9LmXYwg' && \
ln -sf "$(echo *.mp4)" original-video
After that, we’ll download the cut-video script and make it executable:
s=cut-video; curl -sSL TODO(url)/$s -o $s; chmod +x $s
Before calling this script we need to create a file called clips.csv
with the following command:
cat <<'EOF'>clips.csv
00:00:00,00:05:33,$video - 1.nothing-else-matters.mp4
00:05:34,00:12:43,$video - 2.master-of-pupets.mp4
00:12:44,00:18:09,$video - 3.fade-to-black.mp4
00:18:10,00:23:18,$video - 4.the-unforgiven.mp4
00:23:19,00:30:20,$video - 5.one.mp4
EOF
We can now call execute the cut-video script:
./cut-video
If everything goes weel, the tree of files created in this testing will be equals to the output shown in command below:
$ tree . |-- Best Metallica songs on the piano [he_o9LmXYwg].mp4 |-- clips.csv |-- cut-video |-- cut-video.log |-- last-cmd |-- metallica-on-the-piano - 1.nothing-else-matters.mp4 |-- metallica-on-the-piano - 2.master-of-pupets.mp4 |-- metallica-on-the-piano - 3.fade-to-black.mp4 |-- metallica-on-the-piano - 4.the-unforgiven.mp4 |-- metallica-on-the-piano - 5.one.mp4 `-- original-video -> Best Metallica songs on the piano [he_o9LmXYwg].mp4 0 directories, 11 files