Compare commits
10 Commits
1dd8b0bbf2
...
5138132c46
Author | SHA1 | Date |
---|---|---|
yosh | 5138132c46 | |
yosh | eccc30ea85 | |
yosh | eba9ccc504 | |
yosh | 18ceb3c756 | |
yosh | 22ac378902 | |
yosh | c936bcb704 | |
yosh | 6bb63d9d74 | |
yosh | 257dc133eb | |
yosh | aa0eb35dad | |
yosh | 96e9e0f9dd |
24
README.md
24
README.md
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue