mirror of
https://github.com/levogevo/add-synced-lyrics.git
synced 2026-07-21 21:45:21 +00:00
Compare commits
2
Commits
bddc943de5
...
1ea04833aa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ea04833aa | ||
|
|
eeba67f6f6 |
+2
-1
@@ -1 +1,2 @@
|
|||||||
test-dir*
|
test-dir*
|
||||||
|
*.json
|
||||||
+73
-327
@@ -1,118 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# many functions are invoked via variables
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
have_cmd() {
|
|
||||||
for cmd in "$@"; do
|
|
||||||
command -v "${cmd}" &>/dev/null || return 1
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# shellcheck disable=SC2329
|
|
||||||
void() {
|
|
||||||
echo "$@" &>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
# shellcheck disable=SC2329
|
|
||||||
dry() {
|
|
||||||
local args=("$@")
|
|
||||||
local numArgs="${#args[@]}"
|
|
||||||
test "${numArgs}" -eq 0 && return 0
|
|
||||||
echo "'${args[0]}'"
|
|
||||||
local earlyStop=$((numArgs - 1))
|
|
||||||
for ((i = 1; i < earlyStop; i++)); do
|
|
||||||
echo " '${args[${i}]}' \\"
|
|
||||||
done
|
|
||||||
echo " '${args[${i}]}'"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_required_utils() {
|
|
||||||
local utils=(
|
|
||||||
ffmpeg
|
|
||||||
jq
|
|
||||||
syncedlyrics
|
|
||||||
librelyrics
|
|
||||||
spotify # uv tool install --python 3.8 spotify-cli
|
|
||||||
)
|
|
||||||
local missing=false
|
|
||||||
for util in "${utils[@]}"; do
|
|
||||||
if ! have_cmd "${util}"; then
|
|
||||||
echo "missing ${util}!"
|
|
||||||
missing=true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ${missing} == true ]]; then
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# shellcheck disable=SC2329
|
|
||||||
get_spotify_url() {
|
|
||||||
local search="$1"
|
|
||||||
local response artistAlbumTitle url
|
|
||||||
response="$(
|
|
||||||
spotify \
|
|
||||||
search \
|
|
||||||
--track "${search}" \
|
|
||||||
--limit 1 \
|
|
||||||
--raw
|
|
||||||
)"
|
|
||||||
|
|
||||||
artistAlbumTitle="$(
|
|
||||||
jq -r '.items[0] | "\(.artists[0].name) - \(.album.name) - \(.name)"' \
|
|
||||||
<<<"${response}"
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [[ "${artistAlbumTitle}" != "${search}" ]]; then
|
|
||||||
echo "${search} could not be found"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
url="$(
|
|
||||||
jq -r '.items[0] | "\(.external_urls.spotify)"' <<<"${response}"
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [[ -z "${url}" ]]; then
|
|
||||||
echo "could not determine url for ${search}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "${url}"
|
|
||||||
}
|
|
||||||
|
|
||||||
output_eval_functions() {
|
|
||||||
while read -r line; do
|
|
||||||
IFS=' ' read -r _ _ function <<<"${line}"
|
|
||||||
[[ "${function}" == '_determine_inputs_'* ||
|
|
||||||
"${function}" == set_default_options ]] || continue
|
|
||||||
${line}
|
|
||||||
done <<<"$(declare -F)"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_artist_album_title() {
|
|
||||||
local file="$1"
|
|
||||||
local jqFilter='.streams[] | select(.codec_type == "audio") | .tags | "\(.ARTIST) - \(.ALBUM) - \(.TITLE)"'
|
|
||||||
ffprobe \
|
|
||||||
-v error \
|
|
||||||
-show_entries stream \
|
|
||||||
-of json \
|
|
||||||
"${file}" | jq \
|
|
||||||
-r "${jqFilter}"
|
|
||||||
}
|
|
||||||
|
|
||||||
set_lyric_file_name() {
|
|
||||||
local file="$1"
|
|
||||||
if [[ ! "${file}" =~ ${MUSIC_EXTENSION_REGEX} ]]; then
|
|
||||||
echo "${file} is not a music file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "${file%.opus}.lrc"
|
|
||||||
}
|
|
||||||
|
|
||||||
bash_basename() {
|
bash_basename() {
|
||||||
local tmp
|
local tmp
|
||||||
path="$1"
|
path="$1"
|
||||||
@@ -125,245 +17,99 @@ bash_basename() {
|
|||||||
printf '%s\n' "${tmp:-/}"
|
printf '%s\n' "${tmp:-/}"
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly PROGNAME="$(bash_basename "$0")"
|
bash_dirname() {
|
||||||
|
# Usage: dirname "path"
|
||||||
|
local tmp=${1:-.}
|
||||||
|
|
||||||
usage() {
|
[[ $tmp != *[!/]* ]] && {
|
||||||
echo "${PROGNAME} [options]
|
printf '/\n'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
OPTIONS:
|
tmp=${tmp%%"${tmp##*[!/]}"}
|
||||||
-d, --dir process files in directory
|
|
||||||
-r, --recurse recursively process directory
|
[[ $tmp != */* ]] && {
|
||||||
-f, --file process one file
|
printf '.\n'
|
||||||
-n, --dry-run do not actually add synced lyrics,
|
return
|
||||||
just show the files that would be processed
|
}
|
||||||
-e, --eval output bash functions that can be directly processed
|
|
||||||
by eval for use with testing this script.
|
tmp=${tmp%/*}
|
||||||
-i, --ignore ignore errors, and continue attempting to find lyrics
|
tmp=${tmp%%"${tmp##*[!/]}"}
|
||||||
even in the case of errors
|
|
||||||
-h, --help show this output"
|
printf '%s\n' "${tmp:-/}"
|
||||||
exit ${EXIT_CODE:-1}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set_default_options() {
|
PROGPATH="$(readlink -f "$0")"
|
||||||
DIR=${DIR:-}
|
PROGDIR="$(bash_dirname "${PROGPATH}")"
|
||||||
FILE=${FILE:-}
|
PROGNAME="$(bash_basename "${PROGPATH}")"
|
||||||
RECURSE=${RECURSE:-false}
|
# shellcheck disable=SC2034
|
||||||
DRY_RUN=${DRY_RUN:-false}
|
readonly PROGPATH PROGDIR PROGNAME || exit 1
|
||||||
IGNORE=${IGNORE:-false}
|
|
||||||
readonly MUSIC_EXTENSION_REGEX='.*\.(opus)$'
|
|
||||||
}
|
|
||||||
|
|
||||||
process_options() {
|
for lib in "${PROGDIR}/lib/"*; do
|
||||||
while [[ $# -gt 0 ]]; do
|
# shellcheck disable=SC1090
|
||||||
option="$1"
|
source "${lib}" || exit 1
|
||||||
value="${2:-}"
|
done
|
||||||
case "${option}" in
|
|
||||||
-d | --dir)
|
|
||||||
if [[ ! -d "${value}" ]]; then
|
|
||||||
echo "directory ${value} does not exist"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
readonly DIR="${value}"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-r | --recurse)
|
|
||||||
readonly RECURSE=true
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
-n | --dry-run)
|
|
||||||
readonly DRY_RUN=true
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
-i | --ignore)
|
|
||||||
readonly IGNORE=true
|
|
||||||
shift 1
|
|
||||||
;;
|
|
||||||
-e | --eval)
|
|
||||||
output_eval_functions
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
-f | --file)
|
|
||||||
if [[ ! -f "${value}" ]]; then
|
|
||||||
echo "file ${value} does not exist"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
readonly FILE="${value}"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-h | --help)
|
|
||||||
EXIT_CODE=0 usage
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "bad option"
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "${DIR:-}${FILE:-}" == '' ]]; then
|
|
||||||
echo "either --dir or --file must be used"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${DIR}" && -n "${FILE}" ]]; then
|
|
||||||
echo "cannot process both directory and file"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${RECURSE}" == true && -n "${FILE}" ]]; then
|
|
||||||
echo "--recurse can only be used with --dir"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
_determine_inputs_fd() {
|
|
||||||
local fdCmd=(
|
|
||||||
fd
|
|
||||||
--type f
|
|
||||||
"${MUSIC_EXTENSION_REGEX}"
|
|
||||||
"${DIR}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ "${RECURSE}" == false ]]; then
|
|
||||||
fdCmd+=(--max-depth 1)
|
|
||||||
fi
|
|
||||||
|
|
||||||
mapfile -t INPUTS < <("${fdCmd[@]}" | sort)
|
|
||||||
}
|
|
||||||
|
|
||||||
_determine_inputs_find() {
|
|
||||||
local findCmd=(
|
|
||||||
find
|
|
||||||
"${DIR}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ "${RECURSE}" == false ]]; then
|
|
||||||
findCmd+=(-maxdepth 1)
|
|
||||||
fi
|
|
||||||
|
|
||||||
findCmd+=(
|
|
||||||
-regextype posix-extended
|
|
||||||
-regex "${MUSIC_EXTENSION_REGEX}"
|
|
||||||
-type f
|
|
||||||
)
|
|
||||||
|
|
||||||
mapfile -t INPUTS < <("${findCmd[@]}" | sort)
|
|
||||||
}
|
|
||||||
|
|
||||||
_determine_inputs_bash() {
|
|
||||||
local unsortedInputs=()
|
|
||||||
if [[ "${RECURSE}" == true ]]; then
|
|
||||||
local preGlobstar="$(shopt -p globstar)"
|
|
||||||
shopt -s globstar
|
|
||||||
for f in "${DIR}"/**/*; do
|
|
||||||
unsortedInputs+=("${f}")
|
|
||||||
done
|
|
||||||
${preGlobstar}
|
|
||||||
else
|
|
||||||
for f in "${DIR}"/**; do
|
|
||||||
unsortedInputs+=("${f}")
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
local filteredInputs=()
|
|
||||||
for input in "${unsortedInputs[@]}"; do
|
|
||||||
[[ "${input}" =~ ${MUSIC_EXTENSION_REGEX} ]] || continue
|
|
||||||
filteredInputs+=("${input}")
|
|
||||||
done
|
|
||||||
|
|
||||||
mapfile -t INPUTS < <(printf '%s\n' "${filteredInputs[@]}" | sort)
|
|
||||||
}
|
|
||||||
|
|
||||||
determine_inputs() {
|
|
||||||
INPUTS=()
|
|
||||||
if [[ -n "${FILE}" ]]; then
|
|
||||||
INPUTS=("${FILE}")
|
|
||||||
elif have_cmd fd; then
|
|
||||||
_determine_inputs_fd || return 1
|
|
||||||
elif have_cmd find; then
|
|
||||||
_determine_inputs_find || return 1
|
|
||||||
else
|
|
||||||
_determine_inputs_bash || return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${#INPUTS[@]}" -eq 0 ]]; then
|
|
||||||
echo "no inputs found, something is wrong"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
readonly INPUTS
|
|
||||||
}
|
|
||||||
|
|
||||||
create_tmp_dir() {
|
|
||||||
TMPDIR="$(mktemp -d)" || return 1
|
|
||||||
readonly TMPDIR
|
|
||||||
trap 'rm -rf ${TMPDIR}' EXIT
|
|
||||||
}
|
|
||||||
|
|
||||||
process_inputs() {
|
process_inputs() {
|
||||||
local dry='' void=''
|
local isrc output ret=0
|
||||||
if [[ "${DRY_RUN}" == true ]]; then
|
local downloadFuncs=(
|
||||||
dry=dry
|
download_with_lrclib
|
||||||
void=void
|
download_with_spotify
|
||||||
fi
|
download_with_deezer
|
||||||
|
)
|
||||||
|
|
||||||
local search='' output='' spotifyUrl='' ret=''
|
|
||||||
for input in "${INPUTS[@]}"; do
|
for input in "${INPUTS[@]}"; do
|
||||||
if [[ ${IGNORE} == false && ${ret} -ne 0 ]]; then
|
if [[ ${IGNORE} == false && ${ret} -ne 0 ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${input,,}" == *'instrumental'* ]]; then
|
if [[ "${input,,}" == *'instrumental'* ||
|
||||||
|
"${input,,}" == *'acoustic'* ]]; then
|
||||||
echo "${input} is instrumental, skipping"
|
echo "${input} is instrumental, skipping"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
output=''
|
||||||
output="$(set_lyric_file_name "${input}")"
|
output="$(set_lyric_file_name "${input}")"
|
||||||
if [[ ! -f "${output}" ]]; then
|
|
||||||
echo "processing ${input}"
|
|
||||||
search="$(get_artist_album_title "${input}")"
|
|
||||||
|
|
||||||
# try with syncedlyrics first
|
if [[ -f "${output}" ]]; then
|
||||||
${dry} syncedlyrics \
|
if is_valid_lrc "${output}"; then
|
||||||
--synced-only \
|
|
||||||
--output "${output}" \
|
|
||||||
"${search}"
|
|
||||||
|
|
||||||
if ! ${void} test -f "${output}"; then
|
|
||||||
for f in "${TMPDIR}"/*; do
|
|
||||||
if [[ -f "${f}" ]]; then
|
|
||||||
echo "tempdir ${TMPDIR} is not empty!"
|
|
||||||
echo "aborting operation"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# try with librelyrics-spotify next
|
|
||||||
if ! spotifyUrl="$(${dry} get_spotify_url "${search}")"; then
|
|
||||||
ret=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ${dry} librelyrics \
|
|
||||||
--directory "${TMPDIR}" \
|
|
||||||
"${spotifyUrl}"; then
|
|
||||||
ret=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ${dry} mv "${TMPDIR}"/*.lrc "${output}"; then
|
|
||||||
ret=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ${void} test -f "${output}"; then
|
|
||||||
echo "failed to get lyrics for ${input}"
|
|
||||||
ret=1
|
|
||||||
continue
|
continue
|
||||||
|
else
|
||||||
|
rm "${output}" || return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "processing ${input}"
|
||||||
|
isrc=''
|
||||||
|
if ! isrc="$(get_file_isrc "${input}")"; then
|
||||||
|
ret=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmpOutput="${TMPDIR}/tmp.lrc"
|
||||||
|
for func in "${downloadFuncs[@]}"; do
|
||||||
|
"${func}" "${isrc}" "${tmpOutput}" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -f "${tmpOutput}" ]]; then
|
||||||
|
if is_valid_lrc "${tmpOutput}"; then
|
||||||
|
mv "${tmpOutput}" "${output}" &>/dev/null || return 1
|
||||||
|
else
|
||||||
|
rm "${tmpOutput}" || return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! void test -f "${output}"; then
|
||||||
|
echo "failed to get lyrics for ${input}"
|
||||||
|
ret=1
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
void echo "added lyrics for ${input}"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
return ${ret}
|
||||||
}
|
}
|
||||||
|
|
||||||
check_required_utils || exit 1
|
check_required_utils || exit 1
|
||||||
|
|||||||
+261
@@ -0,0 +1,261 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# many functions are invoked via variables
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
|
# check commands exist
|
||||||
|
# "$@" commands to check
|
||||||
|
have_cmd() {
|
||||||
|
for cmd in "$@"; do
|
||||||
|
command -v "${cmd}" &>/dev/null || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# validate results from `jq -r`
|
||||||
|
jq_result_valid() {
|
||||||
|
local result="$1"
|
||||||
|
if [[ -z "${result}" || "${result}" == 'null' ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr() { echo "$*" 1>&2; }
|
||||||
|
info() { stderr "INFO [${FUNCNAME[1]}]: $*"; }
|
||||||
|
error() { stderr "ERROR [${FUNCNAME[1]}]: $*"; }
|
||||||
|
|
||||||
|
# only echo "$@" if ${DEBUG} is true
|
||||||
|
debug() {
|
||||||
|
if [[ ${DEBUG} == false ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
stderr "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# do not do anything
|
||||||
|
# if ${DRY_RUN} is enabled
|
||||||
|
void() {
|
||||||
|
local cmd=("$@")
|
||||||
|
if [[ ${DRY_RUN} == true ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
"${cmd[@]}"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# print out "$@" formatted for bash
|
||||||
|
# if ${DRY_RUN} is enabled
|
||||||
|
# otherwise, execute the actual command
|
||||||
|
dry() {
|
||||||
|
local cmd=("$@")
|
||||||
|
if [[ ${DRY_RUN} == true ]]; then
|
||||||
|
local numCmd="${#cmd[@]}"
|
||||||
|
test "${numCmd}" -eq 0 && return 0
|
||||||
|
local firstLine="'${cmd[0]}'"
|
||||||
|
test "${numCmd}" -gt 1 && firstLine+=' \'
|
||||||
|
stderr "${firstLine}"
|
||||||
|
local earlyStop=$((numCmd - 1))
|
||||||
|
for ((i = 1; i < earlyStop; i++)); do
|
||||||
|
stderr " '${cmd[${i}]}' \\"
|
||||||
|
done
|
||||||
|
stderr " '${cmd[${i}]}'"
|
||||||
|
else
|
||||||
|
"${cmd[@]}"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# check required utilities for this project
|
||||||
|
check_required_utils() {
|
||||||
|
local utils=(
|
||||||
|
base64
|
||||||
|
cat
|
||||||
|
ffprobe
|
||||||
|
jq
|
||||||
|
librelyrics
|
||||||
|
spotify # uv tool install --python 3.8 spotify-cli
|
||||||
|
)
|
||||||
|
local missing=false
|
||||||
|
for util in "${utils[@]}"; do
|
||||||
|
if ! have_cmd "${util}"; then
|
||||||
|
error "missing ${util}!"
|
||||||
|
missing=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${missing} == true ]]; then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_command() {
|
||||||
|
local cmd=("$@")
|
||||||
|
local hash cacheFile cmdOut contents ret
|
||||||
|
hash="$(sha256sum <<<"${cmd[*]}")" || return 1
|
||||||
|
read -r cacheFile _ <<<"${hash}"
|
||||||
|
cacheFilePath="${CACHE_DIR}/${cacheFile}"
|
||||||
|
if [[ -f "${cacheFilePath}" && ${DRY_RUN} == false ]]; then
|
||||||
|
mapfile -t contents <"${cacheFilePath}"
|
||||||
|
ret=$?
|
||||||
|
# skip first line since it is the command
|
||||||
|
printf '%s\n' "${contents[@]:1}"
|
||||||
|
else
|
||||||
|
cmdOut="$(dry "${cmd[@]}")"
|
||||||
|
ret=$?
|
||||||
|
if [[ ${ret} -eq 0 && ${DRY_RUN} == false ]]; then
|
||||||
|
{
|
||||||
|
echo "${cmd[*]}"
|
||||||
|
echo "${cmdOut}"
|
||||||
|
} >"${cacheFilePath}" || return 1
|
||||||
|
echo "${cmdOut}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "${ret}"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_tmp_dir() {
|
||||||
|
TMPDIR="$(mktemp -d)" || return 1
|
||||||
|
readonly TMPDIR
|
||||||
|
trap 'rm -rf ${TMPDIR}' EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
check_tmp_dir_empty() {
|
||||||
|
for f in "${TMPDIR}"/*; do
|
||||||
|
if [[ -f "${f}" ]]; then
|
||||||
|
error "tempdir ${TMPDIR} is not empty!"
|
||||||
|
error "aborting operation"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# librelyrics only exposes a directory to download to
|
||||||
|
# so this wrapper function downloads to a directory
|
||||||
|
# and then outputs the contents of the downloaded file
|
||||||
|
# to stdout so it can effectively be cached
|
||||||
|
librelyrics_dl() {
|
||||||
|
local url="$1"
|
||||||
|
check_tmp_dir_empty || return 1
|
||||||
|
librelyrics \
|
||||||
|
--directory "${TMPDIR}" \
|
||||||
|
"${url}" 1>&2
|
||||||
|
local librelyricsRet=$?
|
||||||
|
|
||||||
|
local downloadedFile=''
|
||||||
|
for f in "${TMPDIR}/"*; do
|
||||||
|
if [[ -f "${f}" ]]; then
|
||||||
|
downloadedFile="${f}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${librelyricsRet} -eq 0 && -z ${downloadedFile} ]]; then
|
||||||
|
error "failed to download file for ${url} despite no librelyrics error"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local fileContents=''
|
||||||
|
if [[ -n "${downloadedFile}" ]]; then
|
||||||
|
fileContents="$(<"${downloadedFile}")"
|
||||||
|
rm "${downloadedFile}" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${librelyricsRet} -eq 0 && -z ${fileContents} ]]; then
|
||||||
|
error "failed to read file for ${url} despite no librelyrics error"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${fileContents}"
|
||||||
|
}
|
||||||
|
|
||||||
|
download_with_librelyrics() {
|
||||||
|
local isrc="$1"
|
||||||
|
local output="$2"
|
||||||
|
local urlType="$3"
|
||||||
|
|
||||||
|
if [[ ! "${urlType}" =~ spotify|deezer ]]; then
|
||||||
|
error "only spotify|deezer are supported"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local url=''
|
||||||
|
url="$(get_"${urlType}"_url "${isrc}")" || return 1
|
||||||
|
|
||||||
|
local lyrics=''
|
||||||
|
lyrics="$(cache_command \
|
||||||
|
librelyrics_dl \
|
||||||
|
"${url}")"
|
||||||
|
|
||||||
|
if [[ -z "${lyrics}" ]]; then
|
||||||
|
error "could not get lyrics for ${isrc} using ${urlType}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "found lyrics for ${isrc} with ${urlType}"
|
||||||
|
echo "${lyrics}" >"${output}"
|
||||||
|
}
|
||||||
|
|
||||||
|
output_eval_functions() {
|
||||||
|
while read -r line; do
|
||||||
|
IFS=' ' read -r _ _ function <<<"${line}"
|
||||||
|
[[ "${function}" == '_determine_inputs_'* ||
|
||||||
|
"${function}" == set_default_options ]] || continue
|
||||||
|
${line}
|
||||||
|
done <<<"$(declare -F)"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_file_isrc() {
|
||||||
|
local file="$1"
|
||||||
|
local probe='' jqResult=''
|
||||||
|
|
||||||
|
local probe=''
|
||||||
|
probe="$(
|
||||||
|
ffprobe \
|
||||||
|
-v error \
|
||||||
|
-show_entries stream \
|
||||||
|
-of json \
|
||||||
|
"${file}"
|
||||||
|
)" || return 1
|
||||||
|
|
||||||
|
local jqFilter='.streams[] | select(.codec_type == "audio") | .tags | with_entries(.key |= ascii_downcase) | .isrc'
|
||||||
|
jqResult="$(jq -r "${jqFilter}" <<<"${probe}")"
|
||||||
|
|
||||||
|
if [[ "${jqResult}" == 'null' ]]; then
|
||||||
|
error "failed to find ISRC for ${file}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "${jqResult}"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_lyric_file_name() {
|
||||||
|
local file="$1"
|
||||||
|
if [[ ! "${file}" =~ ${MUSIC_EXTENSION_REGEX} ]]; then
|
||||||
|
echo "${file} is not a music file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${file%.opus}.lrc"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_valid_lrc() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
local valid=1
|
||||||
|
while read -r line; do
|
||||||
|
[[ "${#line}" -eq 0 ]] && continue
|
||||||
|
if [[ "${line}" != '['* ]]; then
|
||||||
|
valid=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
valid=0
|
||||||
|
done <"${file}"
|
||||||
|
|
||||||
|
return ${valid}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# many functions are invoked via variables
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
|
get_deezer_isrc_response() {
|
||||||
|
local isrc="$1"
|
||||||
|
if [[ -z "${isrc}" ]]; then
|
||||||
|
error "missing isrc"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cache_command \
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
--retry 3 \
|
||||||
|
"https://api.deezer.com/track/isrc:${isrc}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_deezer_url() {
|
||||||
|
local isrc="$1"
|
||||||
|
if [[ -z "${isrc}" ]]; then
|
||||||
|
error "missing isrc"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local deezerResponse=''
|
||||||
|
deezerResponse="$(get_deezer_isrc_response "${isrc}")" || return 1
|
||||||
|
|
||||||
|
local jqResult=''
|
||||||
|
jqResult="$(jq -r .link <<<"${deezerResponse}")"
|
||||||
|
|
||||||
|
if ! jq_result_valid "${jqResult}"; then
|
||||||
|
error "could not get link from deezer for ${isrc}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${jqResult}"
|
||||||
|
}
|
||||||
|
|
||||||
|
download_with_deezer() {
|
||||||
|
local isrc="$1"
|
||||||
|
local output="$2"
|
||||||
|
download_with_librelyrics \
|
||||||
|
"${isrc}" \
|
||||||
|
"${output}" \
|
||||||
|
deezer
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# many functions are invoked via variables
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
|
get_lrclib_api_response() {
|
||||||
|
local isrc="$1"
|
||||||
|
local deezerResponse=''
|
||||||
|
deezerResponse="$(get_deezer_isrc_response "${isrc}")" || return 1
|
||||||
|
|
||||||
|
# get metadata from deezer
|
||||||
|
local deezerMetadata=(
|
||||||
|
"track .title"
|
||||||
|
"artist .artist.name"
|
||||||
|
"album .album.title"
|
||||||
|
"duration .duration"
|
||||||
|
)
|
||||||
|
for data in "${deezerMetadata[@]}"; do
|
||||||
|
IFS=' ' read -r env jqFilter <<<"${data}"
|
||||||
|
jqResult="$(jq -r "${jqFilter}" <<<"${deezerResponse}")"
|
||||||
|
if ! jq_result_valid "${jqResult}"; then
|
||||||
|
error "could not determine lrclib ${env} for ${isrc}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
declare "${env}=${jqResult}"
|
||||||
|
debug "${env}=${jqResult}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# variables assigned above
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
cache_command \
|
||||||
|
curl \
|
||||||
|
"https://lrclib.net/api/get" \
|
||||||
|
--silent \
|
||||||
|
--get \
|
||||||
|
--data-urlencode "track_name=${track}" \
|
||||||
|
--data-urlencode "artist_name=${artist}" \
|
||||||
|
--data-urlencode "album_name=${album}" \
|
||||||
|
--data-urlencode "duration=${duration}"
|
||||||
|
}
|
||||||
|
|
||||||
|
download_with_lrclib() {
|
||||||
|
local isrc="$1"
|
||||||
|
local output="$2"
|
||||||
|
|
||||||
|
# get lrclib ID
|
||||||
|
local lrclibResponse syncedLyrics
|
||||||
|
lrclibResponse="$(get_lrclib_api_response "${isrc}")"
|
||||||
|
syncedLyrics="$(jq -r .syncedLyrics <<<"${lrclibResponse}")"
|
||||||
|
if ! jq_result_valid "${syncedLyrics}"; then
|
||||||
|
error "could not get lyrics for ${isrc}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${syncedLyrics}" >"${output}"
|
||||||
|
}
|
||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# many functions are invoked via variables
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "${PROGNAME} [options]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-d, --dir process files in directory
|
||||||
|
-r, --recurse recursively process directory
|
||||||
|
-f, --file process one file
|
||||||
|
-n, --dry-run do not actually add synced lyrics,
|
||||||
|
just show the files that would be processed
|
||||||
|
-e, --eval output bash functions that can be directly processed
|
||||||
|
by eval for use with testing this script.
|
||||||
|
-i, --ignore ignore errors, and continue attempting to find lyrics
|
||||||
|
even in the case of errors
|
||||||
|
-h, --help show this output
|
||||||
|
--debug extra information useful for debugging"
|
||||||
|
exit "${EXIT_CODE:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_default_options() {
|
||||||
|
DIR=${DIR:-}
|
||||||
|
FILE=${FILE:-}
|
||||||
|
RECURSE=${RECURSE:-false}
|
||||||
|
DRY_RUN=${DRY_RUN:-false}
|
||||||
|
IGNORE=${IGNORE:-false}
|
||||||
|
DEBUG=${DEBUG:-false}
|
||||||
|
|
||||||
|
# cache
|
||||||
|
CACHE_HOME="${XDG_CACHE_HOME:-"${HOME}/.cache"}"
|
||||||
|
CACHE_DIR="${CACHE_HOME}/add-synced-lyrics"
|
||||||
|
if [[ ! -d "${CACHE_DIR}" ]]; then
|
||||||
|
mkdir "${CACHE_DIR}" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
readonly MUSIC_EXTENSION_REGEX='.*\.(opus)$'
|
||||||
|
}
|
||||||
|
|
||||||
|
process_options() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
option="$1"
|
||||||
|
value="${2:-}"
|
||||||
|
case "${option}" in
|
||||||
|
-d | --dir)
|
||||||
|
if [[ ! -d "${value}" ]]; then
|
||||||
|
echo "directory ${value} does not exist"
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
readonly DIR="${value}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-r | --recurse)
|
||||||
|
readonly RECURSE=true
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-n | --dry-run)
|
||||||
|
readonly DRY_RUN=true
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-i | --ignore)
|
||||||
|
readonly IGNORE=true
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
--debug)
|
||||||
|
readonly DEBUG=true
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
-e | --eval)
|
||||||
|
output_eval_functions
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-f | --file)
|
||||||
|
if [[ ! -f "${value}" ]]; then
|
||||||
|
echo "file ${value} does not exist"
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
readonly FILE="${value}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h | --help)
|
||||||
|
EXIT_CODE=0 usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "bad option"
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${DIR:-}${FILE:-}" == '' ]]; then
|
||||||
|
echo "either --dir or --file must be used"
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${DIR}" && -n "${FILE}" ]]; then
|
||||||
|
echo "cannot process both directory and file"
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${RECURSE}" == true && -n "${FILE}" ]]; then
|
||||||
|
echo "--recurse can only be used with --dir"
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_determine_inputs_fd() {
|
||||||
|
local fdCmd=(
|
||||||
|
fd
|
||||||
|
--type f
|
||||||
|
"${MUSIC_EXTENSION_REGEX}"
|
||||||
|
"${DIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "${RECURSE}" == false ]]; then
|
||||||
|
fdCmd+=(--max-depth 1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t INPUTS < <("${fdCmd[@]}" | sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
_determine_inputs_find() {
|
||||||
|
local findCmd=(
|
||||||
|
find
|
||||||
|
"${DIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "${RECURSE}" == false ]]; then
|
||||||
|
findCmd+=(-maxdepth 1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
findCmd+=(
|
||||||
|
-regextype posix-extended
|
||||||
|
-regex "${MUSIC_EXTENSION_REGEX}"
|
||||||
|
-type f
|
||||||
|
)
|
||||||
|
|
||||||
|
mapfile -t INPUTS < <("${findCmd[@]}" | sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
_determine_inputs_bash() {
|
||||||
|
local unsortedInputs=()
|
||||||
|
local pattern="${DIR}/*"
|
||||||
|
shopt -s nullglob globstar || return 1
|
||||||
|
|
||||||
|
[[ "${RECURSE}" == true ]] && pattern="${DIR}/**/*"
|
||||||
|
mapfile -t unsortedInputs < <(printf '%s\n' ${pattern})
|
||||||
|
|
||||||
|
local filteredInputs=()
|
||||||
|
for input in "${unsortedInputs[@]}"; do
|
||||||
|
[[ "${input}" =~ ${MUSIC_EXTENSION_REGEX} ]] || continue
|
||||||
|
filteredInputs+=("${input}")
|
||||||
|
done
|
||||||
|
|
||||||
|
mapfile -t INPUTS < <(printf '%s\n' "${filteredInputs[@]}" | sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
determine_inputs() {
|
||||||
|
INPUTS=()
|
||||||
|
if [[ -n "${FILE}" ]]; then
|
||||||
|
INPUTS=("${FILE}")
|
||||||
|
elif have_cmd fd; then
|
||||||
|
_determine_inputs_fd || return 1
|
||||||
|
elif have_cmd find; then
|
||||||
|
_determine_inputs_find || return 1
|
||||||
|
else
|
||||||
|
_determine_inputs_bash || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${#INPUTS[@]}" -eq 0 ]]; then
|
||||||
|
echo "no inputs found, something is wrong"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
readonly INPUTS
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# many functions are invoked via variables
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
|
get_spotify_url() {
|
||||||
|
local isrc="$1"
|
||||||
|
if [[ -z "${isrc}" ]]; then
|
||||||
|
error "missing isrc"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local search="isrc:${isrc}"
|
||||||
|
local response
|
||||||
|
response="$(
|
||||||
|
cache_command \
|
||||||
|
spotify \
|
||||||
|
search \
|
||||||
|
--track \
|
||||||
|
"${search}" \
|
||||||
|
--limit 1 \
|
||||||
|
--raw
|
||||||
|
)" || return 1
|
||||||
|
|
||||||
|
local tags=(
|
||||||
|
spotifyISRC
|
||||||
|
spotifyURL
|
||||||
|
)
|
||||||
|
# unset tag values
|
||||||
|
for tag in "${tags[@]}"; do
|
||||||
|
declare "${tag}="
|
||||||
|
done
|
||||||
|
|
||||||
|
local jqFilter='.items[] | "\(.external_ids.isrc)|\(.external_urls.spotify)"'
|
||||||
|
local jqOutput="$(jq -r "${jqFilter}" <<<"${response}")"
|
||||||
|
|
||||||
|
# compare tags versus the reference for each line
|
||||||
|
local foundMatching=false
|
||||||
|
while read -r line; do
|
||||||
|
IFS='|' read -r "${tags[@]}" <<<"${line}"
|
||||||
|
|
||||||
|
for tag in "${tags[@]}"; do
|
||||||
|
declare -n spotifyTagValue="${tag}"
|
||||||
|
debug "${tag}=${spotifyTagValue}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
if [[ "${spotifyISRC,,}" == "${isrc,,}" ]]; then
|
||||||
|
foundMatching=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done <<<"${jqOutput}"
|
||||||
|
|
||||||
|
if [[ "${foundMatching}" == false ]]; then
|
||||||
|
error "could not find matching spotify track for ${isrc}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
echo "${spotifyURL}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
download_with_spotify() {
|
||||||
|
local isrc="$1"
|
||||||
|
local output="$2"
|
||||||
|
download_with_librelyrics \
|
||||||
|
"${isrc}" \
|
||||||
|
"${output}" \
|
||||||
|
spotify
|
||||||
|
}
|
||||||
+39
-23
@@ -7,8 +7,12 @@ set -u
|
|||||||
|
|
||||||
readonly THIS_DIR="$(dirname "$(readlink -f "$0")")"
|
readonly THIS_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
readonly TEST_DIR="${THIS_DIR}/test-dir"
|
readonly TEST_DIR="${THIS_DIR}/test-dir"
|
||||||
readonly TEST_FILE_1="${TEST_DIR}/03 - Back Burner.opus"
|
# lyrics exist for this song
|
||||||
readonly TEST_FILE_2="${TEST_DIR}/subdir/05 - Composure.opus"
|
readonly GOOD_SONG="${TEST_DIR}/back burner.opus"
|
||||||
|
readonly GOOD_LYRICS="${GOOD_SONG//.opus/.lrc}"
|
||||||
|
# lyrics do not exist for this song
|
||||||
|
readonly BAD_SONG="${TEST_DIR}/subdir/avalanche.opus"
|
||||||
|
readonly BAD_LYRICS="${BAD_SONG//.opus/.lrc}"
|
||||||
readonly PROG="${THIS_DIR}/add-synced-lyrics.sh"
|
readonly PROG="${THIS_DIR}/add-synced-lyrics.sh"
|
||||||
|
|
||||||
test_bad_input() {
|
test_bad_input() {
|
||||||
@@ -59,31 +63,42 @@ test_determine_inputs_recurse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test_file_dry() {
|
test_file_dry() {
|
||||||
"${PROG}" --file "${TEST_FILE_1}" --dry-run || return 1
|
"${PROG}" --file "${GOOD_SONG}" --dry-run
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_dry() {
|
test_directory_dry() {
|
||||||
"${PROG}" --dir "${TEST_DIR}" --dry-run || return 1
|
"${PROG}" --dir "${TEST_DIR}" --dry-run
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_recurse_dry() {
|
test_directory_recurse_dry() {
|
||||||
"${PROG}" --dir "${TEST_DIR}" --recurse --dry-run || return 1
|
"${PROG}" --dir "${TEST_DIR}" --recurse --dry-run
|
||||||
|
}
|
||||||
|
|
||||||
|
test_good_file() {
|
||||||
|
"${PROG}" --file "${GOOD_SONG}" || return 1
|
||||||
|
test -f "${GOOD_LYRICS}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_bad_file() {
|
||||||
|
"${PROG}" --file "${BAD_SONG}" && return 1
|
||||||
|
! test -f "${BAD_LYRICS}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_directory_recurse() {
|
||||||
|
setup_test_data || return 1
|
||||||
|
# this test should fail since it includes the bad file
|
||||||
|
"${PROG}" --dir "${TEST_DIR}" --recurse && return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
test_file() {
|
test_directory_recurse_ignore() {
|
||||||
"${PROG}" --file "${TEST_FILE_1}" || return 1
|
setup_test_data || return 1
|
||||||
test -f "${TEST_FILE_1//.opus/.lrc}"
|
"${PROG}" --dir "${TEST_DIR}" --recurse --ignore && return 1
|
||||||
}
|
test -f "${GOOD_LYRICS}" || return 1
|
||||||
|
test -f "${BAD_LYRICS}" && return 1
|
||||||
|
|
||||||
test_directory_recurse() {
|
return 0
|
||||||
"${PROG}" --dir "${TEST_DIR}" --recurse || return 1
|
|
||||||
test -f "${TEST_FILE_2//.opus/.lrc}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_test_data() {
|
setup_test_data() {
|
||||||
@@ -96,15 +111,13 @@ setup_test_data() {
|
|||||||
-i anullsrc=channel_layout=stereo:sample_rate=48000
|
-i anullsrc=channel_layout=stereo:sample_rate=48000
|
||||||
-c:a libopus
|
-c:a libopus
|
||||||
-t 5
|
-t 5
|
||||||
-metadata ALBUM="Messengers"
|
|
||||||
-metadata ARTIST="August Burns Red"
|
|
||||||
)
|
)
|
||||||
"${ffmpegCmd[@]}" \
|
"${ffmpegCmd[@]}" \
|
||||||
-metadata TITLE="Back Burner" \
|
-metadata ISRC=USTN10700139 \
|
||||||
"${TEST_FILE_1}" || return 1
|
"${GOOD_SONG}" || return 1
|
||||||
"${ffmpegCmd[@]}" \
|
"${ffmpegCmd[@]}" \
|
||||||
-metadata TITLE="Composure" \
|
-metadata ISRC=US5261822978 \
|
||||||
"${TEST_FILE_2}" || return 1
|
"${BAD_SONG}" || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
TESTS=(
|
TESTS=(
|
||||||
@@ -115,8 +128,10 @@ TESTS=(
|
|||||||
test_directory_dry
|
test_directory_dry
|
||||||
test_directory_recurse_dry
|
test_directory_recurse_dry
|
||||||
test_file_dry
|
test_file_dry
|
||||||
test_file
|
test_good_file
|
||||||
|
test_bad_file
|
||||||
test_directory_recurse
|
test_directory_recurse
|
||||||
|
test_directory_recurse_ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
# make sure no test is accidentally missed
|
# make sure no test is accidentally missed
|
||||||
@@ -135,6 +150,7 @@ for test in "${TESTS[@]}"; do
|
|||||||
"${test}"
|
"${test}"
|
||||||
) 2>&1
|
) 2>&1
|
||||||
)"; then
|
)"; then
|
||||||
|
echo
|
||||||
echo "!! ${test} failed !!"
|
echo "!! ${test} failed !!"
|
||||||
echo "${testOutput}"
|
echo "${testOutput}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user