mirror of
https://github.com/natelandau/shell-scripting-templates.git
synced 2025-11-13 23:43:47 -05:00
major version bump to 2.0
This commit is contained in:
699
bin/convertMedia
699
bin/convertMedia
@@ -3,7 +3,7 @@
|
||||
# ##################################################
|
||||
# My Generic BASH script template
|
||||
#
|
||||
version="1.2.0" # Sets version variable for this script
|
||||
version="2.0.0" # Sets version variable for this script
|
||||
#
|
||||
scriptTemplateVersion="1.5.0" # Version of scriptTemplate.sh that this script is based on
|
||||
#
|
||||
@@ -14,6 +14,13 @@ scriptTemplateVersion="1.5.0" # Version of scriptTemplate.sh that this script is
|
||||
# * 2015-04-07 - v1.1.0 - Added support for music files
|
||||
# * 2015-05-26 - v1.1.1 - Fixed log level on downsize720
|
||||
# * 2015-08-30 - v1.2.0 - Support for recursive directory conversions
|
||||
# * 2016-01-03 - v1.2.1 - Fixed XLD audio conversion which had been broken
|
||||
# * 2016-01-15 - v2.0.0 - Major refactoring of code.
|
||||
# - Better JSON parsing via JQ
|
||||
# - MIME type discovery based on file metadata (rather than user input)
|
||||
# - Better error handling
|
||||
# - Better renaming of existing files
|
||||
#
|
||||
#
|
||||
# ##################################################
|
||||
|
||||
@@ -43,6 +50,7 @@ fi
|
||||
# -----------------------------------
|
||||
function trapCleanup() {
|
||||
echo ""
|
||||
# Delete temp files, if any
|
||||
if is_dir "${tmpDir}"; then
|
||||
rm -r "${tmpDir}"
|
||||
fi
|
||||
@@ -64,6 +72,9 @@ debug=false
|
||||
safeRun=0
|
||||
downsize720=0
|
||||
deleteOriginal=false
|
||||
newFileFlag=false
|
||||
videoFoundFlag=false
|
||||
audioFoundFlag=false
|
||||
XLD=0
|
||||
args=()
|
||||
|
||||
@@ -72,7 +83,7 @@ args=()
|
||||
# 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.$$"
|
||||
tmpDir="/tmp/${scriptName}.${RANDOM}.${RANDOM}.${RANDOM}.${$}"
|
||||
(umask 077 && mkdir "${tmpDir}") || {
|
||||
die "Could not create temporary directory! Exiting."
|
||||
}
|
||||
@@ -85,7 +96,7 @@ tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
|
||||
# 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"
|
||||
logFile="${HOME}/Library/Logs/${scriptBasename}.log"
|
||||
|
||||
# Check for Dependencies
|
||||
# -----------------------------------
|
||||
@@ -100,170 +111,212 @@ caskDependencies=()
|
||||
gemDependencies=()
|
||||
|
||||
function mainScript() {
|
||||
############## Begin Script Here ###################
|
||||
####################################################
|
||||
############## 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)
|
||||
# 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
|
||||
function identifyFiles() {
|
||||
local item
|
||||
local maxSearchDepth
|
||||
local fileType
|
||||
local fileMatch
|
||||
|
||||
# Assign user specified files
|
||||
if [ -n "${userFile}" ]; then
|
||||
filesToConvert+=("${userFile}")
|
||||
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"
|
||||
if [ -n "${args}" ]; then
|
||||
filesToConvert+=("${args[@]}")
|
||||
fi
|
||||
if [[ ! -d "${saveDir}" ]]; then
|
||||
seek_confirmation "${saveDir} does not exist. Create?"
|
||||
if is_confirmed; then
|
||||
mkdir $v "${saveDir}"
|
||||
|
||||
# Respect recursive file searches when requested
|
||||
if ${recursive}; then
|
||||
maxSearchDepth=4
|
||||
else
|
||||
die "Can't run without a place to save the files."
|
||||
maxSearchDepth=1
|
||||
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.
|
||||
# Assign files based on MEDIATYPE extension
|
||||
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 recursiveSearch() {
|
||||
|
||||
# Add matching items to array
|
||||
while read item; do
|
||||
recursiveFiles+=("${item}")
|
||||
done < <(find . -name "*.${MEDIATYPE}" -type f -maxdepth 4)
|
||||
filesToConvert+=("${item}")
|
||||
done < <(find . -name "*.${MEDIATYPE}" -type f -maxdepth ${maxSearchDepth})
|
||||
fi
|
||||
|
||||
for i in "${recursiveFiles[@]}"; do
|
||||
userFile="${i}"
|
||||
verbose "found item: ${userFile}"
|
||||
recursiveDirectory=$(dirname "${userFile}")
|
||||
recursiveDirectory="${recursiveDirectory#*/}"
|
||||
saveDir="${recursiveDirectory}/"
|
||||
runScript
|
||||
# Figure out what to do if a user doesn't specify any files or input types
|
||||
if [ ${#filesToConvert[@]} -eq 0 ]; then
|
||||
notice "We couldn't find any files to act on."
|
||||
seek_confirmation "Do you want to convert all VIDEO files?"
|
||||
if is_confirmed; then
|
||||
for fileType in "${videoTypes[@]}"; do
|
||||
while read fileMatch; do
|
||||
filesToConvert+=("${fileMatch}")
|
||||
done < <(find . -name "*.${fileType}" -type f -maxdepth ${maxSearchDepth})
|
||||
done
|
||||
}
|
||||
|
||||
function userFormat() {
|
||||
# Reads user input for format (-o, --output)
|
||||
# Override defaults with CLI
|
||||
if [ -n "$userOutput" ]; then
|
||||
outputFormat="${userOutput,,}" #&& verbose "outputFormat=${outputFormat}"
|
||||
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
|
||||
seek_confirmation "Do you want to convert all AUDIO files?"
|
||||
if is_confirmed; then
|
||||
for fileType in "${audioTypes[@]}"; do
|
||||
while read fileMatch; do
|
||||
filesToConvert+=("${fileMatch}")
|
||||
done < <(find . -name "*.${fileType}" -type f -maxdepth ${maxSearchDepth})
|
||||
done
|
||||
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}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Error handling: Ensure the count of elements in ${filesToConvert[@]} is not zero.
|
||||
if [ ${#filesToConvert[@]} -eq 0 ]; then
|
||||
error "We couldn't find any files to act on. Exiting."
|
||||
safeExit
|
||||
fi
|
||||
|
||||
# SET OUTPUT FORMAT
|
||||
# Default to 'mp4' for everything.
|
||||
# TODO - think through additional defaults
|
||||
##########################################################
|
||||
# debug
|
||||
verbose "filesToConvert=${filesToConvert[@]}"
|
||||
}
|
||||
|
||||
function testFiles() {
|
||||
# This function ensures that the specified files exist and can be converted
|
||||
local extension
|
||||
|
||||
# Ensure we have a valid file
|
||||
if [ ! -f "${fileToTest}" ]; then die "We can't find '${fileToTest}' or it is not valid."; fi
|
||||
|
||||
# Ensure the extension is known to this script
|
||||
extension="${fileToTest##*.}" # Grab file extension of input file
|
||||
|
||||
# See if the extension is in the specified extension mappings
|
||||
if [[ ! "${videoTypes[*]}" =~ "${extension}" ]] && [[ ! "${audioTypes[*]}" =~ "${extension}" ]]; then
|
||||
error "The extension of '${extension} ' was not recognized."
|
||||
die "We don't know what to do with ${fileToTest}."
|
||||
fi
|
||||
}
|
||||
|
||||
function convertToFormat() {
|
||||
|
||||
# Do things on video files
|
||||
if [[ "${videoTypes[*]}" =~ "${file##*.}" ]]; then
|
||||
# if you wanted a default target format for a specific input format,
|
||||
# you would put it here.
|
||||
# Set the default video conversion format to mp4
|
||||
case "${format}" in
|
||||
'Matroska / WebM') outputFormat='mp4' ;;
|
||||
*) outputFormat='mp4' ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Do things on audio files
|
||||
if [[ "${audioTypes[*]}" =~ "${file##*.}" ]]; then
|
||||
|
||||
# Ensure a user sets an output format since we don't have a default.
|
||||
if [[ -z ${userOutput} ]]; then
|
||||
warning "Please specify an output audio format using '-o, --output'. Exiting"
|
||||
safeExit
|
||||
fi
|
||||
|
||||
# Confirm if a user wants to convert audio to it's own format
|
||||
if [[ "${file##*.}" == "${userOutput}" ]]; then
|
||||
warning "You are attempting to convert a ${file##*.} file to ${userOutput}."
|
||||
seek_confirmation "Do you want to proceed?"
|
||||
if is_not_confirmed; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Reads user input for format (-o, --output)
|
||||
if [ -n "${userOutput}" ]; then
|
||||
outputFormat="${userOutput,,}" #&& verbose "outputFormat=${outputFormat}"
|
||||
fi
|
||||
}
|
||||
|
||||
function parseJSON() {
|
||||
local ii
|
||||
local videoFoundFlag
|
||||
local audioFoundFlag
|
||||
local informationFile
|
||||
|
||||
# Create the temporary JSON file
|
||||
informationFile="${tmpDir}/${file////}.json"
|
||||
|
||||
verbose "Reading audio data and writing JSON to tmp"
|
||||
verbose "-> ${informationFile}"
|
||||
|
||||
# Output a JSON file for each video asset being parsed.
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams "${file}" >> "${informationFile}"
|
||||
|
||||
# Debugging: Uncomment either (or both) of these lines
|
||||
# ffprobe -v quiet -print_format json -show_format -show_streams "${file}" >> "${file}.json"
|
||||
# cat "${informationFile}"
|
||||
|
||||
# Read the necessary information from the JSON
|
||||
format="$(jq -r ".format.format_long_name" "${informationFile}")"
|
||||
formatName="$(jq -r ".format.format_name" "${informationFile}")"
|
||||
jsonFilename="$(jq -r ".format.filename" "${informationFile}")"
|
||||
formatBit_Rate="$(jq -r ".format.bit_rate" "${informationFile}")"
|
||||
numStreams="$(jq -r ".format.nb_streams" "${informationFile}")"
|
||||
|
||||
# Iterate over results
|
||||
ii=0
|
||||
while [ ${ii} -lt ${numStreams} ]; do
|
||||
if [[ $(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_type" "${informationFile}") == "video" ]]; then
|
||||
videoHeight="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].height" "${informationFile}")"
|
||||
videoWidth="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].width" "${informationFile}")"
|
||||
videoCodec="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_name" "${informationFile}")"
|
||||
videoCodecLong="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_long_name" "${informationFile}")"
|
||||
|
||||
videoFoundFlag=true # Used for error handling to confirm we found a video stream
|
||||
fi
|
||||
if [ $(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_type" "${informationFile}") == "audio" ]; then
|
||||
audioCodec="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_name" "${informationFile}")"
|
||||
audioCodecLong="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].codec_long_name" "${informationFile}")"
|
||||
audioSampleRate="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].sample_rate" "${informationFile}")"
|
||||
audioBitRate="$(jq -r --arg ii ${ii} ".streams[$ii | tonumber].bit_rate" "${informationFile}")"
|
||||
|
||||
audioFoundFlag=true # Used for error handling to confirm we found an audio stream
|
||||
fi
|
||||
|
||||
# Increase loop count
|
||||
ii=$[$ii+1]
|
||||
done
|
||||
|
||||
# Error Handling for video files
|
||||
if [[ "${videoTypes[*]}" =~ "${file##*.}" ]]; then
|
||||
if [ "${videoFoundFlag}" = "false" ]; then
|
||||
warning "Missing video stream for '${file}'."
|
||||
seek_confirmation "Inspect the file with ffprobe?"
|
||||
if is_confirmed; then
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams "${file}"
|
||||
safeExit
|
||||
else
|
||||
notice "Exiting"
|
||||
safeExit
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Error Handling for audio files
|
||||
if [[ "${audioTypes[*]}" =~ "${file##*.}" ]]; then
|
||||
if [ "${audioFoundFlag}" = "false" ]; then
|
||||
warning "Missing audio stream for '${file}'."
|
||||
seek_confirmation "Inspect the file with ffprobe?"
|
||||
if is_confirmed; then
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams "${file}"
|
||||
safeExit
|
||||
else
|
||||
notice "Exiting"
|
||||
safeExit
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function convertVideo() {
|
||||
|
||||
verbose "Running 'convertVideo' function"
|
||||
|
||||
# SET AUDIO INFORMATION
|
||||
# Copy audio in compatible formats. Re-encode audio when needed
|
||||
@@ -271,9 +324,9 @@ convertVideo() {
|
||||
|
||||
# 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"
|
||||
aacEncoder="libfdk_aac"
|
||||
else
|
||||
aacencoder="libfaac" && verbose "aac encoder set to libfaac"
|
||||
aacencoder="libfaac"
|
||||
fi
|
||||
|
||||
supportedAudioCodecs=(aac ac3 eac3)
|
||||
@@ -289,13 +342,13 @@ convertVideo() {
|
||||
################################################################
|
||||
|
||||
# Is input video a known preset size?
|
||||
if [[ "$videoWidth" == "1920" && "$videoHeight" == "1080" ]] || [[ "$videoWidth" == "1920" && "$videoHeight" == "816" ]]; then
|
||||
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"
|
||||
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
|
||||
if [[ "${videoWidth}" == "720" && "${videoHeight}" == "576" ]]; then
|
||||
videoPreset="DVPAL" && verbose "Input video has preset: DVPAL"
|
||||
fi
|
||||
|
||||
@@ -320,8 +373,7 @@ convertVideo() {
|
||||
if [[ "${videoPreset}" == "1080p" ]]; then
|
||||
videoSize="hd720" && verbose "videoSize=hd720"
|
||||
else
|
||||
notice "Skipping ${f}. It's not 1080p"
|
||||
breakLoop
|
||||
notice "Skipping ${file}. It's not 1080p"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
@@ -332,14 +384,12 @@ convertVideo() {
|
||||
# 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
|
||||
notice "${file} is already 720p. Skipping...."
|
||||
continue
|
||||
fi
|
||||
elif [[ "${videoSize}" == "hd1080" || "${videoSize}" == "1080" || "${videoSize}" == "1920x1080" ]]; then
|
||||
if [[ "$videoPreset" == "1080p" ]]; then
|
||||
notice ""${f}" is already 1080p. Skipping...."
|
||||
breakLoop
|
||||
notice "${file} is already 1080p. Skipping...."
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
@@ -356,9 +406,8 @@ convertVideo() {
|
||||
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}"."
|
||||
seek_confirmation "Upscale "${file}" to "${videoSize}"? It is already "${videoWidth}"x"${videoHeight}"."
|
||||
if is_not_confirmed; then
|
||||
breakLoop
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
@@ -370,21 +419,19 @@ convertVideo() {
|
||||
|
||||
# Scaling variables
|
||||
# ####################################
|
||||
if is_not_empty "$height"; then
|
||||
if is_not_empty "${height}"; then
|
||||
if [ "${height}" -gt "${videoHeight}" ]; then
|
||||
seek_confirmation "Upscale "${f}" to height "${height}"? It is already "${videoWidth}"x"${videoHeight}"."
|
||||
seek_confirmation "Upscale "${file}" 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 is_not_empty "${width}"; then
|
||||
if [ "${width}" -gt "${videoWidth}" ]; then
|
||||
seek_confirmation "Upscale "${f}" to width "${width}"? It is already "${videoWidth}"x"${videoHeight}"."
|
||||
seek_confirmation "Upscale "${file}" to width "${width}"? It is already "${videoWidth}"x"${videoHeight}"."
|
||||
if is_not_confirmed; then
|
||||
breakLoop
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
@@ -400,88 +447,16 @@ convertVideo() {
|
||||
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
|
||||
if [[ -z ${height} && -z ${width} && -z ${videoSize} && ${downsize720} == "0" && "${outputFormat}" == "${file##*.}" ]]; then
|
||||
error "Can't convert a '"${file##*.}"' file to itself. Skipping ${file}"
|
||||
continue
|
||||
fi
|
||||
}
|
||||
|
||||
function convertAudio() {
|
||||
|
||||
verbose "Running 'convertAudio' function"
|
||||
# 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.
|
||||
@@ -529,7 +504,6 @@ convertMusic() {
|
||||
mp3Encoder='libmp3lame' && verbose "mp3Encoder = libmp3lame"
|
||||
else
|
||||
warning "No workable ffmpeg mp3 encoder. Skipping ${f}..."
|
||||
breakLoop
|
||||
continue
|
||||
fi
|
||||
# Take user specified bitrate
|
||||
@@ -541,55 +515,68 @@ convertMusic() {
|
||||
audioConvertCommand="-acodec ${mp3Encoder} -qscale:a 0" && verbose "audioConvertCommand = -acodec ${mp3Encoder} -qscale:a 0"
|
||||
fi
|
||||
else
|
||||
die "Unknown audio conversion format: ${outputFormat}"
|
||||
die "Unknown audio conversion format: '${outputFormat}'."
|
||||
fi
|
||||
}
|
||||
|
||||
function setOutputDirectory() {
|
||||
if ${verbose}; then v="-v" ; fi
|
||||
|
||||
# Do the conversion
|
||||
# ##########################
|
||||
# Use the user's specified directory to save the new file in if specified.
|
||||
# default to use the location of the original file.
|
||||
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%/}/"
|
||||
else
|
||||
outputDir=$(dirname "${file}")
|
||||
outputDir="${outputDir#*/}"
|
||||
outputDir="${outputDir%/}/"
|
||||
fi
|
||||
}
|
||||
|
||||
doConvert # Run the conversion function
|
||||
function setOutputFile() {
|
||||
|
||||
# Unset variables
|
||||
unset format
|
||||
unset formatName
|
||||
unset audioCodec
|
||||
unset audioCodecLong
|
||||
unset audioSampleRate
|
||||
unset audioBitRate
|
||||
|
||||
breakLoop
|
||||
done
|
||||
breakLoop
|
||||
done
|
||||
}
|
||||
|
||||
function doConvert() {
|
||||
|
||||
# Set the output name, format, and directory
|
||||
# ###############################################
|
||||
|
||||
if $verbose; then v="-v" ; fi
|
||||
# This function creates the name of new file to be generated by the conversion
|
||||
# ##################
|
||||
|
||||
# Set output filename
|
||||
output="$(basename "${f%.*}").$outputFormat" && verbose "output="${output}""
|
||||
|
||||
# Add user's output save directory if used
|
||||
if [[ -n "${outputDir}" ]]; then
|
||||
verbose "outputDir=${outputDir}"
|
||||
output="${outputDir}${output}" && verbose "output=${outputDir}${output}"
|
||||
fi
|
||||
output="$(basename "${file%.*}").${outputFormat}"
|
||||
# Add the output directory
|
||||
output="${outputDir}${output}"
|
||||
|
||||
# Confirm we're not overwriting an existing file
|
||||
if [[ "${safeRun}" -ne "1" ]]; then
|
||||
# If we are, append '.new' to the name
|
||||
fileFlag=0
|
||||
while [[ fileFlag -eq 0 ]]; do
|
||||
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
|
||||
# output="$(basename "${file%.*}").new."${outputFormat}""
|
||||
output="$(basename "${output%.*}").new.${outputFormat}"
|
||||
# Add the directory back to the file
|
||||
output="${outputDir}${output}"
|
||||
# Set a flag so that we can rename this file back to the original name if
|
||||
# a user elects to delete the original file
|
||||
newFileFlag=true
|
||||
else
|
||||
fileFlag=1
|
||||
fi
|
||||
done
|
||||
|
||||
verbose "new file name will be: ${output}"
|
||||
}
|
||||
|
||||
function doConvert() {
|
||||
if ${verbose}; then v="-v" ; fi
|
||||
|
||||
# Respect the 'Quiet' flag
|
||||
if "${quiet}"; then
|
||||
@@ -598,79 +585,98 @@ function doConvert() {
|
||||
fi
|
||||
|
||||
# Respect the 'logfile' flag
|
||||
if ${printLog}; then
|
||||
ffmpegLog=">> ${logFile}"
|
||||
fi
|
||||
|
||||
# Invoke the conversion
|
||||
# ##################################
|
||||
if ${printLog}; then ffmpegLog=">> ${logFile}"; fi
|
||||
|
||||
# Use XLD for audio file conversion if available
|
||||
if [[ "$XLD" == "1" ]]; then
|
||||
verbose "Running XLD commands for audio. No FFMPEG"
|
||||
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}""
|
||||
notice "xld -o "${output}" ${audioConvertCommand} "${file}""
|
||||
else
|
||||
verbose "xld -o "${output}" ${audioConvertCommand} "${f}""
|
||||
xld -o "${output}" ${audioConvertCommand} "${f}"
|
||||
deleteOriginalFile
|
||||
verbose "xld -o "${output}" ${audioConvertCommand} "${file}""
|
||||
xld -o "${output}" ${audioConvertCommand} "${file}"
|
||||
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}"
|
||||
notice "ffmpeg -i "${file}" ${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}
|
||||
deleteOriginalFile
|
||||
verbose "ffmpeg -i "${file}" ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} "${output}" ${ffquiet}"
|
||||
ffmpeg -i "${file}" ${videoResize} ${videoCommand} ${videoAudioCommand} ${audioConvertCommand} "${output}" ${ffquiet}
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
# Unset variables to get ready for the next file
|
||||
unset jsonFilename
|
||||
unset formatBit_Rate
|
||||
unset numStreams
|
||||
unset videoCodec
|
||||
unset videoCodecLong
|
||||
unset format
|
||||
unset formatName
|
||||
unset videoHeight
|
||||
unset videoWidth
|
||||
unset videoPreset
|
||||
unset audioCodec
|
||||
unset audioCodecLong
|
||||
unset audioSampleRate
|
||||
unset audioBitRate
|
||||
}
|
||||
|
||||
function deleteOriginalFile() {
|
||||
function deleteOriginalFile() {
|
||||
local f
|
||||
local newFile
|
||||
|
||||
if ${verbose}; then v="-v" ; fi
|
||||
|
||||
if [[ "${safeRun}" == "0" ]]; then
|
||||
if ${deleteOriginal}; then
|
||||
rm -f $v "${f}"
|
||||
#remove '.new' from filename
|
||||
if [[ -n "${outputDir}" ]]; then
|
||||
for file in "${outputDir}*.new.*"; do
|
||||
rename $v 's/.new//g' "${file}"
|
||||
done
|
||||
else
|
||||
for file in *.new.*; do
|
||||
rename $v 's/.new//g' "${file}"
|
||||
done
|
||||
rm -f ${v} "${file}"
|
||||
mv ${v} "${output}" "${outputDir}${file}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
## RUN THE SCRIPT ##
|
||||
####################################
|
||||
|
||||
# First we need to identify the files to be converted
|
||||
identifyFiles
|
||||
|
||||
# Then we test that all files can be operated upon
|
||||
for fileToTest in "${filesToConvert[@]}"; do testFiles; done
|
||||
|
||||
# Then we work on the individual files. This is the fun part.
|
||||
for file in "${filesToConvert[@]}"; do
|
||||
info "Working on ${file}"
|
||||
|
||||
# First we grab the metadata of the file and assign it to variables
|
||||
parseJSON
|
||||
# Then we set the expected output format
|
||||
convertToFormat
|
||||
# Then we set the appropriate conversion commands
|
||||
if [[ "${videoTypes[*]}" =~ "${file##*.}" ]]; then convertVideo; fi
|
||||
if [[ "${audioTypes[*]}" =~ "${file##*.}" ]]; then convertAudio; fi
|
||||
# Then we tell the script where to output the file
|
||||
setOutputDirectory
|
||||
# Then we generate the name for the new file
|
||||
setOutputFile
|
||||
# Then we actually do the conversion
|
||||
doConvert
|
||||
# Then we delete the original file (if requested)
|
||||
deleteOriginalFile
|
||||
done
|
||||
|
||||
|
||||
####################################################
|
||||
############### End Script Here ####################
|
||||
}
|
||||
|
||||
runScript() {
|
||||
# All functions within the script are here
|
||||
identifyUserFile
|
||||
userFormat
|
||||
outputDir
|
||||
convertVideo
|
||||
convertMusic
|
||||
}
|
||||
|
||||
# Run the functions
|
||||
if ${recursive}; then
|
||||
recursiveSearch # Run the recursive search function
|
||||
else
|
||||
runScript
|
||||
fi
|
||||
|
||||
####################################################
|
||||
############### End Script Here ####################
|
||||
}
|
||||
|
||||
############## Begin Options and Usage ###################
|
||||
|
||||
|
||||
# Print usage
|
||||
usage() {
|
||||
echo -n "${scriptName} ${version} [OPTION]... [ARGUMENT]...
|
||||
# Print usage
|
||||
echo -n "${scriptName} ${version} [OPTION]... [ARGUMENT]...
|
||||
|
||||
${bold}DESCRIPTION${reset}
|
||||
This is a media conversion script which converts audio and video into many
|
||||
@@ -740,10 +746,10 @@ Convert a Windows Media file (file.wmv) to h264 (mp4).
|
||||
$ convertMedia -o mp4 file.wmv
|
||||
|
||||
Do a recursive search for all directories beneath the current one. In each
|
||||
directory, search for .avi iles and convert them to .mp4
|
||||
$ convertMedia --recursive -o mp4 -i avi
|
||||
directory, search for .avi files and convert them to .mp4
|
||||
|
||||
"
|
||||
$ convertMedia -o mp4 -i avi --recursive
|
||||
"
|
||||
}
|
||||
|
||||
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
|
||||
@@ -784,11 +790,12 @@ unset options
|
||||
|
||||
# Print help if no arguments were passed.
|
||||
# Uncomment to force arguments when invoking the script
|
||||
[[ $# -eq 0 ]] && set -- "--help"
|
||||
# [[ $# -eq 0 ]] && set -- "--help"
|
||||
|
||||
# Read the options and set stuff
|
||||
while [[ $1 = -?* ]]; do
|
||||
case $1 in
|
||||
while [[ ${1} = -?* ]]; do
|
||||
case ${1} in
|
||||
|
||||
-f|--file) shift; userFile="$1" ;;
|
||||
-i|--input) shift; MEDIATYPE="$1" ;;
|
||||
-o|--output) shift; userOutput="$1" ;;
|
||||
@@ -817,11 +824,6 @@ done
|
||||
# Store the remaining part as arguments.
|
||||
args+=("$@")
|
||||
|
||||
############## End Options and Usage ###################
|
||||
|
||||
|
||||
|
||||
|
||||
# ############# ############# #############
|
||||
# ## TIME TO RUN THE SCRIPT ##
|
||||
# ## ##
|
||||
@@ -834,7 +836,7 @@ args+=("$@")
|
||||
trap trapCleanup EXIT INT TERM
|
||||
|
||||
# Set IFS to preferred implementation
|
||||
#IFS=$'\n\t'
|
||||
IFS=$' \n\t'
|
||||
|
||||
# Exit on error. Append '||true' when you run the script if you expect an error.
|
||||
set -o errexit
|
||||
@@ -849,8 +851,11 @@ if ${strict}; then set -o nounset ; fi
|
||||
# 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
|
||||
# Invoke the checkDependenices function to test for Bash packages. Uncomment if needed.
|
||||
checkDependencies
|
||||
|
||||
mainScript # Run your script
|
||||
# Run your script
|
||||
mainScript
|
||||
|
||||
safeExit # Exit cleanly
|
||||
# Exit cleanly
|
||||
safeExit
|
||||
Reference in New Issue
Block a user