I’ll keep updating here as I get closer to what I’m looking for. (Script needs ffmpeg and ffprobe, can easily be adapted for non-bash shells, will do later). I have beets setup in a virtual environment, so that can be removed if you have it install on your system and added to path.
I also have Plex Media Server set up (which unfortunately doesn’t support ID3 tags very well)… So for lyrics I’m dependant on the .lrc files.
Quick overview of what this script does:
- Scans media files based on the list of extensions
- Reads lyrics in the file.
- If, Then, Else
- If it’s synced, leave the file alone, and delete .lrc if it exists (my import and library folders are separate, which is why the 2nd part is done)
- Otherwise if it’s unsynced, look for synced lyrics (.lrc file) and embed
- If it’s unsynced and no .lrc file exists, strip the lyrics from the file so beets can autotag it with synced lyrics. Or leave it alone if it’s blank.
- Beet Update/Import/Convert
- Extracts the lyrics (.lrc and .txt) from all media files in the library (and exports a list of songs with unsynced lyrics to the library’s parent folder) for Plex Media Server to read
ROOT_DIR is just where the script is (the /home/$USER directory in my case)
#!/bin/bash
shopt -s globstar nocaseglob
# Get the directory of the script
ROOT_DIR="$(dirname "$(realpath "$0")")"
# State Import/Library folders
IMPORT_DIR="/home/${USER}/Downloads/Music"
LIBRARY_DIR="/home/${USER}/Music/Library"
# List of extensions of media files.
extensions=("flac" "m4a" "mp3")
### Embed Synced Lyrics in music to be imported
# Go to Import Directory
cd "$IMPORT_DIR"
find . -type f -name *.txt -delete
for ext in ${extensions[@]}; do
if [[ "$ext" == "flac" ]]; then
tags="LYRICS"
else
tags="lyrics"
fi
for file in **/*."$ext"; do
# Get lyrics from metadata
lyrics=$(ffprobe -loglevel error -show_entries format_tags="$tags" -of default=noprint_wrappers=1:nokey=1 "$file")
# If not empty, check if it's synced and try to embed if not.
if ! [ -z "$lyrics" ]; then
# If synced, leave untouched
if echo "$lyrics" | grep -qE '^\[[0-9]{2}:[0-9]{2}.[0-9]{2}\]'; then
echo "Synced lyrics already embedded in '$file'."
if [ -e "${file%.*}.lrc" ]; then rm "${file%.*}.lrc"; fi
# If not synced, look for .lrc file and embed
elif [ -e "${file%.*}.lrc" ]; then
lyrics="${file%.*}.lrc"
temp_file="${file%.*}_temp.${file##*.}"
ffmpeg -i "$file" -map 0 -c copy -metadata "$tags"="$(cat "$lyrics")" "$temp_file"
mv "$temp_file" "$file"
rm "$lyrics"
# Otherwise, strip for autotagging in beets
else
echo "Stripping lyrics from '$file' for autotagging."
temp_file="${file%.*}_temp.${file##*.}"
ffmpeg -i "$file" -map 0 -c copy -metadata "$tags"="" "$temp_file"
mv "$temp_file" "$file"
fi
# Embed synced lyrics if available
elif [ -e "${file%.*}.lrc" ]; then
lyrics="${file%.*}.lrc"
temp_file="${file%.*}_temp.${file##*.}"
ffmpeg -i "$file" -map 0 -c copy -metadata "$tags"="$(cat "$lyrics")" "$temp_file"
mv "$temp_file" "$file"
rm "$lyrics"
# Otherwise, just state it's empty
else
echo "'$file' has no lyrics"
fi
done
done
### Import all Music to Library
source "$ROOT_DIR/beets/bin/activate"
beet update
beet import "$IMPORT_DIR"
beet convert -y
deactivate
### Export (hopefully now Synced) Lyrics to .lrc file for Plex Media Server
cd "$LIBRARY_DIR"
for ext in ${extensions[@]}; do
if [[ "$ext" == "flac" ]]; then
tags="LYRICS"
else
tags="lyrics"
fi
for file in **/*."$ext"; do
# Check if any exported lyrics exists, delete unsynced if both exists
if [ -e "${file%.*}.lrc" ] && [ -e "${file%.*}.txt" ]; then
rm "${file%.*}.txt"
# If only unsynced exists
elif [ -e "${file%.*}.txt" ]; then
# Read Lyrics from file
lyrics=$(ffprobe -loglevel error -show_entries format_tags="$tags" -of default=noprint_wrappers=1:nokey=1 "$file")
# Ensure lyrics aren't empty
if ! [ -z "$lyrics" ]; then
# If synced, delete unsynced and save synced
if echo "$lyrics" | grep -qE '^\[[0-9]{2}:[0-9]{2}.[0-9]{2}\]'; then
echo "Synced lyrices for '$file' found, removing unsynced."
rm "${file%.*}.txt"
echo "$lyrics" > "${file%.*}.lrc"
else
echo "No synced lyrics found, leaving files intact."
echo "$file" >> "unsynced.txt"
fi
fi
# If no lyrics exists.
elif [ ! -e "${file%.*}.txt" ] && [ ! -e "${file%.*}.lrc" ]; then
# Read Lyrics from file
lyrics=$(ffprobe -loglevel error -show_entries format_tags="$tags" -of default=noprint_wrappers=1:nokey=1 "$file")
# Ensure lyrics aren't empty
if ! [ -z "$lyrics" ]; then
# If synced, delete unsynced and save synced
if echo "$lyrics" | grep -qE '^\[[0-9]{2}:[0-9]{2}.[0-9]{2}\]'; then
echo "Exporting synced lyrics for '$file'."
echo "$lyrics" > "${file%.*}.lrc"
else
echo "Exporting unsynced lyrics for '$file'."
echo "$lyrics" > "${file%.*}.txt"
echo "$file" >> "unsynced.txt"
fi
fi
else
echo "Lyrics already exists for '$file'"
fi
done
done