transcode/transcode.sh

223 lines
7.2 KiB
Bash
Executable file

#!/usr/bin/env bash
set -eu
tmp_directory=/tmp/transcode
lock_file=/tmp/music-transcode.lock
function die()
{
printf '%s\n' "$1" >&2
exit "${2-1}"
}
function transcode_to_mp3()
{
input_directory="$1"
input_file_name="$2"
mp3_directory="$3"
input_path="$input_directory/$input_file_name"
output_file_name="$(echo "$input_file_name" | sed -E 's/(\.[^. ]+)?$/.mp3/g')"
output_path="$mp3_directory/$output_file_name"
output_directory="$(dirname "$output_path")"
if [ ! -d "$output_directory" ] ; then
echo "New Directory: \"$output_directory\""
mkdir -p "$output_directory" || die "Could not create directory \"$output_directory\"."
fi
transcode=false
if [ ! -f "$output_path" ] ; then
echo "New MP3: \"$output_path\""
transcode=true
elif [ "$input_path" -nt "$output_path" ] ; then
echo "Input file is newer: \"$output_path\""
transcode=true
fi
if [ "$transcode" = true ] ; then
input_type="$(file -b --mime-type "$input_path")"
case "$input_type" in
"audio/flac"|"audio/x-flac")
tmp_path="$tmp_directory/$(basename "$output_path")"
echo "Transcoding MP3..."
ffmpeg -hide_banner -loglevel fatal -i "$input_path" -y -b:a 320k -qscale:a 2 -id3v2_version 3 -f mp3 "$tmp_path" < /dev/null || die "Failed to transcode to \"$tmp_path\"."
mv -f "$tmp_path" "$output_path" || die "Could not move file to \"$output_path\"."
;;
application/octet-stream)
echo "Warning: Assuming \"$input_path\" is a MP3 file."
;&
audio/mpeg)
echo "Copying MP3..."
cp -f "$input_path" "$output_path"
;;
*)
echo "Unsupported input type $input_type (\"$input_path\")"
return 1
;;
esac
fi
}
function ogg_add_cover()
{
ogg_file="$1"
cover_file="$2"
tmp_metafile="$tmp_directory/metadata"
tmp_mdimg="$tmp_directory/image-with-header"
cover_mime_type="$(file -b --mime-type "$cover_file")"
description=""
vorbiscomment --list --raw "$ogg_file" > "$tmp_metafile" && \
sed -i -e '/^metadata_block_picture/d' "$tmp_metafile" && \
echo -n "" > "$tmp_mdimg" && \
printf "0: %.8x" 3 | xxd -r -g0 >> "$tmp_mdimg" && \
printf "0: %.8x" "$(echo -n "$cover_mime_type" | wc -c)" | xxd -r -g0 >> "$tmp_mdimg" && \
echo -n "$cover_mime_type" >> "$tmp_mdimg" && \
printf "0: %.8x" "$(echo -n "$description" | wc -c)" | xxd -r -g0 >> "$tmp_mdimg" && \
echo -n "$description" >> "$tmp_mdimg" && \
printf "0: %.8x" 0 | xxd -r -g0 >> "$tmp_mdimg" && \
printf "0: %.8x" 0 | xxd -r -g0 >> "$tmp_mdimg" && \
printf "0: %.8x" 0 | xxd -r -g0 >> "$tmp_mdimg" && \
printf "0: %.8x" 0 | xxd -r -g0 >> "$tmp_mdimg" && \
printf "0: %.8x" "$(wc -c "$cover_file" | cut --delimiter=' ' --fields=1)" | xxd -r -g0 >> "$tmp_mdimg" && \
cat "$tmp_cover" >> "$tmp_mdimg" && \
echo "metadata_block_picture=$(base64 --wrap=0 < "$tmp_mdimg")" >> "$tmp_metafile" && \
vorbiscomment --write --raw --commentfile "$tmp_metafile" "$ogg_file" && \
rm "$tmp_metafile" "$tmp_mdimg"
}
function transcode_to_ogg()
{
input_directory="$1"
input_file_name="$2"
ogg_directory="$3"
input_path="$input_directory/$input_file_name"
output_file_name="$(echo "$input_file_name" | sed -E 's/(\.[^. ]+)?$/.ogg/g')"
output_path="$ogg_directory/$output_file_name"
directory="$(dirname "$output_path")"
if [ ! -d "$directory" ] ; then
echo "New Directory: \"$directory\""
mkdir -p "$directory" || die "Could not create directory \"$directory\"."
fi
transcode=false
if [ ! -f "$output_path" ] ; then
echo "New OGG: \"$output_path\""
transcode=true
elif [ "$input_path" -nt "$output_path" ] ; then
echo "Input file is newer: \"$output_path\""
transcode=true
fi
if [ "$transcode" = true ] ; then
input_type="$(file -b --mime-type "$input_path")"
tmp_ogg="$tmp_directory/$(basename "$output_path")"
tmp_cover="$tmp_directory/cover"
case "$input_type" in
"audio/flac"|"audio/x-flac")
echo "Transcoding OGG..."
ffmpeg -hide_banner -loglevel fatal -i "$input_path" -y -map a -qscale:a 6 -id3v2_version 3 -f ogg "$tmp_ogg" < /dev/null || die "Could not transcode to \"$tmp_ogg\"."
echo "Adding OGG cover image..."
metaflac --export-picture-to="$tmp_cover" "$input_path" || die "Could not export cover image."
ogg_add_cover "$tmp_ogg" "$tmp_cover" || die "Could not add cover image to \"$tmp_ogg\"."
mv -f "$tmp_ogg" "$output_path" || die "Could not move file to \"$output_path\"."
;;
application/octet-stream)
echo "Warning: Assuming \"$input_path\" is a MP3 file."
;&
audio/mpeg)
echo "Transcoding OGG..."
ffmpeg -hide_banner -loglevel fatal -i "$input_path" -y -map a -qscale:a 6 -id3v2_version 3 -f ogg "$tmp_ogg" || die "Could not transcode to \"$tmp_ogg\"."
echo "Adding OGG cover image..."
exiftool -Picture -b "$input_path" > "$tmp_cover" || die "Could not export cover image."
ogg_add_cover "$tmp_ogg" "$tmp_cover" || die "Could not add cover image to \"$tmp_ogg\"."
mv -f "$tmp_ogg" "$output_path" || die "Could not move file to \"$output_path\"."
;;
*)
echo "Unsupported input type $input_type (\"$input_path\")"
return 1
;;
esac
fi
}
if [ -f "$lock_file" ] ; then
echo "Another instance is already running. Exiting."
echo "If you are certain this is the only instance, you can delete the lock file $lock_file"
exit 2
else
touch "$lock_file" || die "Could not create lock file."
fi
function cleanup()
{
rm -f "$lock_file"
rm -rf "$tmp_directory"
}
trap cleanup EXIT SIGTERM
if [ ! -d "$tmp_directory" ] ; then
mkdir -p "$tmp_directory" || die "Could not create temporary directory \"$tmp_directory\"."
fi
mp3_out=''
ogg_out=''
while [ $# -gt 1 ] ; do
case "$1" in
'--mp3-out')
mp3_out="$2"
shift 2
;;
'--ogg-out')
ogg_out="$2"
shift 2
;;
'--')
shift
break
;;
*)
>&2 echo "Unknown argument: $1"
exit 1
;;
esac
done
if [ $# -lt 1 ] ; then
>&2 echo "Missing input directory argument"
exit 1
fi
input_directory="$1"
while IFS= read -r -d '' file; do
if [ "$mp3_out" != '' ] ; then
transcode_to_mp3 "$input_directory" "$file" "$mp3_out" || break
fi
if [ "$ogg_out" != '' ] ; then
transcode_to_ogg "$input_directory" "$file" "$ogg_out" || break
fi
done < <(
find -P "$input_directory" \( -iname '*.flac' -or -iname '*.mp3' \) -printf '%P\0' | sort -z
)
echo "Done transcoding."