#!/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=0 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) 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 [[ -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 "${saveDir}" && verbose "mkdir ${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 # ############################################### # 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 if [[ "${deleteOriginal}" == "1" ]]; then rm -f "${f}" && verbose "Deleting "${f}"" 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=1 ;; -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