Skip to content

Instantly share code, notes, and snippets.

@AlfredJKwack
Created October 9, 2019 22:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlfredJKwack/6b5a30b6b0b08993748a7c2f2cfe62cf to your computer and use it in GitHub Desktop.
Save AlfredJKwack/6b5a30b6b0b08993748a7c2f2cfe62cf to your computer and use it in GitHub Desktop.
Bash script to automate compression of movie files using ffmpeg. Expects helper scripts from github.com/natelandau/dotfiles/
#!/usr/bin/env bash
_mainScript_() {
# Compresses movies with some sensible settings
# run the script with no options or -h for usage details.
header "---------[ Sensible movie compession script ]---------"
# Verify that minCompression is a number
re='^[0-9]+$'
if ! [[ $minCompression =~ $re ]] ; then
fatal "minCompression: $minCompression; This should be a number."
else
verbose "minCompression: $minCompression"
fi
# Check the input file exists
_parseFilename_ "$inputFile"
# and check if we can write to it.
if [[ -w "${_parsedFileFull-}" ]]; then
verbose "${tan}${_parsedFileFull-} is writeable${purple}"
else
fatal "${_parsedFileFull-} cannot be written to. ${purple}"
fi
# Store input file size in human readable form,
fileInputSize=$( du -h "$_parsedFileFull" | cut -f 1 ) \
&& verbose "${tan}\$fileInputSize: ${fileInputSize-}${purple}"
# and store file size in bytes.
fileInputSizeB=$( wc -c < "$_parsedFileFull" )\
&& verbose "${tan}\$fileInputSizeB: ${fileInputSizeB-}${purple}"
# Create a temporary directory and get a temporary filename.
_makeTempDir_ #sets $tmpDir
tmpFilePath=$( _uniqueFileName_ ""$tmpDir/mvCompTmp$_parseFileExt"" "-" ) \
&& verbose "${tan}\$tmpFilePath: ${tmpFilePath-}${purple}"
# Let the user know where we're at.
info "About to convert file: \"$_parsedFileFull\"."
notice "ffmpeg will now take over for a bit. This can take a while"
#run ffmpeg with some sensible settings
if [ $quiet = true ]; then
_execute_ -v "ffmpeg -i \"$_parsedFileFull\" -nostdin -y -loglevel fatal $ffmpegDefaults \"$tmpFilePath\"" \
"ffmpeg ran: ffmpeg -i \"$_parsedFileFull\" -nostdin -y -loglevel fatal $ffmpegDefaults \"$tmpFilePath\""
elif [[ $verbose = true ]]; then
_execute_ -v "ffmpeg -i \"$_parsedFileFull\" -loglevel verbose $ffmpegDefaults \"$tmpFilePath\"" \
"ffmpeg ran: ffmpeg -i \"$_parsedFileFull\" -loglevel verbose $ffmpegDefaults \"$tmpFilePath\""
else
_execute_ -v "ffmpeg -i \"$_parsedFileFull\" $ffmpegDefaults \"$tmpFilePath\"" \
"ffmpeg ran: ffmpeg -i \"$_parsedFileFull\" $ffmpegDefaults \"$tmpFilePath\""
fi
# Store output file size in human readable form,
fileOutputSize=$( du -h "$tmpFilePath" | cut -f 1 ) \
&& verbose "${tan}\$fileOutputSize: ${fileOutputSize-}${purple}"
# and store file size in bytes.
fileOutputSizeB=$( wc -c < "$tmpFilePath" ) \
&& verbose "${tan}\$fileOutputSizeB: ${fileOutputSizeB-}${purple}"
# Calculate fhe file size diffence in percentage (integer).
fileSizeChange=$( awk "BEGIN {print ((1-$fileOutputSizeB/$fileInputSizeB)*100) }" )
fileSizeChange="${fileSizeChange%%.*}"
# Is the output file smaller than the input?
if [ $fileSizeChange -le $minCompression ]; then
# No, something is off ... clean up your mess and exit.
notice "The compression is $fileSizeChange%. We were looking for at least $minCompression%. Exiting without making changes"
_safeExit_
else
# Yes, lets go ahead and replace the original file with the output from ffmpeg.
notice "The compressed file is $fileOutputSize and the original was $fileInputSize. You will gain $fileSizeChange%."
_execute_ "/bin/mv \"$tmpFilePath\" \"$_parsedFileFull\"" "Replacing original file with the compressed one."
fi
# Let the user know we're all done and exit stage left.
if [ $quiet = false ]; then
_execute_ -s "afplay /System/Library/Sounds/Submarine.aiff -v 10" "All done."
fi
_safeExit_
} # end _mainScript_
_sourceHelperFiles_() {
# DESC: Sources script helper files.
local filesToSource
local sourceFile
filesToSource=(
"${HOME}/dotfiles/scripting/helpers/baseHelpers.bash"
"${HOME}/dotfiles/scripting/helpers/arrays.bash"
"${HOME}/dotfiles/scripting/helpers/files.bash"
"${HOME}/dotfiles/scripting/helpers/macOS.bash"
"${HOME}/dotfiles/scripting/helpers/numbers.bash"
"${HOME}/dotfiles/scripting/helpers/services.bash"
"${HOME}/dotfiles/scripting/helpers/textProcessing.bash"
"${HOME}/dotfiles/scripting/helpers/dates.bash"
)
for sourceFile in "${filesToSource[@]}"; do
[ ! -f "${sourceFile}" ] \
&& {
echo "error: Can not find sourcefile '$sourceFile'."
echo "exiting..."
exit 1
}
source "${sourceFile}"
done
}
_sourceHelperFiles_
# Set initial flags & defaults
quiet=false
printLog=false
logErrors=true
verbose=false
force=false
dryrun=false
minCompression=10
ffmpegDefaults="-c:v libx264 -c:a copy -crf 23"
declare -a args=()
_usage_() {
cat <<EOF
${bold}NAME${reset}
$(basename "$0") -- compress video files
${bold}SYNOPSIS ${reset}
${bold}$(basename "$0")${reset} [${bold}-h${reset}] [${bold}-l${reset}] [${bold}-L${reset}] [${bold}-n${reset}] [${bold}-n${reset}] [${bold}-q${reset}] [${bold}-v${reset}] [${bold}--force${reset}] source file
${bold}DESCRIPTION${reset}
This script compresses movies with some sensible settings. Specifically it
will encode all video streams with libx264 and copy all audio streams. The
quality/size tradeoff has been set to ensure no perceptible loss of
quality occurs. The script will replace the file targeted for processing.
The script will stop processing if any issues occor or in the event the
compression ratio did not meet expectations.
The typical use case for this scipt is unprocessed video files that come out
of your phone, dslr or go-pro wich you intend to keep for archival purposes
and are not intent on processing further.
${white}Options:${reset}
${bold}-i${reset}, ${bold}--inputFile${reset} Path to movie which you want to process
$ $(basename "$0") --inputFile '/my movie/file.mov'
${bold}-h${reset}, ${bold}--help${reset} Display this help and exit
${bold}-m${reset}, ${bold}--minCompression${reset} Minimal compression % we want before replacing the
original file. This is expressed as an integer with no
percent sign.
${bold}-l${reset}, ${bold}--log${reset} Print log to file with all log levels
${bold}-L${reset}, ${bold}--noErrorLog${reset} Default behavior is to print log level error and fatal to
a log. Use this flag to generate no log files at all.
${bold}-n${reset}, ${bold}--dryrun${reset} Non-destructive. Makes no permanent changes.
${bold}-q${reset}, ${bold}--quiet${reset} Quiet (no output). Actions will still appear and interrupt.
${bold}-v${reset}, ${bold}--verbose${reset} Output more information. (Items echoed to 'verbose')
${bold}--force${reset} Skip all user interaction. Implied 'Yes' to all actions.
${bold}DEPENDENCIES${reset}
This script has a few dependencies and expectations without which it will just
fail to run.
You will want to have:
- ${bold}Helper scripts${reset} from Nate Landau installed in the directory
${HOME}/dotfiles/scripting/helpers/
You can obtain these, along with a lot of other goodies from
https://github.com/natelandau/dotfiles/
- ${bold}The ffmpeg library${reset} to perform the video encoding You can obtain
this from https://ffmpeg.org/download.html or if you have homebrew
installed you can just issue a 'brew install ffmpeg' to get you set up.
EOF
}
_parseOptions_() {
# Iterate over options
# breaking -ab into -a -b when needed and --foo=bar into --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i = 1; i < ${#1}; i++)); do
c=${1:i:1}
options+=("-$c") # Add current char to options
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring == *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Read the options and set stuff
while [[ ${1-} == -?* ]]; do
case $1 in
-h | --help)
_usage_ >&2
_safeExit_
;;
-i | --inputFile)
shift
inputFile=${1}
;;
-m | --minCompression)
shift
minCompression=${1}
;;
-L | --noErrorLog) logErrors=false ;;
-n | --dryrun) dryrun=true ;;
-v | --verbose) verbose=true ;;
-l | --log) printLog=true ;;
-q | --quiet) quiet=true ;;
--force) force=true ;;
--endopts)
shift
break
;;
*) die "invalid option: '$1'." ;;
esac
shift
done
args+=("$@") # Store the remaining user input as arguments.
}
# Initialize and run the script
trap '_trapCleanup_ $LINENO $BASH_LINENO "$BASH_COMMAND" "${FUNCNAME[*]}" "$0" "${BASH_SOURCE[0]}"' \
EXIT INT TERM SIGINT SIGQUIT
set -o errtrace # Trap errors in subshells and functions
set -o errexit # Exit on error. Append '||true' if you expect an error
set -o pipefail # Use last non-zero exit code in a pipeline
# shopt -s nullglob globstar # Make `for f in *.txt` work when `*.txt` matches zero files
IFS=$' \n\t' # Set IFS to preferred implementation
# set -o xtrace # Run in debug mode
set -o nounset # Disallow expansion of unset variables
[[ $# -eq 0 ]] && _parseOptions_ "-h" # Force arguments when invoking the script
_parseOptions_ "$@" # Parse arguments passed to script
# _makeTempDir_ "$(basename "$0")" # Create a temp directory '$tmpDir'
# _acquireScriptLock_ # Acquire script lock
_mainScript_ # Run script
_safeExit_ # Exit cleanly
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment