flacconv/flacconv

280 lines
9.0 KiB
Plaintext
Raw Normal View History

2022-09-22 13:30:16 -05:00
#!/bin/sh
2022-09-22 13:41:35 -05:00
2023-10-11 19:15:57 -05:00
VERSION=1.4.6
2023-05-04 20:18:53 -05:00
RED="$(printf '\033[38;5;9m')"
GREEN="$(printf '\033[38;5;10m')"
YELLOW="$(printf '\033[38;5;11m')"
RESET="$(printf '\033[m')"
2022-09-22 13:30:16 -05:00
set -euf
BN="${0##*/}"
2023-05-04 20:18:53 -05:00
NL='
'
2023-05-02 15:18:04 -05:00
export POSIXLY_CORRECT=1
2022-12-22 00:21:36 -06:00
2023-06-21 17:15:00 -05:00
if [ -n "${NO_COLOR:-}" ]; then
2023-05-04 20:18:53 -05:00
RED="" GREEN="" YELLOW="" RESET=""
2022-12-22 00:21:36 -06:00
fi
2022-09-22 13:30:16 -05:00
errecho() {
2023-04-12 11:41:47 -05:00
>&2 echo "$*$RESET"
2022-09-22 13:30:16 -05:00
}
fail() {
2023-04-12 11:41:47 -05:00
errecho "${RED}error: $BN: $RESET$*"
2022-09-22 13:30:16 -05:00
exit 1
}
warn() {
2022-12-22 00:21:36 -06:00
errecho "${YELLOW}warning: $BN: $*"
2022-09-22 13:30:16 -05:00
if [ -z "$IGNOREWARNINGS" ]; then
errecho 'you can ignore stopping for warnings in the future with -i'
2022-12-22 00:21:36 -06:00
errecho 'press enter to continue, or C-c to quit'
read -r __
2022-09-22 13:30:16 -05:00
fi
}
checkupdate() {
2023-05-02 15:18:04 -05:00
errecho "you are running version ${GREEN}${VERSION}"
2023-09-06 21:41:59 -05:00
errecho "until I learn if forgejo has an equivalent for github's latest release download shenanigans just check to see if this version matches the latest release"
errecho "https://git.unix.dog/yosh/flacconv/releases/latest"
exit 0
}
2022-09-22 13:30:16 -05:00
metaflac() { command metaflac --no-utf8-convert "$@"; } # don't wanna fuck up any tags from locale shit
2022-12-22 00:21:36 -06:00
convwarn() { errecho "${RED}an error occured while encoding ${RESET}$infile${RED}, skipping and not deleting" && err=1 && DELETE= ; }
2022-10-10 21:52:42 -05:00
_opusenc() {
opusenc --discard-comments \
2022-12-22 00:21:36 -06:00
"$@" \
${pic:+--picture "${pic}"} \
2022-10-10 21:52:42 -05:00
--bitrate "${BITRATE}k" \
"$infile" \
"${infile%.*}.opus"
}
_mp3enc() {
flac --decode --stdout "$infile" | lame \
-q 0 \
2022-12-22 00:21:36 -06:00
${cbr:--V "${VLVL}"} \
2022-10-10 21:52:42 -05:00
--add-id3v2 \
--tt "${title#*=}" \
--ta "${artist#*=}" \
--tl "${album#*=}" \
--ty "${year#*=}" \
--tn "${tracknumber#*=}" \
--tg "${genre#*=}" \
--tc "${comment#*=}" \
--tv "TPE2=${albumartist#*=}" \
2022-12-22 00:21:36 -06:00
${pic:+--ti "${pic}"} \
2022-10-10 21:52:42 -05:00
- \
"${infile%.*}.mp3"
}
2022-09-22 13:30:16 -05:00
process_file() {
infile="$1"
bname="${infile##*/}"
2022-12-22 00:21:36 -06:00
2022-10-10 21:52:42 -05:00
# tagnames will be used for all non-metaflac operations, as it allows for multiline comment shit
2023-05-04 20:18:53 -05:00
tagnames="$(metaflac --export-tags-to=- "$infile" | grep -E '^[A-Z]+=' | cut -d '=' -f 1 | sort | uniq)"
2022-09-22 13:30:16 -05:00
# check if flac has a picture, if not, set RMPIC
metaflac --export-picture-to=- "$infile" 1>/dev/null 2>&1 || RMPIC=1
2022-10-10 21:52:42 -05:00
# first detect any potential ID3 tags
2023-04-19 15:40:43 -05:00
head_bytes="$(dd if="$infile" bs=1 count=3 2>/dev/null)"
2022-10-10 21:52:42 -05:00
if [ "$head_bytes" = "ID3" ]; then
2022-10-11 08:37:02 -05:00
warn "invalid id3 tags found in $infile, you should get that fixed!"
2022-10-10 21:52:42 -05:00
# in a previous version this would remove invalid id3 tags automatically
2023-04-12 11:41:47 -05:00
# I have since realized that this is outside the scope of this script
2022-10-10 21:52:42 -05:00
# additionally, some people may have tags only in the id3 values, and as
# such would be lost forever if removed automatically
# therefore, this script only detects id3 tags and alterts the user
2022-09-22 13:30:16 -05:00
fi
2023-04-12 11:41:47 -05:00
# now test if keepkeys or removekeys was used, set exported tags
2023-06-21 17:15:00 -05:00
if [ -n "$KEEPKEYS" ]; then
2022-10-10 21:52:42 -05:00
tagnames="$(printf '%s' "$tagnames" | grep -i -E "^$KEEPKEYS")"
fi
2023-06-21 17:15:00 -05:00
if [ -n "$REMOVEKEYS" ]; then
2022-09-22 13:30:16 -05:00
[ "$REMOVEKEYS" = "ALL" ] && REMOVEKEYS=''
2022-10-10 21:52:42 -05:00
tagnames="$(printf '%s' "$tagnames" | grep -v -i -E "^$REMOVEKEYS")"
2022-09-22 13:30:16 -05:00
fi
# export pic if we're keeping it
if [ -z "$RMPIC" ]; then
2023-11-08 18:15:06 -06:00
pic="$(mktemp "${bname%.*}XXXX")"
metaflac --export-picture-to="$pic" "$infile"
fi
2022-09-22 13:30:16 -05:00
# time to encode
if [ "$TYPE" = "opus" ]; then
VLVL=
2022-10-10 21:52:42 -05:00
# build opus comment chain, can't "import" tags like with flac
2022-12-24 17:30:35 -06:00
set --
2023-06-21 17:15:00 -05:00
[ -n "$tagnames" ] && while read -r name; do
2023-05-04 20:18:53 -05:00
tag="$(metaflac --show-tag="$name" "$infile")"
# need a while loop here to handle tags with multiple values
while [ "$tag" != "${tag#*$name=}" ]; do # check if we're on last one
tag="${tag#*$name=}" # trim prefix
set -- "$@" "--comment" "$name=${tag%%$NL$name=*}" # set to val without suffix*
done
2022-09-22 13:30:16 -05:00
done <<-EOF
2022-10-10 21:52:42 -05:00
$tagnames
2022-09-22 13:30:16 -05:00
EOF
2023-06-21 17:15:00 -05:00
if [ -n "$VERBOSE" ]; then
2022-12-22 00:21:36 -06:00
_opusenc "$@" || convwarn
2022-10-10 21:52:42 -05:00
else
2022-12-22 00:21:36 -06:00
_opusenc "$@" 1>/dev/null 2>&1 || convwarn
2022-10-10 21:52:42 -05:00
fi
2023-04-12 11:41:47 -05:00
# remove encoding metadata
2023-06-21 17:15:00 -05:00
[ -n "$RMENCTAG" ] && opustags -d ENCODER -d ENCODER_OPTIONS -i "${infile%.*}.opus"
2022-09-22 13:30:16 -05:00
else
2022-10-10 21:52:42 -05:00
album="$(metaflac --show-tag=album "$infile")"
artist="$(metaflac --show-tag=artist "$infile")"
albumartist="$(metaflac --show-tag=albumartist "$infile")"
title="$(metaflac --show-tag=title "$infile")"
year="$(metaflac --show-tag=date "$infile")"
genre="$(metaflac --show-tag=genre "$infile")"
tracknumber="$(metaflac --show-tag=tracknumber "$infile")"
comment="$(metaflac --show-tag=comment "$infile")"
2023-06-21 17:15:00 -05:00
[ -z "$VLVL" ] && cbr="-b $BITRATE --cbr"
if [ -n "$VERBOSE" ]; then
2022-10-10 21:52:42 -05:00
_mp3enc || convwarn
else
_mp3enc 1>/dev/null 2>&1 || convwarn
fi
2022-09-22 13:30:16 -05:00
fi
2023-04-12 11:41:47 -05:00
2023-06-21 17:15:00 -05:00
[ -n "${pic:-}" ] && rm -f "$pic"
[ -n "$DELETE" ] && rm -f "$infile"
2023-09-06 21:09:07 -05:00
[ -z "${err:-}" ] && errecho "$infile ${GREEN}successfully converted to ${RESET}$TYPE${GREEN} with bitrate/quality ${RESET}${VLVL:+V}${VLVL:-${BITRATE}k}"
2022-09-22 13:30:16 -05:00
}
usage() {
2023-04-19 15:40:43 -05:00
cat >&2 <<-EOF
2023-05-02 15:18:04 -05:00
flacconv $VERSION
2023-04-19 15:40:43 -05:00
usage: $BN [-huvVipe3] [-b BITRATE] [-l LEVEL] [-k KEYS] [-r KEYS] [-j THREADS] [--] [DIRECTORY...]
$BN recursively converts directories of flac files to opus/mp3
DIRECTORY can be specified multiple times. if omitted, the current directory is used
by default, this script outputs opus with variable bitrate 128k
2023-05-02 15:18:04 -05:00
${RED}IF ENCODING TO MP3, -k AND -r WILL NOT WORK.${RESET} the only metadata that will be kept is the following:
${YELLOW}TITLE, ARTIST, ALBUM, ALBUMARTIST, DATE, GENRE, TRACKNUMBER, COMMENT, and the cover picture${RESET}
2023-04-19 15:40:43 -05:00
(blame id3. just use opus)
-h show script help
-u check for updates
2023-05-02 15:18:04 -05:00
-v verbose output ${YELLOW}(messy with multithreading)${RESET}
-V very verbose output ${RED}(VERY messy, use only for debugging and with like, -j 1)${RESET}
2023-04-19 15:40:43 -05:00
-i ignore script-stopping warnings
-d delete original flac files after transcoding
-3 switch output filetype to mp3
-b <BITRATE> output bitrate in kbits (default 128)
this value is variable for opus & CBR for mp3
-l <LEVEL> mp3 only: use specified mp3 variable quality (0-9). integer only
2023-05-02 15:18:04 -05:00
${YELLOW}OVERRIDES -b${RESET}
2023-04-19 15:40:43 -05:00
-k <KEYS> keep specified flac metadata KEYS in output file
keys can be checked with metaflac --export-tags-to=- FILE
2023-05-02 15:18:04 -05:00
option argument is a ${YELLOW}PIPE-separated${RESET} list of keys to keep, case-insensitive
2023-04-19 15:40:43 -05:00
(i.e. -k "artist|title|albumartist|album|date")
2023-05-02 15:18:04 -05:00
${YELLOW}if both -k and -r are not present, all keys are kept.${RESET}
2023-04-19 15:40:43 -05:00
-r <KEYS> remove specified flac metadata KEYS in output file
option argument is of the same format as -k
2023-05-02 15:18:04 -05:00
${YELLOW}if set to "ALL" (with capitalization), all keys are removed${RESET}
2023-04-19 15:40:43 -05:00
-p remove embedded picture in output files
-e remove the "encoder" tag that automatically gets applied with opusenc
(requires opustags)
-j <THREADS> use the specified amount of threads for parallel processing
if omitted, CPU core count will be used
EOF
2022-09-22 13:30:16 -05:00
}
2022-10-10 21:52:42 -05:00
VERBOSE=
REALLYVERBOSE=
DELETE=
2022-09-22 13:30:16 -05:00
VLVL=
RMPIC=
2023-04-12 11:41:47 -05:00
RMENCTAG=
2022-09-22 13:30:16 -05:00
KEEPKEYS=
REMOVEKEYS=
IGNOREWARNINGS=
2023-05-02 15:18:04 -05:00
TYPE=opus
BITRATE=128
2023-04-12 11:41:47 -05:00
THREADS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null)" # hopefully portable enough to not be an issue
2023-06-21 17:15:00 -05:00
[ $? -ne 0 ] && warn 'unable to find number of cores! defaulting to 1 thread unless otherwise specified...' && THREADS=1
2022-09-22 13:30:16 -05:00
2023-04-12 11:41:47 -05:00
while getopts :huvVid3peb:l:k:r:j: OPT; do
2022-09-22 13:30:16 -05:00
case "$OPT" in
h) usage && exit 0 ;;
u) checkupdate ;;
2022-10-10 21:52:42 -05:00
v) VERBOSE=1 ;;
V) VERBOSE=1; REALLYVERBOSE=1 ;;
i) IGNOREWARNINGS=1 ;;
2022-09-22 13:30:16 -05:00
d) DELETE=1 ;;
3) TYPE=mp3 ;;
p) RMPIC=1 ;;
2023-04-12 11:41:47 -05:00
e) RMENCTAG=1 ;;
2022-09-22 13:30:16 -05:00
b) BITRATE="$OPTARG" ;;
2022-10-10 21:52:42 -05:00
l) VLVL="$OPTARG" ;;
2022-09-22 13:30:16 -05:00
k) KEEPKEYS="$OPTARG" ;;
r) REMOVEKEYS="$OPTARG" ;;
j) THREADS="$OPTARG" ;;
*) fail "unknown option: -$OPTARG. run $BN -h to see all options" ;;
esac
done
shift "$((OPTIND - 1))"
2023-06-21 17:15:00 -05:00
[ -n "$REALLYVERBOSE" ] && set -x
2023-11-08 18:15:06 -06:00
2022-09-22 13:30:16 -05:00
command -v metaflac 1>/dev/null 2>&1 || fail 'flac tools are not installed! (metaflac)'
2023-11-08 18:15:06 -06:00
if [ "$TYPE" = "opus" ]; then
command -v opusenc 1>/dev/null 2>&1 || fail 'opus-tools is not installed! (opusenc)'
else
command -v lame 1>/dev/null 2>&1 || fail 'lame is not installed! (lame)'
2023-05-02 15:18:04 -05:00
fi
2023-06-21 17:15:00 -05:00
[ -n "$RMENCTAG" ] && { command -v opustags 1>/dev/null 2>&1 || fail 'opustags is not installed! this is required for -e (opustags)' ; }
2022-09-22 13:30:16 -05:00
2023-11-08 18:15:06 -06:00
# if no arg provided, use cwd
[ "$#" -eq 0 ] && set -- .
2022-09-22 13:30:16 -05:00
# this script assumes you aren't using newlines in path names
# I do not want to change it to account for this
2023-05-02 15:18:04 -05:00
FLACFILES="$(find "$@" -type f -name "*.[fF][lL][aA][cC]")"
2023-09-06 21:09:07 -05:00
[ -z "$FLACFILES" ] && fail 'no flac files found!'
2022-09-22 13:30:16 -05:00
# make a fifo/fd for parallel stuff
mk_parallel_fd() {
2023-11-08 18:15:06 -06:00
fifo_para="$(mktemp -u -t "flacconv.XXXX")"
2022-09-22 13:30:16 -05:00
mkfifo "$fifo_para"
exec 9<>"$fifo_para"
rm -f "$fifo_para"
while [ "$THREADS" -gt 0 ]; do
printf "\n" >&9 # start with THREADS amount of lines in fd 9 for later
THREADS="$((THREADS - 1))"
done
}
# read each line from fd 9, launch new program for each line
# print a line after program finished such that another one can take its place
run_in_parallel() {
cmd="$1"
shift
read -r __ <&9
{
"$cmd" "$@"
printf '\n' >&9
} &
}
2023-04-12 11:41:47 -05:00
errecho "${YELLOW}using ${RESET}$THREADS${YELLOW} threads"
2022-09-22 13:30:16 -05:00
mk_parallel_fd
while read -r file; do
run_in_parallel process_file "$file"
done <<-EOF
$FLACFILES
EOF
wait