Skip to content

Instantly share code, notes, and snippets.

@DroidFreak32
Forked from berturion/flac2opus.sh
Created December 8, 2019 18:31
Show Gist options
  • Save DroidFreak32/3e1034c5b268088d37b7a13fbfaeb863 to your computer and use it in GitHub Desktop.
Save DroidFreak32/3e1034c5b268088d37b7a13fbfaeb863 to your computer and use it in GitHub Desktop.
Encode flac files to opus preserving folder structure and stripping ID3 tags
#!/bin/bash
# Copyright 2019 Bertrand Michas <berturion@free.fr>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCNUMBERLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# "global" variables
USER=$(whoami)
SCRIPT=`realpath $0`
SCRIPT_FILE_NAME=`basename $0`
DIR=`dirname $SCRIPT`
DATEEXEC=`date "+%Y/%m/%d %T"`
# params related variables
ID3_STRIP_ACTION=0
REMOVE_EXISTING_ACTION=0
INPUT_FOLDER=""
OUTPUT_FOLDER=""
BITRATE=0
MIN_BITRATE=16
MAX_BITRATE=192
N=1
MIN_JOBS=1
MAX_JOBS=32
function usage {
cat <<EOF
HELP for $SCRIPT_FILE_NAME:
------------
This script can do multiple actions on flac files.
- Collect all flac files found recursively in a folder to a file
- Remove any ID3 tags in place (ID3 tags are invalid for flac files).
This action modifies the files but preserve all metatada.
- Encode all flac files found into the "input_folder" to the
"output_folder", folder structure is preserved.
- Delete opus encoded target files if exist
Usage: $SCRIPT_FILE_NAME -i input_folder [options]
Options:
-h Show help
-i [required] "input_folder": input folder containing flac
files.
-c [optional] "clear_id3": clear all ID3 tags from flac
files preserving metadata (ID3 tags for flac files are invalid).
-o [optional] "output_folder": output folder where encoded opus
files will be stored. Required with options "-d" or "-b".
-d [optional] "delete_existing": delete existing opus files
in output_folder.
-b [optional] "bitrate": encode flac files from "input_folder"
to opus files into the "output_folder" with the specified vbr
bitrate. Must be between $MIN_BITRATE and $MAX_BITRATE.
Encoding will fail if an ID3 tag is present or if the opus
encoded file already exists.
-j [optional] "jobs": number of parallel jobs. Between
$MIN_JOBS and $MAX_JOBS. Default $N.
Examples :
'$SCRIPT_FILE_NAME -i "path/to/flacs/" -o "path/to/opus/" -d -c -b 96 -j 4
'$SCRIPT_FILE_NAME -h'
Requirements :
flac, opus-tools, id3v2
EOF
}
which metaflac >> /dev/null
if [ $? -ne 0 ]; then
echo "flac tools are not installed. Run \`sudo pacman -S flac\` and try again."
exit 1
fi
which opusenc >> /dev/null
if [ $? -ne 0 ]; then
echo "opus-tools is not installed. Run \`sudo pacman -S opus-tools\` and try again"
exit 1
fi
which id3v2 >> /dev/null
if [ $? -ne 0 ]; then
echo "id3v2 is not installed. Run \`sudo pacman -S id3v2\` and try again"
exit 1
fi
# test that argument is a folder
# if this is not a folder, returns an empty string
# adds a trailing slash if missing
sanitize_folder() {
local folder="$1"
if [ -z "$1" ]; then
folder=""
elif [ ! -d $1 ]; then
folder=""
elif [[ ! "$1" == */ ]];then
folder="$folder/"
fi
echo $folder
}
while getopts "hi:co:edb:j:" opt; do
case $opt in
h)
usage
exit 0
;;
i)
# INPUT_FOLDER=`realpath $OPTARG`
INPUT_FOLDER=`sanitize_folder "$OPTARG"`
if [ ! -d "$INPUT_FOLDER" ];then
echo "-i \"$OPTARG\" is not a folder"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
if [ ! -w "$INPUT_FOLDER" ];then
echo "-i \"$OPTARG\" is not writable"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
;;
c)
ID3_STRIP_ACTION=1
;;
o)
# OUTPUT_FOLDER=`realpath $OPTARG`
OUTPUT_FOLDER=`sanitize_folder $OPTARG`
if [ ! -d "$OUTPUT_FOLDER" ];then
echo "-o \"$OPTARG\" is not a folder"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
if [ ! -w "$OUTPUT_FOLDER" ];then
echo "-o \"$OPTARG\" is not writable"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
;;
d)
REMOVE_EXISTING_ACTION=1
;;
b)
BITRATE=$(sed 's/[^0-9]//g' <<< $OPTARG)
if [ -z $BITRATE ]; then
echo "-b \"$OPTARG\" invalid bitrate"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
if [ $BITRATE -gt $MAX_BITRATE ]; then
echo "-b \"$OPTARG\" greater than $MAX_BITRATE"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
if [ $BITRATE -lt $MIN_BITRATE ]; then
echo "-b \"$OPTARG\" lesser than $MIN_BITRATE"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
;;
j)
N=$(sed 's/[^0-9]//g' <<< $OPTARG)
if [ -z $N ]; then
echo "-j \"$OPTARG\" invalid number of jobs"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
if [ $N -lt $MIN_JOBS ]; then
echo "-j \"$OPTARG\" lesser than $MIN_JOBS"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
if [ $N -gt $MAX_JOBS ]; then
echo "-j \"$OPTARG\" greater than $MAX_JOBS"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
;;
\?)
echo "Invalid option: -$OPTARG"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
;;
:)
echo "Option -$OPTARG requires an argument."
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
;;
esac
done
# Handle missing required params
if [ -z $OUTPUT_FOLDER ] ; then
if [ $BITRATE -gt 0 ] ; then
echo "Output folder is required to encode the files"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
if [ $REMOVE_EXISTING_ACTION -gt 0 ] ; then
echo "Output folder is required to remove existing encoded files"
echo "Call '$SCRIPT_FILE_NAME -h' to see usage"
exit 1
fi
fi
echo "Enabled actions:"
echo "- Collect flac files"
if [ $BITRATE -gt 0 ];then
echo "- Encode with a bitrate of $BITRATE"
fi
if [ $REMOVE_EXISTING_ACTION -gt 0 ];then
echo "- Remove encoded existing encoded file"
fi
if [ $ID3_STRIP_ACTION -gt 0 ];then
echo "- Strip ID3 tags from flac files"
fi
echo "Parameters:"
echo "- Input folder: $INPUT_FOLDER"
echo "- Output folder: $OUTPUT_FOLDER"
echo "- Number of parallel jobs: $N"
COLLECTED_FLAC_FILES_FILE="${INPUT_FOLDER}collected_flac_files.txt"
rm -f "$COLLECTED_FLAC_FILES_FILE"
# Process flac file with chosen actions
# params:
# - "$1" : input flac file
process_flac() {
flac_file="$1"
echo "Processing $flac_file"
if [[ "$flac_file" == *.flac ]];then
if [ $ID3_STRIP_ACTION -eq 1 ];then
head_bytes=`dd if="$flac_file" of=/dev/stdout bs=1 count=3 2>/dev/null`
if [ "$head_bytes" = "ID3" ];then
echo "Invalid ID3 tag in this .flac file."
metaflac_file="${flac_file}.metaflac"
# Remove invalid ID3 tag from flac file
metaflac --export-tags-to="$metaflac_file" "$flac_file"
id3v2 -D "$flac_file"
metaflac --remove-all-tags --import-tags-from="$metaflac_file" "$flac_file"
rm "$metaflac_file"
fi
fi
new_file="${1/$INPUT_FOLDER/$OUTPUT_FOLDER}"
new_file="${new_file/%flac/opus}"
new_folder="$(dirname "$new_file")"
if [[ -f "$new_file" ]] && [[ $REMOVE_EXISTING_ACTION -eq 1 ]]; then
rm -v "$new_file"
fi
if [ $BITRATE -gt 0 ];then
if [ ! -f "$new_file" ]; then
mkdir -p "$new_folder" && opusenc --vbr --bitrate "$BITRATE" "$flac_file" "$new_file" 2> /dev/null
if [ $? -gt 0 ] ; then
echo "Error encoding $flac_file"
else
echo "Encoded into $new_file"
fi
else
echo "Encoded file already exists: $new_file"
fi
fi
fi
}
# Recursive walk through directories and put each flac file found into
# "$COLLECTED_FLAC_FILES_FILE" file
recurse_conv() {
for i in "$1"/* ; do
if [ -d "$i" ];then
recurse_conv "$i"
elif [ -f "$i" ]; then
found_file="$i"
if [[ $found_file == *.flac ]];then
NB_FLAC_FILES=$NB_FLAC_FILES+1
echo $found_file >> "$COLLECTED_FLAC_FILES_FILE"
fi
fi
done
}
# @see https://unix.stackexchange.com/questions/103920/parallelize-a-bash-for-loop
open_sem(){
mkfifo pipe-$$
exec 3<>pipe-$$
rm pipe-$$
local i=$1
for((;i>0;i--)); do
printf %s 000 >&3
done
}
run_with_lock(){
local x
read -u 3 -n 3 x && ((0==x)) || exit $x
(
( "$@"; )
printf '%.3d' $? >&3
)&
}
echo "Recursive flac collection into $COLLECTED_FLAC_FILES_FILE"
# Removing all trailing slashes before recursive loop in folders
TMP_INPUT_FOLDER=`echo "$INPUT_FOLDER" | sed 's/\/*$//'`
recurse_conv "$TMP_INPUT_FOLDER"
if [[ $BITRATE -gt 0 ]] || [[ $REMOVE_EXISTING_ACTION -gt 0 ]] || [[ $ID3_STRIP_ACTION -gt 0 ]]; then
echo "Parallel processing"
open_sem $N
while read p; do
run_with_lock process_flac "$p"
done <"$COLLECTED_FLAC_FILES_FILE"
fi
# wait until all parallel jobs are finished
wait
echo "Done"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment