Compare commits

...

10 Commits

Author SHA1 Message Date
yosh 5138132c46 paranoia 2023-06-21 17:15:00 -05:00
yosh eccc30ea85 1.4.4 changes yippee 2023-05-04 21:18:53 -04:00
yosh eba9ccc504 silly edits 2023-05-02 16:23:49 -04:00
yosh 18ceb3c756 find update, dd update, usage 2023-04-19 16:48:05 -04:00
yosh 22ac378902 use backup tput commands in case setf fails 2023-04-15 13:30:56 -04:00
yosh c936bcb704 1.4.0 updates 2023-04-12 12:41:47 -04:00
yosh 6bb63d9d74 remove quotes on readme usage 2022-12-24 17:30:58 -06:00
yosh 257dc133eb fix double newline tag stuff 2022-12-24 17:30:35 -06:00
yosh aa0eb35dad oops I fucked up my two versions of this 2022-12-22 11:43:36 -06:00
yosh 96e9e0f9dd 1.3.0 - yippee!!! 2022-12-22 00:24:00 -06:00
2 changed files with 122 additions and 94 deletions

View File

@ -1,5 +1,5 @@
# flacconv
flacconv is a 100% POSIX[^1] shell script that recursively converts directories of flac files to opus/mp3.
flacconv is a (hopefully) 100% POSIX and portable[^1] shell script that recursively converts directories of flac files to opus/mp3.
when invoked with no arguments, it recursively converts the current directory's flac files to opus with a bitrate of 128k, retaining all metadata and not deleting the original flac files
@ -7,15 +7,19 @@ it also has options for you to change the bitrate, use a variable quality for mp
## dependencies
- Some implementation of a shell and [POSIX Utilities](https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html) (usually, this is GNU coreutils, which you probably have)
- `mktemp -u` (again, probably have this already)
- `flac`
- `lame` (required for mp3, not required otherwise)
- `opus-tools` (required for opus, not required otherwise)
- `lame` (only if encoding to mp3)
- `opus-tools` (only if encoding to opus)
- [opustags](https://github.com/fmang/opustags) (optional, to use the -e option)
- `curl` (optional, for checking for updates)
## usage
```
"usage: flacconv [-huvVip3] [-b BITRATE] [-l LEVEL] [-k KEYS] [-r KEYS] [-j THREADS] [--] [DIRECTORY...]
usage: flacconv [-huvVipe3] [-b BITRATE] [-l LEVEL] [-k KEYS] [-r KEYS] [-j THREADS] [--] [DIRECTORY...]
flacconv 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
IF ENCODING TO MP3, -k AND -r WILL NOT WORK. the only metadata that will be kept is the following:
TITLE, ARTIST, ALBUM, ALBUMARTIST, DATE, GENRE, TRACKNUMBER, COMMENT, and the cover picture
(blame id3. just use opus)
@ -38,16 +42,18 @@ IF ENCODING TO MP3, -k AND -r WILL NOT WORK. the only metadata that will be kept
if both -k and -r are not present, all keys are kept.
-r <KEYS> remove specified flac metadata KEYS in output file
option argument is of the same format as -k
if set to 'ALL', all keys are removed
if set to "ALL" (with capitalization), all keys are removed
-p remove embedded picture in output files
-e remove the "encoder" tags 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"
if omitted, CPU core count will be used
```
## examples
recursively convert a directory of flacs to opus 128k, removing the picture for every one
recursively convert a directory of flacs to opus 128k, removing the picture and ENCODER tags for every file
`flacconv -p /path/to/dir`
`flacconv -p -e /path/to/dir`
recursively convert the current directory to mp3 320k
@ -59,6 +65,6 @@ recursively convert current directory to opus 160k, while removing any COMMENT o
recursively convert current directory to mp3 v0, removing all pictures
`flacconv -3 -v 0 -p`
`flacconv -3 -l 0 -p`
[^1]: tested with dash, bash, and yash (set -o posixly-correct) on linux. further testing encouraged

192
flacconv Normal file → Executable file
View File

@ -1,38 +1,43 @@
#!/bin/sh
# flacconv 1.2.2 // use https://github.com/yoshiyoshyosh/flacconv for issues
VERSION=1.4.4
RED="$(printf '\033[38;5;9m')"
GREEN="$(printf '\033[38;5;10m')"
YELLOW="$(printf '\033[38;5;11m')"
RESET="$(printf '\033[m')"
set -euf
TAB="$(printf '\t')"
VTAB="$(printf '\v')" # need a really obscure control character for IFS shenanigans
BN="${0##*/}"
NL='
'
BN="${0##*/}"
POSIXLY_CORRECT=1
export POSIXLY_CORRECT=1
if [ -n "${NO_COLOR:-}" ]; then
RED="" GREEN="" YELLOW="" RESET=""
fi
errecho() {
>&2 echo "$@"
>&2 echo "$*$RESET"
}
fail() {
errecho "error: $BN: $*"
errecho "${RED}error: $BN: $RESET$*"
exit 1
}
warn() {
errecho "warning: $BN: $*"
errecho "${YELLOW}warning: $BN: $*"
if [ -z "$IGNOREWARNINGS" ]; then
errecho 'you can ignore stopping for warnings in the future with -i'
errecho 'press enter to continue, or C-v to quit'
errecho 'press enter to continue, or C-c to quit'
read -r __
fi
}
_tempdir() {
set +u
[ "$TMPDIR" ] || \
{ [ "$TEMP" ] && TMPDIR="$TEMP"; } || \
{ [ "$TMP" ] && TMPDIR="$TMP"; } || \
[ -n "$TMPDIR" ] || \
{ [ -n "$TEMP" ] && TMPDIR="$TEMP"; } || \
{ [ -n "$TMP" ] && TMPDIR="$TMP"; } || \
{ [ -d "/tmp" ] && TMPDIR="/tmp"; } || \
{ [ -d "/var/tmp" ] && TMPDIR="/var/tmp"; } || \
{ [ -d "/usr/tmp" ] && TMPDIR="/usr/tmp"; } || \
@ -41,25 +46,26 @@ _tempdir() {
}
checkupdate() {
errecho 'checking for updates...'
if curl -s https://raw.githubusercontent.com/yoshiyoshyosh/flacconv/main/flacconv | cmp -s "$0" -; then
errecho 'your script is updated to the latest version!'
errecho "you are running version ${GREEN}${VERSION}"
errecho "${YELLOW}checking for updates..."
if curl -s https://github.com/yoshiyoshyosh/flacconv/releases/latest/download/flacconv | cmp -s "$0" -; then
errecho "${GREEN}your script is updated to the latest version!"
else
errecho 'your script differs from remote!'
errecho 'if this is unintentional, download the newest version from:'
errecho 'https://raw.githubusercontent.com/yoshiyoshyosh/flacconv/main/flacconv'
errecho "${RED}your script differs from remote!"
errecho 'if this is unintentional, download the newest version from'
errecho 'https://github.com/yoshiyoshyosh/flacconv/releases/latest'
fi
exit 0
}
metaflac() { command metaflac --no-utf8-convert "$@"; } # don't wanna fuck up any tags from locale shit
convwarn() { errecho "an error occured while encoding $infile, skipping and not deleting" && err=1 && DELETE= ; }
convwarn() { errecho "${RED}an error occured while encoding ${RESET}$infile${RED}, skipping and not deleting" && err=1 && DELETE= ; }
_opusenc() {
opusenc --discard-comments \
$opustags \
${pic:+--picture${VTAB}"${pic}"} \
"$@" \
${pic:+--picture "${pic}"} \
--bitrate "${BITRATE}k" \
"$infile" \
"${infile%.*}.opus"
@ -68,7 +74,7 @@ _opusenc() {
_mp3enc() {
flac --decode --stdout "$infile" | lame \
-q 0 \
${cbr:--V${VTAB}"${VLVL}"} \
${cbr:--V "${VLVL}"} \
--add-id3v2 \
--tt "${title#*=}" \
--ta "${artist#*=}" \
@ -78,37 +84,37 @@ _mp3enc() {
--tg "${genre#*=}" \
--tc "${comment#*=}" \
--tv "TPE2=${albumartist#*=}" \
${pic:+--ti${VTAB}"${pic}"} \
${pic:+--ti "${pic}"} \
- \
"${infile%.*}.mp3"
}
# some code reused from https://gist.github.com/berturion/5377d6653ef93049f4ff54cff2003e11#file-flac2opus-sh
process_file() {
infile="$1"
bname="${infile##*/}"
# tagnames will be used for all non-metaflac operations, as it allows for multiline comment shit
tagnames="$(metaflac --export-tags-to=- "$infile" | grep -F '=' | cut -d '=' -f 1)"
tagnames="$(metaflac --export-tags-to=- "$infile" | grep -E '^[A-Z]+=' | cut -d '=' -f 1 | sort | uniq)"
# check if flac has a picture, if not, set RMPIC
metaflac --export-picture-to=- "$infile" 1>/dev/null 2>&1 || RMPIC=1
# first detect any potential ID3 tags
head_bytes="$(dd if="$infile" of=/dev/stdout bs=1 count=3 2>/dev/null)"
head_bytes="$(dd if="$infile" bs=1 count=3 2>/dev/null)"
if [ "$head_bytes" = "ID3" ]; then
warn "invalid id3 tags found in $infile, you should get that fixed!"
# in a previous version this would remove invalid id3 tags automatically
# I have since realized that this is outside the scope of this program
# I have since realized that this is outside the scope of this script
# 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
fi
# now test if keepkeys or whatever was used, set exported tags
if [ "$KEEPKEYS" ]; then
# now test if keepkeys or removekeys was used, set exported tags
if [ -n "$KEEPKEYS" ]; then
tagnames="$(printf '%s' "$tagnames" | grep -i -E "^$KEEPKEYS")"
fi
if [ "$REMOVEKEYS" ]; then
if [ -n "$REMOVEKEYS" ]; then
[ "$REMOVEKEYS" = "ALL" ] && REMOVEKEYS=''
tagnames="$(printf '%s' "$tagnames" | grep -v -i -E "^$REMOVEKEYS")"
fi
@ -120,22 +126,28 @@ process_file() {
fi
# time to encode
IFS="$VTAB"
if [ "$TYPE" = "opus" ]; then
VLVL=
opustags=""
# build opus comment chain, can't "import" tags like with flac
[ "$tagnames" ] && while read -r name; do
tmptag="$(metaflac --show-tag="$name" "$infile")"
[ "$tmptag" ] && opustags="${opustags:+${opustags}${VTAB}}--comment${VTAB}${tmptag}"
set --
[ -n "$tagnames" ] && while read -r name; do
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
done <<-EOF
$tagnames
EOF
if [ "$VERBOSE" ]; then
_opusenc || convwarn
if [ -n "$VERBOSE" ]; then
_opusenc "$@" || convwarn
else
_opusenc 1>/dev/null 2>&1 || convwarn
_opusenc "$@" 1>/dev/null 2>&1 || convwarn
fi
# remove encoding metadata
[ -n "$RMENCTAG" ] && opustags -d ENCODER -d ENCODER_OPTIONS -i "${infile%.*}.opus"
else
album="$(metaflac --show-tag=album "$infile")"
artist="$(metaflac --show-tag=artist "$infile")"
@ -146,66 +158,74 @@ process_file() {
tracknumber="$(metaflac --show-tag=tracknumber "$infile")"
comment="$(metaflac --show-tag=comment "$infile")"
[ "$VLVL" ] || cbr="-b${VTAB}$BITRATE${VTAB}--cbr"
if [ "$VERBOSE" ]; then
[ -z "$VLVL" ] && cbr="-b $BITRATE --cbr"
if [ -n "$VERBOSE" ]; then
_mp3enc || convwarn
else
_mp3enc 1>/dev/null 2>&1 || convwarn
fi
fi
[ "${pic:-}" ] && rm -f "$pic"
[ -z "${err:-}" ] && echo "$infile successfully converted to $TYPE with bitrate/quality ${VLVL:-${BITRATE}k}"
[ -z "$DELETE" ] || rm -f "$infile"
[ -n "${pic:-}" ] && rm -f "$pic"
[ -n "$DELETE" ] && rm -f "$infile"
[ -n "${err:-}" ] || errecho "$infile ${GREEN}successfully converted to ${RESET}$TYPE${GREEN} with bitrate/quality ${RESET}${VLVL:+V}${VLVL:-${BITRATE}k}"
}
usage() {
errecho \
"usage: $BN [-huvVip3] [-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
IF ENCODING TO MP3, -k AND -r WILL NOT WORK. the only metadata that will be kept is the following:
TITLE, ARTIST, ALBUM, ALBUMARTIST, DATE, GENRE, TRACKNUMBER, COMMENT, and the cover picture
(blame id3. just use opus)
cat >&2 <<-EOF
flacconv $VERSION
usage: $BN [-huvVipe3] [-b BITRATE] [-l LEVEL] [-k KEYS] [-r KEYS] [-j THREADS] [--] [DIRECTORY...]
-h show script help
-u check for updates
-v verbose output (messy with multithreading)
-V very verbose output (VERY messy, use only for debugging and with like, -j 1)
-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
OVERRIDES -b
-k <KEYS> keep specified flac metadata KEYS in output file
keys can be checked with metaflac --export-tags-to=- FILE
option argument is a PIPE-separated list of keys to keep, case-insensitive
(i.e. -k 'artist|title|albumartist|album|date')
if both -k and -r are not present, all keys are kept.
-r <KEYS> remove specified flac metadata KEYS in output file
option argument is of the same format as -k
if set to 'ALL', all keys are removed
-p remove embedded picture in output files
-j <THREADS> use the specified amount of threads for parallel processing
if omitted, CPU core count will be used"
$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
${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}
(blame id3. just use opus)
-h show script help
-u check for updates
-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}
-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
${YELLOW}OVERRIDES -b${RESET}
-k <KEYS> keep specified flac metadata KEYS in output file
keys can be checked with metaflac --export-tags-to=- FILE
option argument is a ${YELLOW}PIPE-separated${RESET} list of keys to keep, case-insensitive
(i.e. -k "artist|title|albumartist|album|date")
${YELLOW}if both -k and -r are not present, all keys are kept.${RESET}
-r <KEYS> remove specified flac metadata KEYS in output file
option argument is of the same format as -k
${YELLOW}if set to "ALL" (with capitalization), all keys are removed${RESET}
-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
}
VERBOSE=
REALLYVERBOSE=
DELETE=
TYPE=opus
BITRATE=128
VLVL=
RMPIC=
RMENCTAG=
KEEPKEYS=
REMOVEKEYS=
IGNOREWARNINGS=
THREADS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || getconf NPROCESSORS_ONLN)" # linux & freebsd compat I think
TYPE=opus
BITRATE=128
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
[ $? -ne 0 ] && warn 'unable to find number of cores! defaulting to 1 thread unless otherwise specified...' && THREADS=1
_tempdir # set temp dir
while getopts :huvVid3pb:l:k:r:j: OPT; do
while getopts :huvVid3peb:l:k:r:j: OPT; do
case "$OPT" in
h) usage && exit 0 ;;
u) checkupdate ;;
@ -215,6 +235,7 @@ while getopts :huvVid3pb:l:k:r:j: OPT; do
d) DELETE=1 ;;
3) TYPE=mp3 ;;
p) RMPIC=1 ;;
e) RMENCTAG=1 ;;
b) BITRATE="$OPTARG" ;;
l) VLVL="$OPTARG" ;;
k) KEEPKEYS="$OPTARG" ;;
@ -225,19 +246,19 @@ while getopts :huvVid3pb:l:k:r:j: OPT; do
done
shift "$((OPTIND - 1))"
[ "$REALLYVERBOSE" ] && set -x
[ -n "$REALLYVERBOSE" ] && set -x
command -v metaflac 1>/dev/null 2>&1 || fail 'flac tools are not installed! (metaflac)'
[ $TYPE = "mp3" ] && { command -v lame 1>/dev/null 2>&1 || fail 'lame is not installed!' ; }
[ $TYPE = "opus" ] && { command -v opusenc 1>/dev/null 2>&1 || fail 'opus-tools is not installed! (opusenc)' ; }
{ [ "$KEEPKEYS" ] && [ "$REMOVEKEYS" ]; } && fail 'don'\''t use -k and -r in the same call!'
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)'
fi
[ -n "$RMENCTAG" ] && { command -v opustags 1>/dev/null 2>&1 || fail 'opustags is not installed! this is required for -e (opustags)' ; }
# this script assumes you aren't using newlines in path names
# I do not want to change it to account for this
# you should never put newlines in paths
# it is a very bad idea for many programs
FLACFILES="$(find "$@" -type f -name "*.flac")"
[ "$FLACFILES" ] || fail 'no flac files found!'
FLACFILES="$(find "$@" -type f -name "*.[fF][lL][aA][cC]")"
[ -n "$FLACFILES" ] || fail 'no flac files found!'
# make a fifo/fd for parallel stuff
mk_parallel_fd() {
@ -263,6 +284,7 @@ run_in_parallel() {
} &
}
errecho "${YELLOW}using ${RESET}$THREADS${YELLOW} threads"
mk_parallel_fd
while read -r file; do
run_in_parallel process_file "$file"