From b6f559235377175798a3ee4109da4144349fd250 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Fri, 1 Oct 2010 19:01:02 +0200 Subject: add convtoflac.sh Signed-off-by: Florian Pritz --- convtoflac.sh | 497 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100755 convtoflac.sh (limited to 'convtoflac.sh') diff --git a/convtoflac.sh b/convtoflac.sh new file mode 100755 index 0000000..a2abd15 --- /dev/null +++ b/convtoflac.sh @@ -0,0 +1,497 @@ +#!/bin/bash + +# ----------------------------------------------------------------------------- +# +# App Title: convtoflac.sh +# App Version: 2.1.3 +# Author: Jared Breland +# Homepage: http://www.legroom.net/software +# +# Script Function: +# Convert losslessly compressed audio file to FLAC format, preserving tags +# Currently supports FLAC, Monkey's Audio (APE), Shorten, WAV, and WavPack +# +# Instructions: +# Ensure that all programs are properly set in "Setup environment" +# +# Caveats: +# Transcoded files will retain original file name, but use .flac extension +# The one exception is for FLAC input files - the original input file will +# be renamed _old.flac, and the transcoded file will be named +# .flac. +# +# Requirements: +# The following programs must be installed and available +# sed (http://sed.sourceforge.net/) +# used to handle case sensitivity and tag processing +# trash-cli (http://code.google.com/p/trash-cli/)) +# used for moving files to trash rather than deleting +# flac/metaflac (http://flac.sourceforge.net/) +# used to create and tag new FLAC files +# alac (http://craz.net/programs/itunes/alac.html) +# used to decompress ALAC (Apple Lossles) files +# mp4info, part of libmp4v2 (http://resare.com/libmp4v2/) +# used to read tags from ALAC (Apple Lossles) files +# mac (http://sourceforge.net/projects/mac-port/) +# used to decompress APE (Monkey's Audio) files +# apeinfo (http://www.legroom.net/software) +# used to read tags from APE files +# shorten (http://etree.org/shnutils/shorten/) +# used to decompress Shorten files +# ttaenc (http://www.true-audio.com/) +# used to decompress TTA (True Audio) files +# wvunpack (http://www.wavpack.com/) +# used to decompress WavPack files +# ffmpeg (http://www.ffmpeg.org/) +# optionally used to decompress ALAC, APE, Shorten, and WavPack files +# +# Please visit the application's homepage for additional information. +# +# ----------------------------------------------------------------------------- + +renice -n 19 $$ + +# Static variables +readonly VERSION="2.1.3" +readonly PROG=$(basename $0) + +# Setup environment +TMP='/tmp' +DELETE='' +OVERWRITE='' +USEFFMPEG='' +COMPRESS=8 +THREADS=1 +COPYTAGS=1 +FILES=() +#COLOR='\E[33;40m\033[1m' #comment these out if your term doesn't support colors +COLORWARN='\E[31;40m\033[1m' +IFS='@' + +# Function to display usage information +function warning() { + echo -ne "Usage: $PROG [-h] [-V] [-d|-m|-p] [-f] [-tN] [-n] [-o] [-cN]\n" + echo -ne " $(printf "%${#PROG}s") [ ...]\n" + echo -ne "Convert losslessly compressed audio files to FLAC format, preserving tags\n" + echo -ne "\nOptions:\n" + echo -ne " -h Display this help information\n" + echo -ne " -V Display version and exit\n" + echo -ne " -d Delete file after conversion\n" + echo -ne " -m Move file to trash after conversion\n" + echo -ne " -p Prompt to delete file after conversion\n" + echo -ne " -f Use ffmpeg instead of default utilities to decode input files\n" + echo -ne " Note: Existing tags will not be copied if ffmpeg is used\n" + echo -ne " -tN Convert N number of files concurrently; default is 1\n" + echo -ne " -n Do not copy existing tags to new FLAC file\n" + echo -ne " -o Overwrite existing output FLAC files\n" + echo -ne " -cN Set FLAC compression level, where N = 0 (fast) - 8 (best); default is 8\n" + echo -ne "\nSupported input formats:\n" + echo -ne " Apple Lossless (.m4a)\n" + echo -ne " FLAC (.flac)\n" + echo -ne " Monkey's Audio (.ape)\n" + echo -ne " Shorten (.shn)\n" + echo -ne " True Audio (.tta)\n" + echo -ne " WAV (.wav)\n" + echo -ne " WavPack (.wv)\n" + exit +} + +# Function to display colorized output +function cecho () { + MESSAGE=${1:-"Error: No message passed"} + echo -e "${COLOR}${MESSAGE}" + tput sgr0 +} + +# Function to display colorized warnings +function cwarn () { + MESSAGE=${1:-"Error: No message passed"} + echo -e "${COLORWARN}${MESSAGE}" + tput sgr0 +} + +# Function to determine if variable is an integer +function is_int() { + return $(test "$1" -eq "$1" > /dev/null 2>&1); +} + +# Function to check for ffmpeg binary +function ffmpeg_check() { + FFMPEG=$(which ffmpeg 2>/dev/null) + if [ ! -e "$FFMPEG" ]; then + echo "Error: cannot find ffmpeg binary" + MISSING=true + fi +} + +# Function to verify that necessary support binaries exist +function bincheck() { + MISSING='' + case $EXT in + "ape") + MAC=$(which mac 2>/dev/null) + APEINFO=$(which apeinfo 2>/dev/null) + if [ -n "$USEFFMPEG" ]; then + ffmpeg_check + else + [ ! -e "$MAC" ] && MISSING+='mac, ' + [[ -n "$COPYTAGS" && ! -e "$APEINFO" ]] && MISSING+="apeinfo (optional with '-n'), " + fi + ;; + "flac") + if [ -n "$USEFFMPEG" ]; then + echo "Warning: ffmpeg is not used for FLAC (.flac) files" + fi + ;; + "m4a") + ALAC=$(which alac 2>/dev/null) + MP4INFO=$(which mp4info 2>/dev/null) + if [ -n "$USEFFMPEG" ]; then + ffmpeg_check + else + [ ! -e "$ALAC" ] && MISSING+='alac, ' + [[ -n "$COPYTAGS" && ! -e "$MP4INFO" ]] && MISSING+="mp4info (optional with '-n'), " + fi + ;; + "shn") + SHORTEN=$(which shorten 2>/dev/null) + if [ -n "$USEFFMPEG" ]; then + ffmpeg_check + elif [ ! -e "$SHORTEN" ]; then + MISSING+='shorten, ' + fi + ;; + "tta") + TTAENC=$(which ttaenc 2>/dev/null) + if [ ! -e "$TTAENC" ]; then + if [ -n "$USEFFMPEG" ]; then + MISSING+='ttaenc (ffmpeg does not support True Audio (.tta) files), ' + else + MISSING+='ttaenc, ' + fi + elif [ -n "$USEFFMPEG" ]; then + echo "Warning: ffmpeg is not used for True Audio (.tta) files" + fi + ;; + "wav") + if [ -n "$USEFFMPEG" ]; then + echo "Warning: ffmpeg is not used for WAV (.wav) files" + fi + ;; + "wv") + WVUNPACK=$(which wvunpack 2>/dev/null) + if [ -n "$USEFFMPEG" ]; then + ffmpeg_check + elif [ ! -e "$WVUNPACK" ]; then + MISSING+='wvunpack, ' + fi + ;; + esac + if [ -n "$MISSING" ]; then + echo "Error: cannot find the following binaries: ${MISSING%%, }" + exit + fi +} + +# Function to parse mp4info output to find tags and convert to VORBISCOMMENT +function mp4tags() { + TAGS2=${TAGS}.alac + $SED -i "/ \w*: /w${TAGS2}" $TAGS + $SED -i "s/^ //" $TAGS2 + $SED -i "s/: /=/" $TAGS2 + $SED -i "s/ of [0-9]\+//" $TAGS2 + $SED -i "s/\(.*\)=/\U\1=/" $TAGS2 + $SED -i "s/TRACK=/TRACKNUMBER=/;s/YEAR=/DATE=/;s/COMMENTS=/DESCRIPTION=/;s/DISK=/DISCNUMBER=/" $TAGS2 + mv $TAGS2 $TAGS +} + +# Function to parse wvunpack output to find tags and convert to VORBISCOMMENT +function wvtags() { + TAGS2=${TAGS}.wv + $SED -i "/ = /w${TAGS2}" $TAGS + $SED -i "s/ = /=/" $TAGS2 + $SED -i "s/\(.*\)=/\U\1=/" $TAGS2 + $SED -i "s/TRACK=/TRACKNUMBER=/;s/YEAR=/DATE=/;s/COMMENT=/DESCRIPTION=/;s/DISK=/DISCNUMBER=/" $TAGS2 + mv $TAGS2 $TAGS +} + +# Function to copy tags for supported formats +function processtags() { + OUTPUT="\nCopying tags for '$FILE'..." + TAGS=/tmp/$PROG.$RANDOM.tags + if [ "$EXT" == "ape" ]; then + $APEINFO -t "$FILE" >$TAGS + elif [ "$EXT" == "flac" ]; then + $METAFLAC --export-tags-to=$TAGS "$FILE" + elif [ "$EXT" == "m4a" ]; then + $MP4INFO "$FILE" >$TAGS + mp4tags + elif [ "$EXT" == "wv" ]; then + $WVUNPACK -qss "$FILE" >$TAGS + wvtags + else + OUTPUT+=" tags not supported by for this format\n" + return + fi + if [[ $? -ne 0 || ! -s "$TAGS" ]]; then + OUTPUT+="\nWarning: tags could not be read from \"$FILE\"\n" + else + $METAFLAC --import-tags-from=$TAGS "$NAME.flac" + if [[ $? -ne 0 ]]; then + OUTPUT+="\nWarning: tags could not be written to \"$NAME.flac\"\n" + else + OUTPUT+=" complete\n" + fi + fi + rm $TAGS + echo -ne "$OUTPUT" +} + +# Function to perform actual transcoding +function transcode() { + QUIET='' + + # Use ffmpeg, if requested, to decode input + if [ -n "$USEFFMPEG" ] && [ "$EXT" == "ape" -o "$EXT" == "m4a" -o "$EXT" == "shn" -o "$EXT" == "wv" ]; then + WAVENAME="/tmp/$(basename $NAME).wav" + [ $THREADS -gt 1 ] && QUIET='2>/dev/null' + eval $FFMPEG -i \"$FILE\" -f wav \"$WAVENAME.wav\" $QUIET + if [ $? -ne 0 ]; then + cwarn "\nError: \"$FILE\" could not be converted to a FLAC file." + rm "$WAVENAME.wav" + exit 1 + fi + eval $FLAC -$COMPRESS $OVERWRITE -o \"$NAME.flac\" \"$WAVENAME.wav\" $QUIET + if [ $? -ne 0 ]; then + cwarn "\nError: \"$FILE\" could not be converted to a FLAC file." + rm "$WAVENAME.wav" + exit 1 + fi + rm "$WAVENAME.wav" + + # Otherwise, use dedicated binaries for decoding + else + + # Monkey's Audio input + if [ "$EXT" == "ape" ]; then + [ $THREADS -gt 1 ] && QUIET='2>/dev/null' + eval $MAC \"$FILE\" - -d $QUIET | $FLAC -$COMPRESS $OVERWRITE -s -o "$NAME.flac" - + + # FLAC input + elif [ "$EXT" == "flac" ]; then + # Original FLAC file needs to be renamed + if [[ -e "${NAME}_old.flac" ]]; then + if [ $OVERWRITE ]; then + mv -i "$FILE" "${NAME}_old.flac" + else + echo -e "Error: '${NAME}_old.flac' already exists: could not rename input file" + exit 1 + fi + else + mv -i "$FILE" "${NAME}_old.flac" + fi + FILE="${NAME}_old.flac" + [ $THREADS -gt 1 ] && QUIET='-s' + $FLAC -d "$FILE" $QUIET -c | $FLAC -$COMPRESS $OVERWRITE -s -o "$NAME.flac" - + + # ALAC input + elif [ "$EXT" == "m4a" ]; then + $ALAC -t "$FILE" + # .m4a is not a unique extension, so first verify the format + if [ $? -ne 0 ]; then + echo "ERROR: '$FILE' is not a valid ALAC file" + exit 1 + fi + [ $THREADS -gt 1 ] && QUIET='-s' + $ALAC "$FILE" | $FLAC -$COMPRESS $OVERWRITE $QUIET -o "$NAME.flac" - + + # Shorten input + elif [ "$EXT" == "shn" ]; then + [ $THREADS -gt 1 ] && QUIET='-s' + $SHORTEN -x "$FILE" - | $FLAC -$COMPRESS $OVERWRITE $QUIET -o "$NAME.flac" - + + # True Audio input + elif [ "$EXT" == "tta" ]; then + [ $THREADS -gt 1 ] && QUIET='2>/dev/null' + eval $TTAENC -d -o - \"$FILE\" $QUIET | $FLAC -$COMPRESS $OVERWRITE -s -o "$NAME.flac" - + + # WAVE input + elif [ "$EXT" == "wav" ]; then + [ $THREADS -gt 1 ] && QUIET='-s' + $FLAC -$COMPRESS $OVERWRITE $QUIET -o "$NAME.flac" "$FILE" + + # WavPack input + elif [ "$EXT" == "wv" ]; then + [ $THREADS -gt 1 ] && QUIET='-q' + $WVUNPACK $QUIET "$FILE" -o - | $FLAC -$COMPRESS $OVERWRITE -s -o "$NAME.flac" - + fi + fi + + # Abort if transcode failed + if [ ${PIPESTATUS[0]} -ne 0 ]; then + cwarn "\nError: \"$FILE\" could not be converted to a FLAC file." + if [[ "$EXT" == "flac" ]]; then + cwarn "Restoring _old file to original name.\n" + mv "$FILE" "$NAME.flac" + fi + exit 1 + fi + + # Copy metadata to transcoded file, but not for ffmpeg decoding + if [ $COPYTAGS ]; then + processtags + fi + + # Delete old file if requested + if [ "$DELETE" == "prompt" ]; then + echo -ne "\nDelete \"$FILE\"? " + read -e DELPROMPT + if [[ "$DELPROMPT" == "y" || "$DELPROMPT" == "Y" ]]; then + DELETE=force + fi + fi + OUTPUT="\nConversion complete - " + if [ "$DELETE" == "force" ]; then + rm "$FILE" + OUTPUT+="deleted" + elif [ "$DELETE" == "move" ]; then + $TPUT "$FILE" + OUTPUT+="trashed" + else + OUTPUT+="kept" + fi + OUTPUT+=" \"$FILE\"\n\n" + echo -ne "$OUTPUT" +} + +# Process arguments +if [[ $# -eq 0 ]]; then + warning +else + while [ $# -ne 0 ]; do + + # Match known arguments + if [ "$1" == "-h" ]; then + warning + elif [ "$1" == "-V" ]; then + echo "Version $VERSION" + exit + elif [ "$1" == "-d" ]; then + if [ "$DELETE" == "" ]; then + DELETE="force" + else + echo "Error: Only one deletion option (-d, -m, -p) can be specified)" + exit + fi + elif [ "$1" == "-m" ]; then + if [ "$DELETE" == "" ]; then + DELETE="move" + else + echo "Error: Only one deletion option (-d, -m, -p) can be specified)" + exit + fi + elif [ "$1" == "-p" ]; then + if [ $THREADS -gt 1 ]; then + echo "Error: The -p and -t options cannot be used together" + exit + fi + if [ "$DELETE" == "" ]; then + DELETE="prompt" + else + echo "Error: Only one deletion option (-d, -m, -p) can be specified)" + exit + fi + elif [ "${1:0:2}" == "-c" ]; then + COMPRESS=${1:2} + is_int "$COMPRESS" + if [ $? -ne 0 ] || [ $COMPRESS -lt 0 -o $COMPRESS -gt 8 ]; then + echo "Error: You must specify a number between 0-8 for compression (-cN)" + exit + fi + elif [ "${1:0:2}" == "-t" ]; then + PROCS=$(grep -c processor /proc/cpuinfo) + THREADS=${1:2} + is_int "$THREADS" + if [ $? -ne 0 ] || [ $THREADS -lt 1 ]; then + echo "Error: You must specify the number of threads (-tN)" + exit + elif [ ${1:2} -gt $PROCS ]; then + echo "You specified $THREADS threads, but you only have $PROCS processors." + echo "Please specify no more than $PROCS threads." + exit + elif [ "$DELETE" == "prompt" -a $THREADS -gt 1 ]; then + echo "Error: The -p and -t options cannot be used together" + exit + fi + elif [ "$1" == "-n" ]; then + COPYTAGS='' + elif [ "$1" == "-o" ]; then + OVERWRITE='-f' + elif [ "$1" == "-f" ]; then + USEFFMPEG=true + COPYTAGS='' + + # Anything that's not a known argument gets treated as a file + else + FILES[${#FILES[*]}]=$1 + fi + shift + done +fi + +# Validate COMPRESS setting +if [[ "$COMPRESS" != [0-8] ]]; then + echo "Error: FLAC compression level must be between 0 and 8" + exit +fi + +# Define and verify core apps exist +SED=$(which sed 2>/dev/null) +FLAC=$(which flac 2>/dev/null) +METAFLAC=$(which metaflac 2>/dev/null) +TPUT=$(which trash-put 2>/dev/null) +MISSING='' +[ ! -e "$SED" ] && MISSING+='sed, ' +[ ! -e "$FLAC" ] && MISSING+='flac, ' +[ ! -e "$METAFLAC" ] && MISSING+='metaflac, ' +[ "$DELETE" == "move" -a ! -e "$TPUT" ] && MISSING+='trash-put, ' +if [ -n "$MISSING" ]; then + echo "Error: cannot find the following binaries: ${MISSING%%, }" + exit +fi + + +# Process each passed file sequentially +for FILE in ${FILES[@]}; do + # Verify file exists + if [ ! -e "$FILE" ]; then + echo "Error: '$FILE' does not exist" + exit 1 + fi + + # Determine file type and base filename + NAME=${FILE%.*} + EXT=$(echo "${FILE##*.}" | $SED 's/\(.*\)/\L\1/') + + # Exit if wrong file passed + if [[ "$EXT" != "ape" && "$EXT" != "flac" && "$EXT" != "m4a" && "$EXT" != "shn" && "$EXT" != "tta" && "$EXT" != "wav" && "$EXT" != "wv" ]]; then + echo "Error: '$FILE' is not a supported input format" + exit 1 + fi + + # Verify support binaries + bincheck + + # Transcode file, concurrently up to number of specified threads + cecho "\nProcessing '$FILE'...\n" + if [ $(jobs | wc -l) -lt $THREADS ]; then + transcode & + fi + while [ $(jobs | wc -l) -ge $THREADS ]; do + sleep 0.1 + jobs >/dev/null + done +done + +# Wait for any remaining processes to finish before exiting +wait -- cgit v1.2.3-24-g4f1b