Created
October 9, 2019 22:42
-
-
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/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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