Last active
June 19, 2024 06:46
-
-
Save berturion/5377d6653ef93049f4ff54cff2003e11 to your computer and use it in GitHub Desktop.
Encode flac files to opus preserving folder structure and stripping ID3 tags
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Copyright 2019 Bertrand Michas <[email protected]> | |
# | |
# 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 |
Hello,
Sorry, I haven't used this script for several years.
If I have time I'll have a look at it sometime. I can't promise anything though, I'm pretty busy at the moment.
Have a nice day.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
oops it seems Github has automatically removed the double white spaces there lol but you probably get the idea