Shell peer review for ffmpeg shell scripts

I have written some ffmpeg shell scripts to process audio and video,
and perform some tasks you would normally need to use a video editor for.

If anyone has a free moment for some peer review or feedback that would be great,
i have already used shellcheck on all the scripts but if anyone can spot anything i missed let me know

Here is a list of the scripts and what they do
  • combine-clips
  • ebu-meter
  • fade-clip
  • fade-normalize
  • fade-title
  • loudnorm
  • normalize
  • subtitle-add
  • trim-clip
  • waveform
combine-clips

combine video and audio files into new clip

Suppose you have extracted the audio from a video clip to clean up with a program like audacity
and you want to reattach the new clean audio to the original video track.

Normally you would open a video editor import the original video, delete the audio track import the clean audio and export the video

The combine-clips script accepts a video file and audio file,
and then combines the original video with the new audio file and saves it in a new video file.

The script will convert the audio to aac if it is a different codec otherwise it will just copy the audio and video into a new video file

  • script usage

combine-clip -v video.(mp4|mov|mkv|m4v) -a audio.(m4a|aac|wav|mp3)


ebu-meter

The ebu-meter uses ffplay to display a real time audio meter using the ebu123 loudness standard

Note the Freebsd ffmpeg package doesn't include ffplay so you need to use poudriere to build a custom version of ffmpeg and enable SDL to build and install ffplay

  • script usage

ebu-meter -i infile.(mp4|mov|mkv|m4v|m4a|aac|wav|mp3)


fade-clip

fade video and audio in and out

  • script usage

fade-clip -i video.(mp4|mkv|mov|m4v)


fade-normalize

fade video and audio in and out and normalize the audio

The fade-normalize script fades the audio and video in and out and also normalizes the audio using the ebu128 standard,
use a noise gate and compressor, high and low audio pass, white noise removal, click removal and deesser

  • script usage

fade-normalize -i video.(mp4|mkv|mov|m4v)


fade-title

fade video and audio in and out, normalize and create video title from filename

The fade-title script fades the audio and video in and out and also normalizes the audio using the ebu128 standard,
use a noise gate and compressor, high and low audio pass, white noise removal, click removal and deesser

It then uses the ffmpeg drawtext and drawbox filters to create a lower third title using the video's filename for the title,
the text using the drawtext filter is faded in and out,
but the lower third bar which uses the drawbox filter doesnt support any opacity options to fade at the moment

  • script usage

fade-title -i video.(mp4|mkv|mov|m4v)


loudnorm

The loudnorm script analyzes a video or audio file and gives you stats about the loudness of the file using lufs and ebu128 standard

  • script usage

loudnorm -i infile.(mkv|mp4|mov|m4v|m4a|aac|wav|mp3)


normalize

normalize audio levels in a audio or video file to the ebu128 standard

  • script usage

normalize -i infile.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3)


subtitle-add

add subtitles to a video file

The subtitle-add script lets you add a srt subtitles to a video file as a track that can be enabled or disabled by the user,
note this isnt hard subs burned on top of the video.

The video file with the subtitle track works in all players i have tested including Quicktime on the Mac

  • script usage

subtitle-add -v video.(mp4|mov|mkv|m4v) -s subtitle.srt


trim-clip

The trim-clip script lets you trim a video clip by specifying the start point and the number of seconds after the start point to create a new clip from

  • script usage

trim-clip -ss 00:00:00 -i video.(mp4|mov|mkv|m4v) -t 00:00:00


waveform

create a waveform from an audio or video file and save as a png

  • script usage

waveform -i infile.(mp4|mkv|mov|m4v|wav|aac|m4a|mp3)


fade-title code

This is the code for the fade-title script,
all of the ffmpeg shell scripts are on github

Bash:
#!/bin/sh

# fade video, audio add title from video filename

# script usage
script_usage="$(basename "$0") -i infile.(mp4|mkv|mov|m4v)"

# error messages
HOME_ERR='HOME directory not set or null'

# check arguments passed to script
if [ $# -eq 2 ]; then
   {
   [ "$1" = '-i' ] && \
   [ -f "$2" ]
   } || { echo "$script_usage" && exit; }
else
   { echo "$script_usage" && exit; }
fi

# infile, infile name and file extension
infile="$2"
infile_nopath="${infile##*/}"
infile_name="${infile_nopath%.*}"
infile_ext="${infile##*.}"

# file command check input file mime type
filetype="$(file --mime-type -b "$infile")"

# video mimetypes
mov_mime='video/quicktime'
mkv_mime='video/x-matroska'
mp4_mime='video/mp4'
m4v_mime='video/x-m4v'

# check the files mime type
case "$filetype" in
    ${mov_mime}|${mkv_mime}|${mp4_mime}|${m4v_mime});;
    *) { echo "$script_usage" && exit; };;
esac

# file extension regular expressions for case statement
mp4='[Mm][Pp]4'
mkv='[Mm][Kk][Vv]'
mov='[Mm][[Oo][Vv]'
m4v='[Mm]4[Vv]'

# check the file extension
case "$infile_ext" in
    ${mp4}|${mkv}|${mov}|${m4v});;
    *) { echo "$script_usage" && exit; };;
esac

# outfile file recording destination
normalized_file="${HOME:?${HOME_ERR}}/Desktop/${infile_name}-$(date +"%Y-%m-%d-%H-%M-%S").mp4"

# print analyzing file
echo '+ Analyzing file with ffmpeg'

# ffmpeg loudnorm get stats from file
normalize=$(ffmpeg \
-hide_banner \
-i "$infile" \
-af "loudnorm=I=-16:dual_mono=true:TP=-1.5:LRA=11:print_format=summary" \
-f null - 2>&1 | tail -n 12)

# read the output of normalize line by line and store in variables
for line in "$normalize"; do
    measured_I=$(echo "$line" | awk -F' ' '/Input Integrated:/ {print $3}')
    measured_TP=$(echo "$line" | awk -F' ' '/Input True Peak:/ {print $4}')
    measured_LRA=$(echo "$line" | awk -F' ' '/Input LRA:/ {print $3}')
    measured_thresh=$(echo "$line" | awk -F' ' '/Input Threshold:/ {print $3}')
    offset=$(echo "$line" | awk -F' ' '/Target Offset:/ {print $3}')
done

# video duration and video offset minus 1 second for fade out
video_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$infile" | cut -d\. -f1)
vid_offset=$(echo "${video_dur}-1" | bc -l)

# video height
video_size=$(ffprobe -v error -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "$infile")

# video title from filename
title="$infile_name"

# video title variables
font="OpenSans-Regular.ttf"
font_color="white"
boxcolor="black@0.4"

# video title fade
DS=2.0 # display start
DE=16.0 # display end, number of seconds after start
FID=1.5 # fade in duration
FOD=1.5 # fade out duration

# calculate drawbox and drawtext size based on video height
case "$video_size" in
    1080) # 1080 height
    drawbox_height=$(printf "%s\n" "${video_size}/13.4" | bc)
    drawtext_size=$(printf "%s\n" "${drawbox_height}/2" | bc)
    ;;
    720) # 720 height
    drawbox_height=$(printf "%s\n" "${video_size}/9" | bc)
    drawtext_size=$(printf "%s\n" "${drawbox_height}/2" | bc)
    ;;
    *) # all other heights
    drawbox_height=$(printf "%s\n" "${video_size}/9" | bc)
    drawtext_size=$(printf "%s\n" "${drawbox_height}/2" | bc)
    ;;
esac

# drawbox, drawtext size
boxheight="$drawbox_height"
font_size="$drawtext_size"

# video function
video () {
    ffmpeg \
    -hide_banner \
    -stats -v panic \
    -i "$infile" \
    -filter_complex \
    "[0:a] afade=t=in:st=0:d=1,afade=t=out:st='$vid_offset':d=1,
    compand=attacks=0:points=-70/-90|-24/-12|0/-6|20/-6,
    highpass=f=60,
    lowpass=f=13700,
    afftdn=nt=w,
    adeclick,
    deesser,
    loudnorm=I=-16:
    dual_mono=true:
    TP=-1.5:
    LRA=11:
    measured_I=${measured_I}:
    measured_LRA=${measured_LRA}:
    measured_TP=${measured_TP}:
    measured_thresh=${measured_thresh}:
    offset=${offset}:
    linear=true:
    print_format=summary [audio];
    [0:v] fade=t=in:st=0:d=1,fade=t=out:st='$vid_offset':d=1, \
    format=yuv444p,
    drawbox=enable='between(t,${DS},${DE})':
    y=(ih-h/PHI)-(${boxheight}):
    color=${boxcolor}:
    width=iw:height=${boxheight}:t=fill,
    drawtext=fontfile=${font}:
    text=${title}:
    fontcolor=${font_color}:fontsize=${font_size}:
    x=20:
    y=h-(${boxheight})-(${boxheight}/2)+th/4:
    :fontcolor_expr=fdfdfd%{eif\\\\: clip(255*(1*between(t\\, $DS + $FID\\, $DE - $FOD) + ((t - $DS)/$FID)*between(t\\, $DS\\, $DS + $FID) + (-(t - $DE)/$FOD)*between(t\\, $DE - $FOD\\, $DE) )\\, 0\\, 255) \\\\: x\\\\: 2 }, \
     format=yuv420p[video]" \
    -map "[video]" -map "[audio]" \
    -c:a aac \
    -c:v libx264 -preset fast \
    -profile:v high \
    -crf 18 -coder 1 \
    -pix_fmt yuv420p \
    -movflags +faststart \
    -f mp4 \
    "$normalized_file"
}

# run the audio or video function based on the file extension
case "$infile_ext" in
    ${mp4}|${mkv}|${mov}|${m4v}) video "$infile";;
    *) { echo "$script_usage" && exit; };;
esac

Lower third video title example

This is a screen shot of Big Bug Bunny with a lower third titles using the fade-title script

7150
 
Back
Top