mirror of
https://github.com/levogevo/add-synced-lyrics.git
synced 2026-07-21 21:45:21 +00:00
add --embed
This commit is contained in:
+12
-5
@@ -74,9 +74,13 @@ process_inputs() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
output=''
|
output=''
|
||||||
output="$(set_lyric_file_name "${input}")"
|
if ! output="$(set_lyric_file_name "${input}")"; then
|
||||||
|
echo_fail "could not determine lyric file name for ${input}"
|
||||||
|
ret=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if validate_lrc "${output}"; then
|
if validate_song_lyrics "${input}" "${output}"; then
|
||||||
echo_pass "${input} already has lyrics, skipping"
|
echo_pass "${input} already has lyrics, skipping"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -87,7 +91,7 @@ process_inputs() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local tmpOutput="${TMPDIR}/tmp.lrc"
|
local tmpOutput="${TMPDIR}/$(bash_basename "${output}")"
|
||||||
for func in "${downloadFuncs[@]}"; do
|
for func in "${downloadFuncs[@]}"; do
|
||||||
# try to download
|
# try to download
|
||||||
if ! "${func}" "${isrc}" "${tmpOutput}"; then
|
if ! "${func}" "${isrc}" "${tmpOutput}"; then
|
||||||
@@ -105,8 +109,11 @@ process_inputs() {
|
|||||||
[[ ${DRY_RUN} == true ]] && continue
|
[[ ${DRY_RUN} == true ]] && continue
|
||||||
|
|
||||||
if [[ -f "${tmpOutput}" ]]; then
|
if [[ -f "${tmpOutput}" ]]; then
|
||||||
mv "${tmpOutput}" "${output}" &>/dev/null || return 1
|
add_lyrics_to_song \
|
||||||
echo_pass "added lyrics for ${input}"
|
"${input}" \
|
||||||
|
"${tmpOutput}" \
|
||||||
|
"${output}" &&
|
||||||
|
echo_pass "added lyrics for ${input}"
|
||||||
else
|
else
|
||||||
echo_fail "could not find lyrics for ${input}"
|
echo_fail "could not find lyrics for ${input}"
|
||||||
ret=1
|
ret=1
|
||||||
|
|||||||
+122
-9
@@ -32,7 +32,7 @@ stderr() {
|
|||||||
local word="${WORD:-}"
|
local word="${WORD:-}"
|
||||||
local color="${COLOR:-}"
|
local color="${COLOR:-}"
|
||||||
local function="${FUNCNAME[2]}"
|
local function="${FUNCNAME[2]}"
|
||||||
if [[ "${function}" =~ void ]]; then
|
if [[ "${function}" =~ debug|void ]]; then
|
||||||
function="${FUNCNAME[3]}"
|
function="${FUNCNAME[3]}"
|
||||||
fi
|
fi
|
||||||
echo \
|
echo \
|
||||||
@@ -44,7 +44,7 @@ stderr() {
|
|||||||
echo_info() { WORD=INFO COLOR="${CYAN}" stderr "$@"; }
|
echo_info() { WORD=INFO COLOR="${CYAN}" stderr "$@"; }
|
||||||
echo_fail() { WORD=FAIL COLOR="${RED}" stderr "$@"; }
|
echo_fail() { WORD=FAIL COLOR="${RED}" stderr "$@"; }
|
||||||
echo_pass() { WORD=PASS COLOR="${GREEN}" stderr "$@"; }
|
echo_pass() { WORD=PASS COLOR="${GREEN}" stderr "$@"; }
|
||||||
echo_dbug() { WORD=DBUG COLOR="${PURPLE}" stderr "$@"; }
|
echo_debug() { WORD=DBUG COLOR="${PURPLE}" stderr "$@"; }
|
||||||
|
|
||||||
# only perform the command given if ${DEBUG} is enabled
|
# only perform the command given if ${DEBUG} is enabled
|
||||||
debug() {
|
debug() {
|
||||||
@@ -111,6 +111,20 @@ check_required_utils() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo_if_fail() {
|
||||||
|
local cmd=("$@")
|
||||||
|
local output
|
||||||
|
output="$("${cmd[@]}" 2>&1)"
|
||||||
|
local ret=$?
|
||||||
|
|
||||||
|
if [[ ${ret} -ne 0 ]]; then
|
||||||
|
echo_fail "${cmd[*]}"
|
||||||
|
echo_fail "${output}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return ${ret}
|
||||||
|
}
|
||||||
|
|
||||||
cache_command() {
|
cache_command() {
|
||||||
local cmd=("$@")
|
local cmd=("$@")
|
||||||
local hash cacheFile cmdOut contents ret
|
local hash cacheFile cmdOut contents ret
|
||||||
@@ -149,8 +163,10 @@ check_tmp_dir_empty() {
|
|||||||
for f in "${TMPDIR}"/*; do
|
for f in "${TMPDIR}"/*; do
|
||||||
if [[ -f "${f}" ]]; then
|
if [[ -f "${f}" ]]; then
|
||||||
echo_fail "tempdir ${TMPDIR} is not empty!"
|
echo_fail "tempdir ${TMPDIR} is not empty!"
|
||||||
|
echo_fail "contains ${f}"
|
||||||
echo_fail "aborting operation"
|
echo_fail "aborting operation"
|
||||||
return 1
|
# execution becomes undefined if TMPDIR is not empty
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -255,10 +271,43 @@ get_file_isrc() {
|
|||||||
echo "${jqResult}"
|
echo "${jqResult}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_file_lyrics() {
|
||||||
|
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) | .lyrics'
|
||||||
|
jqResult="$(jq -r "${jqFilter}" <<<"${probe}")"
|
||||||
|
|
||||||
|
if [[ "${jqResult}" == 'null' ]]; then
|
||||||
|
debug echo_fail "could not find lyrics for ${file}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "${jqResult}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_music_file() {
|
||||||
|
local file="$1"
|
||||||
|
[[ "${file}" =~ ${MUSIC_EXTENSION_REGEX} ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_lrc_file() {
|
||||||
|
local file="$1"
|
||||||
|
[[ "${file}" == *'.lrc' ]]
|
||||||
|
}
|
||||||
|
|
||||||
set_lyric_file_name() {
|
set_lyric_file_name() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
if [[ ! "${file}" =~ ${MUSIC_EXTENSION_REGEX} ]]; then
|
if ! is_music_file "${file}"; then
|
||||||
echo "${file} is not a music file"
|
echo_fail "${file} is not a music file"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -268,19 +317,27 @@ set_lyric_file_name() {
|
|||||||
validate_lrc() {
|
validate_lrc() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
|
|
||||||
# file does not exist, is invalid
|
# file does not exist, invalid
|
||||||
if [[ ! -f "${file}" ]]; then
|
if [[ ! -f "${file}" ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local valid=0
|
# file is not lyric file, invalid
|
||||||
while read -r line; do
|
if ! is_lrc_file "${file}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t fileContents <"${file}"
|
||||||
|
|
||||||
|
local valid=1
|
||||||
|
for line in "${fileContents[@]}"; do
|
||||||
[[ "${#line}" -eq 0 ]] && continue
|
[[ "${#line}" -eq 0 ]] && continue
|
||||||
if [[ "${line}" != '['* && "${line}" != '#'* ]]; then
|
if [[ "${line}" != '['* && "${line}" != '#'* ]]; then
|
||||||
valid=1
|
valid=1
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done <"${file}"
|
valid=0
|
||||||
|
done
|
||||||
|
|
||||||
if [[ ${valid} -eq 1 ]]; then
|
if [[ ${valid} -eq 1 ]]; then
|
||||||
debug echo_fail "${file} is not valid lrc file"
|
debug echo_fail "${file} is not valid lrc file"
|
||||||
@@ -289,3 +346,59 @@ validate_lrc() {
|
|||||||
|
|
||||||
return ${valid}
|
return ${valid}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_song_lyrics() {
|
||||||
|
local songFile="$1"
|
||||||
|
local lyricsDestination="$2"
|
||||||
|
|
||||||
|
# embed or not both get validated
|
||||||
|
validate_lrc "${lyricsDestination}"
|
||||||
|
local validateDestination=$?
|
||||||
|
|
||||||
|
# only continue to validate for embed
|
||||||
|
if [[ ${EMBED} == false ]]; then
|
||||||
|
return ${validateDestination}
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmpLyricsFile="${TMPDIR}/$(bash_basename "${lyricsDestination}")"
|
||||||
|
get_file_lyrics "${songFile}" >"${tmpLyricsFile}"
|
||||||
|
validate_lrc "${tmpLyricsFile}"
|
||||||
|
local validateRet=$?
|
||||||
|
[[ ${validateRet} -eq 0 ]] && rm "${tmpLyricsFile}"
|
||||||
|
|
||||||
|
return ${validateRet}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_lyrics_to_song() {
|
||||||
|
local songFile="$1"
|
||||||
|
local lyricsToAdd="$2"
|
||||||
|
local lyricsDestination="$3"
|
||||||
|
|
||||||
|
# this function removes file so be extra certain
|
||||||
|
is_music_file "${songFile}" &&
|
||||||
|
is_lrc_file "${lyricsToAdd}" &&
|
||||||
|
is_lrc_file "${lyricsDestination}" || return 1
|
||||||
|
|
||||||
|
if [[ ${EMBED} == false ]]; then
|
||||||
|
mv "${lyricsToAdd}" "${lyricsDestination}" &>/dev/null
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmpSongFile="${TMPDIR}/$(bash_basename "${songFile}")"
|
||||||
|
echo_if_fail \
|
||||||
|
ffmpeg \
|
||||||
|
-hide_banner \
|
||||||
|
-i "${songFile}" \
|
||||||
|
-metadata "LYRICS=$(<"${lyricsToAdd}")" \
|
||||||
|
-c copy \
|
||||||
|
"${tmpSongFile}"
|
||||||
|
local ffmpegRet=$?
|
||||||
|
rm "${lyricsToAdd}"
|
||||||
|
|
||||||
|
if [[ ${ffmpegRet} -eq 0 ]]; then
|
||||||
|
mv "${tmpSongFile}" "${songFile}" &>/dev/null || return 1
|
||||||
|
if [[ -f "${lyricsDestination}" ]]; then
|
||||||
|
rm "${lyricsDestination}" || return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|||||||
+15
-9
@@ -10,14 +10,15 @@ OPTIONS:
|
|||||||
-d, --dir process files in directory
|
-d, --dir process files in directory
|
||||||
-r, --recurse recursively process directory
|
-r, --recurse recursively process directory
|
||||||
-f, --file process one file
|
-f, --file process one file
|
||||||
|
-e, --embed embed lyrics directly into the song file
|
||||||
-n, --dry-run do not add synced lyrics,
|
-n, --dry-run do not add synced lyrics,
|
||||||
just show the files that would be processed
|
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
|
-i, --ignore ignore errors, and continue attempting to find lyrics
|
||||||
even in the case of errors
|
even in the case of errors
|
||||||
-h, --help show this output
|
-h, --help show this output
|
||||||
--debug extra information useful for debugging"
|
--debug extra information useful for debugging
|
||||||
|
--eval output bash functions that can be directly processed
|
||||||
|
by eval for use with testing this script."
|
||||||
exit "${EXIT_CODE:-1}"
|
exit "${EXIT_CODE:-1}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ set_default_options() {
|
|||||||
RECURSE=${RECURSE:-false}
|
RECURSE=${RECURSE:-false}
|
||||||
DRY_RUN=${DRY_RUN:-false}
|
DRY_RUN=${DRY_RUN:-false}
|
||||||
IGNORE=${IGNORE:-false}
|
IGNORE=${IGNORE:-false}
|
||||||
|
EMBED=${EMBED:-false}
|
||||||
DEBUG=${DEBUG:-false}
|
DEBUG=${DEBUG:-false}
|
||||||
|
|
||||||
# cache
|
# cache
|
||||||
@@ -64,14 +66,10 @@ process_options() {
|
|||||||
readonly IGNORE=true
|
readonly IGNORE=true
|
||||||
shift 1
|
shift 1
|
||||||
;;
|
;;
|
||||||
--debug)
|
-e | --embed)
|
||||||
readonly DEBUG=true
|
readonly EMBED=true
|
||||||
shift 1
|
shift 1
|
||||||
;;
|
;;
|
||||||
-e | --eval)
|
|
||||||
output_eval_functions
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
-f | --file)
|
-f | --file)
|
||||||
if [[ ! -f "${value}" ]]; then
|
if [[ ! -f "${value}" ]]; then
|
||||||
echo "file ${value} does not exist"
|
echo "file ${value} does not exist"
|
||||||
@@ -80,6 +78,14 @@ process_options() {
|
|||||||
readonly FILE="${value}"
|
readonly FILE="${value}"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--debug)
|
||||||
|
readonly DEBUG=true
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
--eval)
|
||||||
|
output_eval_functions
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
EXIT_CODE=0 usage
|
EXIT_CODE=0 usage
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ run_test_cmd() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local output
|
local output
|
||||||
output="$("${cmd[@]}")"
|
output="$("${cmd[@]}" 2>&1)"
|
||||||
local actualRetval=$?
|
local actualRetval=$?
|
||||||
|
|
||||||
declare -g CMD_OUTPUT="${output}"
|
declare -g CMD_OUTPUT="${output}"
|
||||||
@@ -88,6 +88,10 @@ run_test_cmd() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_help_option() {
|
||||||
|
add-synced-lyrics --help
|
||||||
|
}
|
||||||
|
|
||||||
test_bad_input() {
|
test_bad_input() {
|
||||||
# incomplete args
|
# incomplete args
|
||||||
run_test_cmd \
|
run_test_cmd \
|
||||||
@@ -114,6 +118,11 @@ test_bad_input() {
|
|||||||
1 \
|
1 \
|
||||||
add-synced-lyrics --file "${PROGPATH}" --dir "${PROGDIR}" --recurse
|
add-synced-lyrics --file "${PROGPATH}" --dir "${PROGDIR}" --recurse
|
||||||
|
|
||||||
|
# non-existent flag
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
add-synced-lyrics --this-flag-does-not-exist
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,23 +183,43 @@ test_determine_inputs_find_recurse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test_file_dry() {
|
test_file_dry() {
|
||||||
|
setup_test_data
|
||||||
add-synced-lyrics --file "${GOOD_SONG}" --dry-run
|
add-synced-lyrics --file "${GOOD_SONG}" --dry-run
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_dry() {
|
test_directory_dry() {
|
||||||
|
setup_test_data
|
||||||
add-synced-lyrics --dir "${TEST_DIR}" --dry-run
|
add-synced-lyrics --dir "${TEST_DIR}" --dry-run
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_recurse_dry() {
|
test_directory_recurse_dry() {
|
||||||
|
setup_test_data
|
||||||
add-synced-lyrics --dir "${TEST_DIR}" --recurse --dry-run
|
add-synced-lyrics --dir "${TEST_DIR}" --recurse --dry-run
|
||||||
}
|
}
|
||||||
|
|
||||||
test_good_file() {
|
test_good_file() {
|
||||||
|
setup_test_data
|
||||||
add-synced-lyrics --file "${GOOD_SONG}" --debug
|
add-synced-lyrics --file "${GOOD_SONG}" --debug
|
||||||
test -f "${GOOD_LYRICS}"
|
test -f "${GOOD_LYRICS}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_good_file_embed() {
|
||||||
|
test_good_file
|
||||||
|
# rm "${GOOD_LYRICS}"
|
||||||
|
add-synced-lyrics --file "${GOOD_SONG}" --embed
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
test -f "${GOOD_LYRICS}"
|
||||||
|
|
||||||
|
# do it again to test that embedded is picked up and valid
|
||||||
|
run_test_cmd \
|
||||||
|
0 \
|
||||||
|
add-synced-lyrics --file "${GOOD_SONG}" --embed
|
||||||
|
[[ "${CMD_OUTPUT}" == *'already has lyrics, skipping'* ]]
|
||||||
|
}
|
||||||
|
|
||||||
test_bad_file() {
|
test_bad_file() {
|
||||||
|
setup_test_data
|
||||||
run_test_cmd \
|
run_test_cmd \
|
||||||
1 \
|
1 \
|
||||||
add-synced-lyrics \
|
add-synced-lyrics \
|
||||||
@@ -248,6 +277,7 @@ setup_test_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TESTS=(
|
TESTS=(
|
||||||
|
test_help_option
|
||||||
test_determine_inputs
|
test_determine_inputs
|
||||||
test_determine_inputs_recurse
|
test_determine_inputs_recurse
|
||||||
test_determine_inputs_find
|
test_determine_inputs_find
|
||||||
@@ -257,6 +287,7 @@ TESTS=(
|
|||||||
test_directory_recurse_dry
|
test_directory_recurse_dry
|
||||||
test_file_dry
|
test_file_dry
|
||||||
test_good_file
|
test_good_file
|
||||||
|
test_good_file_embed
|
||||||
test_bad_file
|
test_bad_file
|
||||||
test_directory_recurse
|
test_directory_recurse
|
||||||
test_directory_recurse_ignore
|
test_directory_recurse_ignore
|
||||||
@@ -376,5 +407,6 @@ if [[ $# -eq 0 ]]; then
|
|||||||
echo
|
echo
|
||||||
analyze_coverage
|
analyze_coverage
|
||||||
else
|
else
|
||||||
|
setup_coverage
|
||||||
"$@"
|
"$@"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user