#!/bin/sh # this script converts / downloads / embeds cover art and removes additional unnecessary files. # requires: # sacad for downloading # metaflac for embedding # imagemagick for converting # imlib2 for getting dimensions # nsxiv for viewing (optional) # for the best results, this script should be run after tags have been correctly set. # the db.csv file is also required to make sure sacad searches for sub-units where necessary. # if running the script in batches, I also recommend deleting ~/.cache/sacad/sacad-cache.sqlite # before each run to avoid false positives when searching for art. # for safety, the script will only scan directories to check for existing cover art by default. # use -c to download and convert cover art. # use -e to embed art. this should only be done when you are sure all albums have high res art. # use -x to remove excess files. # NOTE: as of mid 2024 sacad has removed the amazon source due to users getting ip blocked. # this makes sacad almost completely useless for scraping kpop cover art since amazon was the only # source that consistently had high res art. expect to do a lot of manual downloading from now on. # I suggest using music.bugs.co.kr as it will almost always have art for korean releases in the highest # quality. rateyourmusic.com can be used as a secondary option and yandex image search as a last resort. # when downloading cover art from bugs you will need to change the url to get the high res art. # for example change this -> https://image.bugsm.co.kr/album/images/50/203232/20323299.jpg # to this -> https://image.bugsm.co.kr/album/images/3000/203232/20323299.jpg # you can go up to 5000 but anything in the 2-3k range is good enough imo. # an extension such as redirector can be used to automate this url changing. # check variables and programs. check() { [ ! -d "$master" ] && echo "invalid directory!" && exit [ ! -d "$(dirname "$log")" ] && mkdir "$(dirname "$log")" [ ! -f "$db" ] && echo "could not find db.csv database. generate it using list.sh" && exit [ ! "$(command -v sacad)" ] || [ ! "$(command -v magick)" ] && [ -n "$dl" ] && echo "imagemagick and sacad are required to use -c." && exit [ ! "$(command -v flac)" ] && [ -n "$embed" ] && echo "imagemagick and sacad are required to use -c." && exit } # downloads cover art using sacad dl() { album="$(metaflac --show-tag=ALBUM "$file" | cut -d '=' -f 2-)" artist="$(metaflac --show-tag=ARTIST "$file" | cut -d '=' -f 2-)" # ALBUMARTIST tag should be used unless artist is a sub-unit. if ! grep ^U "$db" | cut -f 2 | grep -qi -m 1 ^"$artist"$; then artist="$(metaflac --show-tag=ALBUMARTIST "$file" | cut -d '=' -f 2)" fi # aim for 3000x3000 with a 50% tolerance, allowing for as low as 1500x1500. sacad -t 50 "$artist" "$album" 3000 "$dir/Cover-new.jpg" } # converts existing cover art to the correct format / file naming convention. conv() { album="$(metaflac --show-tag=ALBUM "$file" | cut -d '=' -f 2-)" # list of common cover art file names to check for in order of prevalence. album title # is used as the last possible file name. it's a rare case but still worth checking for. for img in Cover cover COVER Folder folder FOLDER Front front FRONT "$album"; do if [ -f "$dir/$img.jpg" ]; then cp "$dir/$img.jpg" "$dir/Cover.jpg" && return elif [ -f "$dir/$img.jpeg" ]; then cp "$dir/$img.jpeg" "$dir/Cover.jpg" && return elif [ -f "$dir/$img.JPG" ]; then cp "$dir/$img.JPG" "$dir/Cover.jpg" && return elif [ -f "$dir/$img.png" ]; then convert -quality 95 "$dir/$img.png" "$dir/Cover.jpg" && return fi done } # compares the dimensions of downloaded cover art with already existing cover art. compare() { old="$(imlib2_load -vvj "$dir/Cover.jpg" | grep -oE 'WxH=([[:digit:]]+x[[:digit:]]+)' | cut -d 'x' -f 3)" new="$(imlib2_load -vvj "$dir/Cover-new.jpg" | grep -oE 'WxH=([[:digit:]]+x[[:digit:]]+)' | cut -d 'x' -f 3)" # if new cover art is larger than the old cover art, use it. # old cover art will not be overwritten in case the new cover art is wrong. if [ "$new" -gt "$old" ]; then mv "$dir/Cover.jpg" "$dir/Cover-old.jpg" mv "$dir/Cover-new.jpg" "$dir/Cover.jpg" fi } # embeds cover art embed() { # check if image file size isn't too big, otherwise embedding will fail. # first du checks file size is in MB and second checks if the first char is >5. if du "$dir/Cover.jpg" | grep -q M && [ "$(du "$dir/Cover.jpg" | cut -c 1)" -gt 5 ]; then qual=95 while :; do # use magick to reduce cover file size until it will fit echo "cover art file size is too big. reducing quality to $qual..." magick "$dir/Cover.jpg" -quality "$qual" "$dir/Cover-small.jpg" # don't overwrite old large cover art if [ "$(du "$dir/Cover-small.jpg" | cut -c 1)" -le 5 ]; then mv "$dir/Cover.jpg" "$dir/Cover-large.jpg" mv "$dir/Cover-small.jpg" "$dir/Cover.jpg" break fi qual=$((qual-5)) done fi # remove existing cover art metaflac --preserve-modtime --remove --block-type=PICTURE,PADDING --dont-use-padding "$dir"/*.flac # embed cover art metaflac --preserve-modtime --import-picture-from="$dir/Cover.jpg" "$dir"/*.flac } # scan directories for missing cover art and other miscellaneous files scan() { find "$master" -mindepth 1 -type d | while read -r dir; do if ls "$dir"/*.flac >/dev/null 2>&1; then [ ! -f "$dir"/Cover.jpg ] && echo "$(basename "$dir") is missing cover art!" | tee -a "$log" find "$dir" -type f | grep -qvE '(Cover\.jpg$|.*\.flac$)' && echo "$(basename "$dir") has extra files!" | tee -a "$log" file="$(find "$dir" -type f -name '*.flac' | head -n 1)" metaflac --list --block-type=PICTURE "$file" | grep -qE '(Cover (front)|PICTURE)' || echo "$(basename "$dir") is missing embedded art!" | tee -a "$log" fi done } # delete additional files delete() { # get number of files num="$(find "$master" -mindepth 1 -type d | while read -r dir; do if ls "$dir"/*.flac >/dev/null 2>&1; then find "$dir" -type f | grep -vE '(Cover\.jpg$|.*\.flac$)' | while read -r f; do echo "$f"; done fi done | wc -l)" # if one or more files are found, then print a list and give the user the option to delete if [ "$num" -ge 1 ]; then find "$master" -mindepth 1 -type d | while read -r dir; do if ls "$dir"/*.flac >/dev/null 2>&1; then find "$dir" -type f | grep -vE '(Cover\.jpg$|.*\.flac$)' | while read -r f; do echo "$f"; done fi done printf "delete %d extra files? [Y/n]: " "$num" read -r ans if [ "$ans" != "n" ]; then find "$master" -mindepth 1 -type d | while read -r dir; do if ls "$dir"/*.flac >/dev/null 2>&1; then find "$dir" -type f | grep -vE '(Cover\.jpg$|.*\.flac$)' | while read -r f; do rm -v "$f"; done fi done fi fi } # view cover art. marked files will be sent to the log. view() { # nsxiv must be installed to view cover art [ ! "$(command -v nsxiv)" ] && echo "nsxiv is required to view cover art." && exit find "$master" -type f -name 'Cover.jpg' | LC_COLLATE=C sort -f | nsxiv -ioat | while read -r path; do [ -n "$path" ] && echo "$path marked in nsxiv" | tee -a "$log"; done } while getopts "d:cehvx" o; do case "${o}" in c) dl=1;; e) embed=1;; d) master="$OPTARG";; v) check; view; exit;; x) check; delete; exit;; h|*) printf "usage: ./%s [OPTION]...\n\noptions: -d specify directory to scan -c convert existing and download new cover art -e embed cover art -v view existing cover art -x delete additional files\n" "$(basename "$0")" && exit 1 esac done # set log and database files log="../logs/cover.log" db="../lists/db.csv" # check variables and programs. check # begin new log printf "\nscript run - %s\n" "$(date)" >> "$log" if [ -n "$dl" ] || [ -n "$embed" ]; then num=1 dirs="$(find "$master" -type d -links 2)" # main loop echo "$dirs" | while read -r dir; do if ls "$dir"/*.flac >/dev/null 2>&1; then printf "\033[1m[%d/%d] %s\033[0m\n" "$num" "$(echo "$dirs" | wc -l)" "$(basename "$dir")" # prefer using a track without features # shouldn't matter if the tag script has already been run, but it's best to be safe file="$(find "$dir" -type f -name '*.flac' | grep -ivE feat | head -n 1)" if [ "$(find "$dir" -type f -name '*.flac' | wc -l)" -eq 1 ] || [ -z "$file" ]; then file="$(find "$dir" -type f -name '*.flac' | head -n 1)" fi # download / convert cover art if [ -n "$dl" ]; then # check for existing cover art and convert it to the correct file name and format [ ! -f "$dir/Cover.jpg" ] && echo "converting existing cover art if present..." && conv # if cover art now exists, check the dimensions if [ -f "$dir/Cover.jpg" ]; then dim="$(imlib2_load -vvj "$dir/Cover.jpg" | grep -oE 'WxH=([[:digit:]]+x[[:digit:]]+)' | cut -d 'x' -f 3)" else # otherwise reset this var dim="" fi # if height isn't at least 2000, try to download higher res cover art if [ -n "$dim" ] && [ "$dim" -ge 2000 ]; then echo "using existing cover art..." else echo "downloading cover art..." && dl fi # if no art was downloaded and no art already existed, move to the next directory if [ ! -f "$dir"/Cover.jpg ] && [ ! -f "$dir"/Cover-new.jpg ]; then echo "could not find cover art. skipping..." && continue # if art was downloaded and art already existed, compare the dimensions and choose the highest quality image elif [ -f "$dir"/Cover.jpg ] && [ -f "$dir"/Cover-new.jpg ]; then echo "selecting highest quality cover art..." && compare # if art was downloaded and no art already existed, just rename the file elif [ ! -f "$dir"/Cover.jpg ] && [ -f "$dir"/Cover-new.jpg ]; then mv "$dir/Cover-new.jpg" "$dir/Cover.jpg" fi fi # embed cover art [ -n "$embed" ] && [ -f "$dir"/Cover.jpg ] && echo "embedding cover art..." && embed num=$((num+1)) fi done else printf "\033[1mperforming DRY RUN. use -c to download new and convert existing cover art or -e to embed cover art.\033[0m\n\n" fi printf "\n\033[1mscanning for missing cover art and additional files...\033[0m\n" && scan printf "\n\033[1mscript completed! remember to check log for errors.\033[0m\n" # sometimes sacad can download the wrong cover art, so we should manually check all the covers by opening them in nsxiv. # any image viewer that can print to stdout could be used here in place of nsxiv. if [ -n "$dl" ] && [ -z "$embed" ]; then printf "view cover art? [Y/n]: " read -r ans [ "$ans" != "n" ] && view fi