mirror of
https://github.com/natelandau/shell-scripting-templates.git
synced 2025-11-12 06:53:48 -05:00
810 lines
30 KiB
Bash
Executable File
810 lines
30 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# ##################################################
|
|
# My Generic BASH script template
|
|
#
|
|
version="1.1.1" # Sets version variable for this script
|
|
#
|
|
scriptTemplateVersion="1.3.0" # Version of scriptTemplate.sh that this script is based on
|
|
#
|
|
#
|
|
# HISTORY:
|
|
#
|
|
# * 2015-03-31 - v1.0.0 - First creation
|
|
# * 2015-04-07 - v1.1.0 - Added support for music files
|
|
# * 2015-05-26 - v1.1.1 - Fixed log level on downsize720
|
|
#
|
|
# ##################################################
|
|
|
|
# Provide a variable with the location of this script.
|
|
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
|
|
# Source Scripting Utilities
|
|
# -----------------------------------
|
|
# These shared utilities provide many functions which are needed to provide
|
|
# the functionality in this boilerplate. This script will fail if they can
|
|
# not be found.
|
|
# -----------------------------------
|
|
|
|
utilsLocation="${scriptPath}/../lib/utils.sh" # Update this path to find the utilities.
|
|
|
|
if [ -f "${utilsLocation}" ]; then
|
|
source "${utilsLocation}"
|
|
else
|
|
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
|
|
exit 1
|
|
fi
|
|
|
|
# trapCleanup Function
|
|
# -----------------------------------
|
|
# Any actions that should be taken if the script is prematurely
|
|
# exited. Always call this function at the top of your script.
|
|
# -----------------------------------
|
|
function trapCleanup() {
|
|
echo ""
|
|
if is_dir "${tmpDir}"; then
|
|
rm -r "${tmpDir}"
|
|
fi
|
|
die "Exit trapped." # Edit this if you like.
|
|
}
|
|
|
|
# Set Flags
|
|
# -----------------------------------
|
|
# Flags which can be overridden by user input.
|
|
# Default values are below
|
|
# -----------------------------------
|
|
quiet=0
|
|
printLog=0
|
|
verbose=false
|
|
force=0
|
|
strict=0
|
|
debug=0
|
|
safeRun=0
|
|
downsize720=0
|
|
deleteOriginal=0
|
|
XLD=0
|
|
args=()
|
|
|
|
# Set Temp Directory
|
|
# -----------------------------------
|
|
# Create temp directory with three random numbers and the process ID
|
|
# in the name. This directory is removed automatically at exit.
|
|
# -----------------------------------
|
|
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
|
|
(umask 077 && mkdir "${tmpDir}") || {
|
|
die "Could not create temporary directory! Exiting."
|
|
}
|
|
|
|
# Logging
|
|
# -----------------------------------
|
|
# Log is only used when the '-l' flag is set.
|
|
#
|
|
# To never save a logfile change variable to '/dev/null'
|
|
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
|
|
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
|
|
# -----------------------------------
|
|
logFile="$HOME/Library/Logs/${scriptBasename}.log"
|
|
|
|
# Check for Dependencies
|
|
# -----------------------------------
|
|
# Arrays containing package dependencies needed to execute this script.
|
|
# The script will fail if dependencies are not installed. For Mac users,
|
|
# most dependencies can be installed automatically using the package
|
|
# manager 'Homebrew'.
|
|
# -----------------------------------
|
|
homebrewDependencies=(ffmpeg jq rename)
|
|
|
|
function mainScript() {
|
|
############## Begin Script Here ###################
|
|
####################################################
|
|
|
|
# file extension mappings - ie - are we working with a video or music file....
|
|
videoTypes=(mp4 mov avi mkv wmv flv ogg m4p m4v 3gp divx h264)
|
|
audioTypes=(mp3 m4a aiff aac m4p wav wma flac)
|
|
|
|
function breakLoop() {
|
|
# Break the for loop when a user specifies a file from the CLI.
|
|
# Basically, this ensures that we only run the loop once for a single file.
|
|
if [[ -n "${userFile}" ]]; then
|
|
break
|
|
fi
|
|
}
|
|
|
|
function outputDir() {
|
|
if $verbose; then v="-v" ; fi
|
|
|
|
if [[ -n "${saveDir}" ]]; then
|
|
if [[ -e "${saveDir}" && ! -d "${saveDir}" ]]; then
|
|
die "${saveDir} exists but is not a directory"
|
|
fi
|
|
if [[ ! -d "${saveDir}" ]]; then
|
|
seek_confirmation "${saveDir} does not exist. Create?"
|
|
if is_confirmed; then
|
|
mkdir $v "${saveDir}"
|
|
else
|
|
die "Can't run without a place to save the files."
|
|
fi
|
|
fi
|
|
# remove trailing slash if included. Add back to ensure it's always there.
|
|
outputDir="${saveDir%/}/" && verbose "outputDir=${saveDir%/}/"
|
|
fi
|
|
}
|
|
|
|
function identifyUserFile() {
|
|
# If a user specifies a single file type extension, respect it.
|
|
if [ -n "${MEDIATYPE}" ]; then
|
|
if [[ "${videoTypes[*]}" =~ "${MEDIATYPE}" ]]; then
|
|
videoTypes=($MEDIATYPE) # Reset the video type array to user-input, if specified
|
|
fi
|
|
if [[ "${audioTypes[*]}" =~ "${MEDIATYPE}" ]]; then
|
|
audioTypes=($MEDIATYPE) # Reset the audio type array to user-input, if specified
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${args}" ]]; then
|
|
userFile="${args}"
|
|
fi
|
|
|
|
if [[ -n "${userFile}" ]]; then
|
|
#test -f "${f}" # Ensure that what we've found is a file
|
|
extension="${userFile##*.}" # Grab file extension of input file
|
|
if [[ "${videoTypes[*]}" =~ "${extension}" ]]; then
|
|
userVideoFile="${userFile}"
|
|
fi
|
|
if [[ "${audioTypes[*]}" =~ "${extension}" ]]; then
|
|
userAudioFile="${userFile}"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function userFormat() {
|
|
# Reads user input for format (-o, --output)
|
|
# Override defaults with CLI
|
|
if [ -n "$userOutput" ]; then
|
|
outputFormat="${userOutput,,}" && verbose "outputFormat=${outputFormat}"
|
|
fi
|
|
}
|
|
|
|
function doConvert() {
|
|
|
|
# Set the output name, format, and directory
|
|
# ###############################################
|
|
|
|
if $verbose; then v="-v" ; fi
|
|
|
|
# Set output filename
|
|
output="$(basename "${f%.*}").$outputFormat" && verbose "output="${output}""
|
|
|
|
# Add users output save directory if used
|
|
if [[ -n "${outputDir}" ]]; then
|
|
output="${outputDir}${output}" && verbose "output=${outputDir}${output}"
|
|
fi
|
|
|
|
# Confirm we're not overwriting an existing file
|
|
if [[ "${safeRun}" -ne "1" ]]; then
|
|
if [ -e "${output}" ]; then
|
|
# rename the new file to '.new'
|
|
output="$(basename "${f%.*}").new."${outputFormat}"" && verbose "Adding '.new' to the new file name"
|
|
if [[ -n "${outputDir}" ]]; then
|
|
output="${outputDir}${output}" && verbose "output=${outputDir}${output}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Respect the 'Quiet' flag
|
|
if [[ "${quiet}" == "1" ]]; then
|
|
ffquiet="-loglevel quiet"
|
|
fi
|
|
|
|
# # Respect the 'logfile' flag
|
|
# if [[ "${printLog}" == "1" ]]; then
|
|
# ffmpegLog=">> ${logFile}"
|
|
# fi
|
|
|
|
# Invoke the conversion
|
|
# ##################################
|
|
|
|
# Use XLD for audio file conversion if available
|
|
if [[ "$XLD" == "1" ]]; then
|
|
verbose "Running XLD commands for audio. No FFMPEG"
|
|
# Respect --safe flag.
|
|
if [[ "${safeRun}" == "1" ]]; then
|
|
notice "xld -o "${output}" ${audioConvertCommand} "${f}""
|
|
else
|
|
verbose "xld -o "${output}" ${audioConvertCommand} "${f}""
|
|
xld -o "${output}" ${audioConvertCommand} "${f}"
|
|
fi
|
|
else # Use ffmpeg when XLD is set to 0
|
|
# Respect --safe flag.
|
|
if [[ "${safeRun}" == "1" ]]; then
|
|
notice "ffmpeg -i "${f}" ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} "${output}" ${ffquiet}"
|
|
else
|
|
verbose "ffmpeg -i "${f}" ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} "${output}" ${ffquiet}"
|
|
ffmpeg -i "${f}" ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} "${output}" ${ffquiet}
|
|
|
|
# delete original if requested and remove '.new' from new file
|
|
if [[ "${deleteOriginal}" == "1" ]]; then
|
|
rm -f $v "${f}"
|
|
#remove '.new' from filename
|
|
for file in *.new.*; do
|
|
rename $v 's/.new//g' "${file}"
|
|
done
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
convertVideo() {
|
|
for vt in "${videoTypes[@]}"; do
|
|
for f in *."${vt}"; do
|
|
if [[ -n "${userVideoFile}" ]]; then
|
|
# Override the file search if user specifies a specific file from CLI.
|
|
f="${userVideoFile}"
|
|
fi
|
|
test -f "${f}" || continue # Ensure that what we've found is a file
|
|
extension="${f##*.}" # Grab file extension of input file
|
|
informationFile="${tmpDir}/${f////.}.json"
|
|
|
|
# JSON METADATA FOR EACH ASSET
|
|
######################################################################
|
|
verbose "Reading JSON and writing to TMP"
|
|
|
|
|
|
# Output a JSON file for each video asset being parsed.
|
|
ffprobe -v quiet -print_format json -show_format -show_streams "${f}" >> "${informationFile}"
|
|
# uncomment the line below for debugging. It will write a json file for each file in the source directory
|
|
# ffprobe -v quiet -print_format json -show_format -show_streams "${f}" >> "${f}.json"
|
|
|
|
# Read the necessary information from the JSON
|
|
format="$(jq -r ".format.format_long_name" "${informationFile}")"
|
|
formatName="$(jq -r ".format.format_name" "${informationFile}")"
|
|
|
|
if [[ $(jq -r ".streams[0].codec_type" "${informationFile}") == "video" ]]; then
|
|
videoHeight="$(jq -r ".streams[0].height" "${informationFile}")"
|
|
videoWidth="$(jq -r ".streams[0].width" "${informationFile}")"
|
|
videoCodec="$(jq -r '.streams[0].codec_name' "${informationFile}")"
|
|
videoCodecLong="$(jq -r ".streams[0].codec_long_name" "${informationFile}")"
|
|
elif [[ $(jq -r ".streams[1].codec_type" "${informationFile}") == "video" ]]; then
|
|
videoHeight="$(jq -r ".streams[1].height" "${informationFile}")"
|
|
videoWidth="$(jq -r ".streams[1].width" "${informationFile}")"
|
|
videoCodec="$(jq -r '.streams[1].codec_name' "${informationFile}")"
|
|
videoCodecLong="$(jq -r ".streams[1].codec_long_name" "${informationFile}")"
|
|
elif [[ $(jq -r ".streams[2].codec_type" "${informationFile}") == "video" ]]; then
|
|
videoHeight="$(jq -r ".streams[2].height" "${informationFile}")"
|
|
videoWidth="$(jq -r ".streams[2].width" "${informationFile}")"
|
|
videoCodec="$(jq -r '.streams[2].codec_name' "${informationFile}")"
|
|
videoCodecLong="$(jq -r ".streams[2].codec_long_name" "${informationFile}")"
|
|
else
|
|
warning "Missing video information for '"$f"'. Inspecting with 'ffprobe'."
|
|
ffprobe -v quiet -print_format json -show_format -show_streams "${f}"
|
|
safeExit
|
|
fi
|
|
if [ $(jq -r ".streams[0].codec_type" "${informationFile}") == "audio" ]; then
|
|
audioCodec="$(jq -r '.streams[0].codec_name' "${informationFile}")"
|
|
audioCodecLong="$(jq -r ".streams[0].codec_long_name" "${informationFile}")"
|
|
audioSampleRate="$(jq -r ".streams[0].sample_rate" "${informationFile}")"
|
|
audioBitRate="$(jq -r ".streams[0].bit_rate" "${informationFile}")"
|
|
elif [ $(jq -r ".streams[1].codec_type" "${informationFile}") == "audio" ]; then
|
|
audioCodec="$(jq -r '.streams[1].codec_name' "${informationFile}")"
|
|
audioCodecLong="$(jq -r ".streams[1].codec_long_name" "${informationFile}")"
|
|
audioSampleRate="$(jq -r ".streams[1].sample_rate" "${informationFile}")"
|
|
audioBitRate="$(jq -r ".streams[1].bit_rate" "${informationFile}")"
|
|
elif [ $(jq -r ".streams[2].codec_type" "${informationFile}") == "audio" ]; then
|
|
audioCodec="$(jq -r '.streams[2].codec_name' "${informationFile}")"
|
|
audioCodecLong="$(jq -r ".streams[2].codec_long_name" "${informationFile}")"
|
|
audioSampleRate="$(jq -r ".streams[2].sample_rate" "${informationFile}")"
|
|
audioBitRate="$(jq -r ".streams[2].bit_rate" "${informationFile}")"
|
|
else
|
|
warning "Missing audio information for '"$f"'. Inspecting with 'ffprobe'."
|
|
ffprobe -v quiet -print_format json -show_format -show_streams "${f}"
|
|
safeExit
|
|
fi
|
|
|
|
# SET OUTPUT FORMAT
|
|
# Default to 'mp4' for everything.
|
|
# TODO - think through additional defaults
|
|
##########################################################
|
|
|
|
# if you wanted a default target format for a specific input format,
|
|
# you would put it here.
|
|
case "${format}" in
|
|
'Matroska / WebM') outputFormat='mp4' ;;
|
|
*) outputFormat='mp4' ;;
|
|
esac
|
|
|
|
|
|
# SET AUDIO INFORMATION
|
|
# Copy audio in compatible formats. Re-encode audio when needed
|
|
################################################################
|
|
|
|
# Pick the best aac audio encoder
|
|
if $(ffmpeg -version | grep enable-libfdk-aac >/dev/null); then
|
|
aacEncoder="libfdk_aac" && verbose "aac encoder set to libfdk_aac"
|
|
else
|
|
aacencoder="libfaac" && verbose "aac encoder set to libfaac"
|
|
fi
|
|
|
|
supportedAudioCodecs=(aac ac3 eac3)
|
|
if [[ "${supportedAudioCodecs[*]}" =~ "${audioCodec}" ]]; then
|
|
videoAudioCommand="-c:a copy" && verbose "videoAudioCommand set to -c:a copy"
|
|
else
|
|
videoAudioCommand="-c:a ${aacEncoder} -b:a 160k" && verbose "videoAudioCommand set to -c:a ${aacEncoder} -b:a 160k"
|
|
fi
|
|
|
|
# SET VIDEO INFORMATION
|
|
# Copy video in compatible formats. Re-encode audio when needed.
|
|
# Set resizing options
|
|
################################################################
|
|
|
|
# Is input video a known preset size?
|
|
if [[ "$videoWidth" == "1920" && "$videoHeight" == "1080" ]] || [[ "$videoWidth" == "1920" && "$videoHeight" == "816" ]]; then
|
|
videoPreset="1080p" && verbose "Input video has preset: 1080p"
|
|
fi
|
|
if [[ "$videoWidth" == "1280" && "$videoHeight" == "720" ]] || [[ "$videoWidth" == "1280" && "$videoHeight" == "544" ]]; then
|
|
videoPreset="720p" && verbose "Input video has preset: 720p"
|
|
fi
|
|
if [[ "$videoWidth" == "720" && "$videoHeight" == "576" ]]; then
|
|
videoPreset="DVPAL" && verbose "Input video has preset: DVPAL"
|
|
fi
|
|
|
|
# Enable resizing of videos
|
|
# #############################
|
|
|
|
# Fail if user sets more than one value
|
|
if [[ -n "${videoSize}" ]] && [[ -n "${height}" || -n "${width}" ]]; then
|
|
die "We can't set a 'height', 'width', and a 'size' at the same time."
|
|
fi
|
|
|
|
# if user sets both a height and width, run that as a videoSize variable
|
|
if [[ -n "${width}" ]] && [[ -n "${height}" ]]; then
|
|
videoSize="${width}x${height}" && verbose "videoSize=${width}x${height}"
|
|
unset width
|
|
unset height
|
|
fi
|
|
|
|
# downsize720
|
|
# Commonly used function to downsize 1080p to 720p
|
|
if [[ "${downsize720}" == "1" ]]; then
|
|
if [[ "${videoPreset}" == "1080p" ]]; then
|
|
videoSize="hd720" && verbose "videoSize=hd720"
|
|
else
|
|
notice "Skipping ${f}. It's not 1080p"
|
|
breakLoop
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# Do something when a user specifies a size
|
|
if [[ -n "${videoSize}" ]]; then
|
|
|
|
# Don't resize videos to their same size
|
|
if [[ "${videoSize}" == "hd720" ]] || [[ "${videoSize}" == "720" || "${videoSize}" == "1280x720" ]]; then
|
|
if [[ "$videoPreset" == "720p" ]]; then
|
|
notice ""${f}" is already 720p. Skipping...."
|
|
breakLoop
|
|
continue
|
|
fi
|
|
elif [[ "${videoSize}" == "hd1080" || "${videoSize}" == "1080" || "${videoSize}" == "1920x1080" ]]; then
|
|
if [[ "$videoPreset" == "1080p" ]]; then
|
|
notice ""${f}" is already 1080p. Skipping...."
|
|
breakLoop
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# Confirm if user wants to upscale their video
|
|
if [[ "${videoSize}" == "hd1080" || "${videoSize}" == "1080" ]]; then
|
|
userWidth="1920"
|
|
userHeight="1080"
|
|
elif [[ "${videoSize}" == "hd720" ]] || [[ "${videoSize}" == "720" ]]; then
|
|
userWidth="1280"
|
|
userHeight="720"
|
|
else
|
|
# break user's video size into a height and width
|
|
userWidth=$(echo ${videoSize} | cut -f1 -dx)
|
|
userHeight=$(echo ${videoSize} | cut -f2 -dx)
|
|
if [ "${userWidth}" -gt "${videoWidth}" ] || [ "${userHeight}" -gt "${videoHeight}" ]; then
|
|
seek_confirmation "Upscale "${f}" to "${videoSize}"? It is already "${videoWidth}"x"${videoHeight}"."
|
|
if is_not_confirmed; then
|
|
breakLoop
|
|
continue
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Finally, set the resize variable
|
|
videoResize="-vf scale=${videoSize}" && verbose "videoResize='-vf scale=${videoSize}'"
|
|
fi
|
|
|
|
# Scaling variables
|
|
# ####################################
|
|
if is_not_empty "$height"; then
|
|
if [ "${height}" -gt "${videoHeight}" ]; then
|
|
seek_confirmation "Upscale "${f}" to height "${height}"? It is already "${videoWidth}"x"${videoHeight}"."
|
|
if is_not_confirmed; then
|
|
breakLoop
|
|
continue
|
|
fi
|
|
fi
|
|
videoResize="-vf scale=-1:${height}" && verbose "videoResize='-vf scale=-1:${height}'"
|
|
fi
|
|
if is_not_empty "$width"; then
|
|
if [ "${width}" -gt "${videoWidth}" ]; then
|
|
seek_confirmation "Upscale "${f}" to width "${width}"? It is already "${videoWidth}"x"${videoHeight}"."
|
|
if is_not_confirmed; then
|
|
breakLoop
|
|
continue
|
|
fi
|
|
fi
|
|
videoResize="-vf scale=${width}:-1" && verbose "videoResize='-vf scale=${width}:-1'"
|
|
fi
|
|
|
|
# Copy h264 when possible
|
|
# Save precious time by not re-encoding files that are already H264.
|
|
# ###########################
|
|
if [[ "${videoCodec}" == "h264" ]] && [[ -z "${videoResize}" ]]; then
|
|
videoCommand="-c:v copy" && verbose "videoCommand='-c:v copy'"
|
|
else
|
|
videoCommand="-c:v libx264 -crf 18 -preset slow" && verbose "videoCommand'-c:v libx264 -crf 18 -preset slow'"
|
|
fi
|
|
|
|
# Do the conversion
|
|
# ##########################
|
|
|
|
# Don't convert to self if no other options set
|
|
if [[ -z $height && -z $width && -z $videoSize && $downsize720 == "0" && "$outputFormat" == "$extension" ]]; then
|
|
warning "Can't convert a '"${extension}"' to itself. Skipping all '"${extension}"' files."
|
|
break
|
|
fi
|
|
|
|
doConvert # Invoke FFMpeg
|
|
|
|
# Unset variables
|
|
unset videoCodec
|
|
unset videoCodecLong
|
|
unset format
|
|
unset formatName
|
|
unset videoHeight
|
|
unset videoWidth
|
|
unset videoPreset
|
|
unset audioCodec
|
|
unset audioCodecLong
|
|
unset audioSampleRate
|
|
unset audioBitRate
|
|
|
|
breakLoop
|
|
done
|
|
breakLoop
|
|
done
|
|
|
|
}
|
|
|
|
convertMusic() {
|
|
for mt in "${audioTypes[@]}"; do
|
|
for f in *."${mt}"; do
|
|
if [[ -n "${userAudioFile}" ]]; then # TODO: Rewrite user video files and write function to detect user video or music
|
|
# Override the file search if user specifies a specific file from CLI.
|
|
f="${userAudioFile}"
|
|
fi
|
|
test -f "${f}" || continue # Ensure that what we've found is a file
|
|
extension="${f##*.}" # Grab file extension of input file
|
|
informationFile="${tmpDir}/${f////.}.json"
|
|
|
|
# For audio files, ensure that the user specifies an output format
|
|
if [[ -z ${userOutput} ]]; then
|
|
die "Please specify an output audio format using '-o, --output'"
|
|
fi
|
|
|
|
# JSON METADATA FOR EACH ASSET
|
|
######################################################################
|
|
verbose "Reading audio data and writing JSON to tmp"
|
|
|
|
# Output a JSON file for each video asset being parsed.
|
|
ffprobe -v quiet -print_format json -show_format -show_streams "${f}" >> "${informationFile}"
|
|
# uncomment the line below for debugging. It will write a json file for each file in the source directory
|
|
# ffprobe -v quiet -print_format json -show_format -show_streams "${f}" >> "${f}.json"
|
|
|
|
# Read the necessary information from the JSON
|
|
format="$(jq -r ".format.format_long_name" "${informationFile}")"
|
|
formatName="$(jq -r ".format.format_name" "${informationFile}")"
|
|
formatBit_Rate="$(jq -r ".format.bit_rate" "${informationFile}")"
|
|
|
|
if [[ $(jq -r ".streams[0].codec_type" "${informationFile}") == "audio" ]]; then
|
|
audioCodec="$(jq -r '.streams[0].codec_name' "${informationFile}")"
|
|
audioCodecLong="$(jq -r ".streams[0].codec_long_name" "${informationFile}")"
|
|
audioSampleRate="$(jq -r ".streams[0].sample_rate" "${informationFile}")"
|
|
audioBitRate="$(jq -r ".streams[0].bit_rate" "${informationFile}")"
|
|
elif [[ $(jq -r ".streams[1].codec_type" "${informationFile}") == "audio" ]]; then
|
|
audioCodec="$(jq -r '.streams[1].codec_name' "${informationFile}")"
|
|
audioCodecLong="$(jq -r ".streams[1].codec_long_name" "${informationFile}")"
|
|
audioSampleRate="$(jq -r ".streams[1].sample_rate" "${informationFile}")"
|
|
audioBitRate="$(jq -r ".streams[1].bit_rate" "${informationFile}")"
|
|
elif [[ $(jq -r ".streams[2].codec_type" "${informationFile}") == "audio" ]]; then
|
|
audioCodec="$(jq -r '.streams[2].codec_name' "${informationFile}")"
|
|
audioCodecLong="$(jq -r ".streams[2].codec_long_name" "${informationFile}")"
|
|
audioSampleRate="$(jq -r ".streams[2].sample_rate" "${informationFile}")"
|
|
audioBitRate="$(jq -r ".streams[2].bit_rate" "${informationFile}")"
|
|
else
|
|
warning "Missing audio information for '"$f"'. Inspect with 'ffprobe'."
|
|
ffprobe -v quiet -print_format json -show_format -show_streams "${f}"
|
|
safeExit
|
|
fi
|
|
|
|
|
|
# note on XLD: If you have XLD installed and configured, lossless audio conversion
|
|
# will be run using built-in XLD profiles. You can disable this by
|
|
# changing ensuring that XLD=0 in sections below.
|
|
#
|
|
# #############################################################
|
|
|
|
# Build the Conversion Command
|
|
# ########################################
|
|
|
|
# FLAC TO ALAC
|
|
if [[ "${userOutput,,}" == "alac" ]]; then
|
|
if type_exists "xlds"; then
|
|
XLD=1
|
|
# audioConvertCommand="--profile FLACtoALAC"
|
|
audioConvertCommand="-f alac" && verbose "Using XLD. audioConvertCommand = -f alac"
|
|
else
|
|
audioConvertCommand="-acodec alac" && verbose "Using ffmpeg. audioConvertCommand = -acodec alac"
|
|
outputFormat="m4a"
|
|
fi
|
|
elif [[ "${userOutput,,}" == "flac" ]]; then
|
|
if type_exists "xlds"; then
|
|
XLD=1
|
|
audioConvertCommand="-f flac" && verbose "Using XLD. audioConvertCommand = -f flac"
|
|
else
|
|
audioConvertCommand="-c:a flac" && verbose "Using ffmpeg. audioConvertCommand = -c:a flac"
|
|
fi
|
|
elif [[ "${userOutput,,}" == "aac" || "${userOutput,,}" == "m4a" ]]; then
|
|
# Pick the best aac audio encoder
|
|
if $(ffmpeg -version | grep enable-libfdk-aac >/dev/null); then
|
|
# set variable bit rate to '5', the highest
|
|
aacEncoder='libfdk_aac -vbr 5' && verbose "aacEncoder = libfdk_aac -vbr 5"
|
|
else
|
|
aacEncoder='libfaac -q:a 400' && verbose "aacEncoder = libfaac -q:a 400"
|
|
fi
|
|
if type_exists "xlds"; then
|
|
XLD=1
|
|
audioConvertCommand="-f aac" && verbose "using xld. audioConvertCommand = -f aac "
|
|
else
|
|
audioConvertCommand="-acodec ${aacEncoder}" && verbose "using ffmpeg. audioConvertCommand = -acodec ${aacEncoder}"
|
|
fi
|
|
elif [[ "${userOutput,,}" == "mp3" ]]; then
|
|
# Can we convert to mp3? Do we have an ffmpeg encoder?
|
|
if $(ffmpeg -version | grep enable-libmp3lame >/dev/null); then
|
|
mp3Encoder='libmp3lame' && verbose "mp3Encoder = libmp3lame"
|
|
else
|
|
warning "No workable ffmpeg mp3 encoder. Skipping ${f}..."
|
|
breakLoop
|
|
continue
|
|
fi
|
|
# Take user specified bitrate
|
|
if [ -n "$bitrate" ]; then
|
|
bitrate="${bitrate%k}k" # Ensure 'k' is at the end of any bitrate sent to ffmpeg
|
|
ffmpegBitrate="-b:a $bitrate" && verbose "bitrate = ${bitrate}"
|
|
audioConvertCommand="-acodec ${mp3Encoder} ${ffmpegBitrate}" && verbose "audioConvertCommand = -acodec ${mp3Encoder} ${ffmpegBitrate}"
|
|
else
|
|
audioConvertCommand="-acodec ${mp3Encoder} -qscale:a 0" && verbose "audioConvertCommand = -acodec ${mp3Encoder} -qscale:a 0"
|
|
fi
|
|
else
|
|
die "Unknown audio conversion format: ${outputFormat}"
|
|
fi
|
|
|
|
|
|
# Do the conversion
|
|
# ##########################
|
|
|
|
doConvert # Run the conversion function
|
|
|
|
# Unset variables
|
|
unset format
|
|
unset formatName
|
|
unset audioCodec
|
|
unset audioCodecLong
|
|
unset audioSampleRate
|
|
unset audioBitRate
|
|
|
|
breakLoop
|
|
done
|
|
breakLoop
|
|
done
|
|
|
|
}
|
|
|
|
# Run the functions
|
|
identifyUserFile
|
|
userFormat
|
|
outputDir
|
|
convertVideo
|
|
convertMusic
|
|
|
|
####################################################
|
|
############### End Script Here ####################
|
|
}
|
|
|
|
############## Begin Options and Usage ###################
|
|
|
|
|
|
# Print usage
|
|
usage() {
|
|
echo -n "${scriptName} ${version} [OPTION]... [ARGUMENT]...
|
|
|
|
${bold}DESCRIPTION${reset}
|
|
This is a media conversion script which converts audio and video into many
|
|
different formats. It was written to eliminate the need to remember specific
|
|
FFMPEG commands. All conversions can be performed with ffmpeg. XLD on a mac
|
|
is used for audio conversions when available.
|
|
|
|
${BOLD}DEPENDENCIES${reset}
|
|
This script makes heavy use of shared functions contained in ${bold}lib/utils.sh${reset} which are
|
|
available as part of the same Github repository. It will fail if these are not found.
|
|
|
|
This script relies on ${bold}ffmpeg${reset} for video and audio conversion as well as ${bold}jq${reset}
|
|
for parsing JSON files. These must be installed prior to usage. If run on a
|
|
mac, the script will attempt to help you install these packages using ${bold}Homebrew${reset}.
|
|
|
|
${bold}General Options:${reset}
|
|
${bold}-h, --help${reset} Display this help and exit
|
|
${bold}-d, --debug${reset} Runs script in BASH debug mode ('set -x')
|
|
${bold}--force${reset} Skip all user interaction. Implied 'Yes' to all actions.
|
|
${bold}-l, --log${reset} Print log to file
|
|
${bold}-q, --quiet${reset} Quiet (no output)
|
|
${bold}-v, --verbose${reset} Output more information. (Items echoed to 'verbose')
|
|
${bold} --safe${reset} Runs the script without actually invoking FFMPEG. Will simply print
|
|
the FFMPEG commands to the terminal
|
|
${bold}--version${reset} Output version information and exit
|
|
|
|
${bold}File Options:${reset}
|
|
${bold}-f, --file${reset} Specify a specific file to take actions on.
|
|
${bold}-i, --input${reset} Specify the specific media type to search for and take action
|
|
on. (mov', 'mp4', 'mp3')
|
|
${bold}-o, --output${reset} Specify the output format for the file(s) to be converted to.
|
|
('mkv', 'mp4', 'm4a')
|
|
${bold}--delete ${reset} Delete the original file after conversion.
|
|
${bold}--saveDir${reset} Specify a folder for the converted files to be saved to. Defaults to
|
|
the directory the script is invoked in.
|
|
|
|
${bold}Video Specific Options:${reset}
|
|
For video files, if no output format is specified, the script will attempt to convert every
|
|
video file it finds in the directory into h264 .mp4 format.
|
|
|
|
${bold}--size${reset} Set the size of the target file. Can be one of 'hd1080', 'hd720',
|
|
or 'HeightxWidth' (ie. 1920x1080)
|
|
${bold}--height${reset} Set a height in pixels to scale the target file.
|
|
${bold}--width${reset} Set a width in pixels to scale the target file.
|
|
${bold}--downsize720${reset} Searches for 1080p videos and downsizes them to 720p. No need
|
|
for other scaling options. Used to reduce the size of video collections
|
|
when quality is not the primary need.
|
|
|
|
${bold}Audio Specific Options:${reset}
|
|
${bold}--bitrate${reset} Set a bit rate for audio conversions. Note, this does not
|
|
effect video conversions.
|
|
|
|
${bold}EXAMPLES:${reset}
|
|
Search for all *.flac files in a directory and convert them to
|
|
Apple Lossless (alac). Once the conversion is complete, original files
|
|
will be deleted.
|
|
|
|
$ convertMedia -i flac -o alac --delete
|
|
|
|
Search for all 1080p files in a directory and downsize them to 720p.
|
|
|
|
$ convertMedia --downsize720
|
|
|
|
Convert a Windows Media file (file.wmv) to h264 (mp4).
|
|
|
|
$ convertMedia -o mp4 file.wmv
|
|
|
|
"
|
|
}
|
|
|
|
# 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}
|
|
|
|
# Add current char to options
|
|
options+=("-$c")
|
|
|
|
# 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
|
|
|
|
# Print help if no arguments were passed.
|
|
# Uncomment to force arguments when invoking the script
|
|
[[ $# -eq 0 ]] && set -- "--help"
|
|
|
|
# Read the options and set stuff
|
|
while [[ $1 = -?* ]]; do
|
|
case $1 in
|
|
-f|--file) shift; userFile="$1" ;;
|
|
-i|--input) shift; MEDIATYPE="$1" ;;
|
|
-o|--output) shift; userOutput="$1" ;;
|
|
--safe) safeRun=1 ;;
|
|
--size) shift; videoSize="$1" ;;
|
|
--height) shift; height="$1" ;;
|
|
--width) shift; width="$1" ;;
|
|
--downsize720) downsize720=1 ;;
|
|
--delete) deleteOriginal=1 ;;
|
|
--saveDir) shift; saveDir="$1" ;;
|
|
--bitrate) shift; bitrate="$1" ;;
|
|
-h|--help) usage >&2; safeExit ;;
|
|
--force) force=1 ;;
|
|
--version) echo "$(basename $0) $version"; safeExit ;;
|
|
-v|--verbose) verbose=true ;;
|
|
-l|--log) printLog=1 ;;
|
|
-q|--quiet) quiet=1 ;;
|
|
-d|--debug) debug=1;;
|
|
--endopts) shift; break ;;
|
|
*) die "invalid option: '$1'." ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Store the remaining part as arguments.
|
|
args+=("$@")
|
|
|
|
############## End Options and Usage ###################
|
|
|
|
|
|
|
|
|
|
# ############# ############# #############
|
|
# ## TIME TO RUN THE SCRIPT ##
|
|
# ## ##
|
|
# ## You shouldn't need to edit anything ##
|
|
# ## beneath this line ##
|
|
# ## ##
|
|
# ############# ############# #############
|
|
|
|
# Trap bad exits with your cleanup function
|
|
trap trapCleanup EXIT INT TERM
|
|
|
|
# Exit on error. Append '||true' when you run the script if you expect an error.
|
|
set -o errexit
|
|
|
|
# Run in debug mode, if set
|
|
if [ "${debug}" == "1" ]; then
|
|
set -x
|
|
fi
|
|
|
|
# Exit on empty variable
|
|
if [ "${strict}" == "1" ]; then
|
|
set -o nounset
|
|
fi
|
|
|
|
# Bash will remember & return the highest exitcode in a chain of pipes.
|
|
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
|
|
set -o pipefail
|
|
|
|
checkDependencies # Invoke the checkDependenices function to test for Bash packages
|
|
|
|
mainScript # Run your script
|
|
|
|
safeExit # Exit cleanly |