bash-music-sync/music-sync.sh

412 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
begin=$(date +"%s")
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset
source="-"
dest="-"
convert=false
verbose=2
bitrate=192
help=false
temp="/tmp/converted"
convertart=false
coverartsize=200
CheckDeps() {
if [[ $1 == 0 ]]; then
# Check getopt
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
VerboseOutput 5 "\`getopt --test\` failed"
VerboseOutput 5 "Sorry, It seems that your shell is not supported"
VerboseOutput 5 "If you're using MacOS or another unix-like system, please install GNU getopt"
ExecTime
exit 1
fi
VerboseOutput 0 "\`getopt --test\` succeeded"
fi
if [[ $1 == 1 ]]; then
# Check lame
if [[ $convert == true && ! $(lame --version 2>/dev/null) ]]; then
VerboseOutput 5 "\`lame --version\` failed"
VerboseOutput 5 "Sorry, It seems that lame is not installed on your system"
VerboseOutput 5 "Please install lame from your repositories and make sure it is available in your \$PATH"
VerboseOutput 5 "Otherwise disable conversion"
ExecTime
exit 1
fi
VerboseOutput 0 "\`lame --version\` succeeded"
# Check EyeD3
if [[ $convertart == true && ! $(eyeD3 --version 2>/dev/null) ]]; then
VerboseOutput 5 "\`eyeD3 --version\` failed"
VerboseOutput 5 "Sorry, It seems that eyeD3 is not installed on your system"
VerboseOutput 5 "Please install eyeD3 from your repositories and make sure it is available in your \$PATH"
VerboseOutput 5 "Otherwise disable albumart conversion"
ExecTime
exit 1
fi
VerboseOutput 0 "\`eyeD3 --version\` succeeded"
# Check ImageMagick
if [[ $convertart == true && ! $(convert --version 2>/dev/null) ]]; then
VerboseOutput 5 "\`convert --version\` failed"
VerboseOutput 5 "Sorry, It seems that ImageMagick is not installed on your system"
VerboseOutput 5 "Please install ImageMagick from your repositories and make sure it is available in your \$PATH"
VerboseOutput 5 "Otherwise disable albumart conversion"
ExecTime
exit 1
fi
VerboseOutput 0 "\`convert --version\` succeeded"
fi
VerboseOutput 1 "Dependency test OK"
}
GetOptions() {
# https://stackoverflow.com/a/29754866
OPTIONS=s:d:t:c::a::v::h
LONGOPTS=source:,dest:,temp:,convert::,resize-art::,verbose::,help
# -use ! and PIPESTATUS to get exit code with errexit set
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via -- "$@" to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
# e.g. return value is 1
# then getopt has complained about wrong arguments to stdout
Usage
exit 2
fi
# read getopts output this way to handle the quoting right:
eval set -- "$PARSED"
# now enjoy the options in order and nicely split until we see --
while true; do
case "$1" in
-v|--verbose)
verbose=2
if [[ $2 != "" ]]; then
verbose=${2}
shift
fi
shift
;;
-h|--help)
help=true
shift
;;
-s|--source)
VerboseOutput 0 "--source given."
VerboseOutput 1 "Source is ${2}"
source="$2"
shift 2
;;
-d|--dest)
VerboseOutput 0 "--dest given."
VerboseOutput 1 "Destination is ${2}"
dest="$2"
shift 2
;;
-t|--temp)
VerboseOutput 0 "--temp given"
VerboseOutput 1 "Tempfolder is ${2}"
temp="$2"
shift 2
;;
-c|--convert)
VerboseOutput 0 "--convert given"
convert=true
bitrate=192
if [[ $2 != "" ]]; then
bitrate=${2}
shift
fi
VerboseOutput 1 "Converted bitrate is ${bitrate}"
shift
;;
-a|--resize-art)
VerboseOutput 0 "--resize-art given"
convertart=true
coverartsize=200
if [[ $2 != "" ]]; then
coverartsize=${2}
shift
fi
shift
VerboseOutput 1 "Album art will ${coverartsize}px wide"
;;
--)
shift
break
;;
*)
VerboseOutput 5 "Programming error"
return 3
;;
esac
done
if [[ ! -z "${1+x}" ]]; then
if [[ ${source} != "-" ]]; then
VerboseOutput 4 "Source provided twice. Continueing with ${1}"
fi
source="$1"
VerboseOutput 1 "Source is ${1}"
fi
if [[ ! -z "${2+x}" ]]; then
if [[ ${dest} != "-" ]]; then
VerboseOutput 4 "Destination provided twice. Continueing with ${2}"
fi
dest="$2"
VerboseOutput 1 "Destination is ${2}"
fi
if [[ $dest == "-" ]] || [[ $source == "-" ]]; then
help=true
fi
VerboseOutput 1 "Checks OK. Going on"
}
Usage() {
echo ""
echo "Usage:"
echo "music-sync <options> -s|--source <source> -d|--dest <destination>"
echo "music-sync <options> <source> <destination>"
echo ""
echo "Syncronises music from one folder to another."
echo ""
echo "Options:"
echo " -s, --source <source> The source folder of the music"
echo " -d, --dest <destination> The destination folder of the music"
echo " -t, --temp <folder> The temporary cache for converted files (default: /tmp/converted)"
echo " -c, --convert <bitrate> Convert files to a given bitrate in kbps before syncing (default: 192)"
echo " -a, --resize-art <width> Resize album-art before syncing (default width: 200)"
echo " -v, --verbose <0-6> Set log level (default: 2)"
echo " -h, --help Display this help text"
echo ""
echo "Log levels:"
echo " 0 | Verbose"
echo " 1 | Debug"
echo " 2 | Info"
echo " 3 | Warning"
echo " 4 | Error"
echo " 5 | Fatal"
echo " 6 | Silence"
echo ""
echo "Exit Codes:"
echo " 1 Dependencies not met"
echo " 2 Invalid Argument"
echo " 3 Source Unreachable"
echo " 4 Destination Unreachable"
echo " 5 Command failed"
echo ""
}
VerboseOutput() {
level=""
if [[ $verbose -le $1 ]]; then
case "$1" in
0)
level="\033[1;36mVerbose\033[0m"
;;
1)
level="\033[1;34m Debug \033[0m"
;;
2)
level="\033[1;37m Info \033[0m"
;;
3)
level="\033[1;33mWarning\033[0m"
;;
4)
level="\033[1;31m Error \033[0m"
;;
5)
level="\033[1;30m Fatal \033[0m"
;;
esac
echo -e "[$level] $2" >&2
fi
}
CreateFileList() {
# ${1} /mnt/hdd/Example-Artist/Example-Album
# ${2} /mnt/mtp/Example-Artist/Example-Album
# ${3} Example-Artist/Example-Album
IFS=""
sourcepath="${1/\[/\\\[}/*"
sourcepath="${sourcepath/\]/\\\]}"
for file in $sourcepath; do
origfile="${file#"$1/"}"
relfile=$(echo ${origfile} | sed -e 's/\(\.\)*$//g')
VerboseOutput 0 "Checking ${origfile}"
if [[ -d "${1}/$origfile" ]]; then
VerboseOutput 0 "${origfile} is folder"
newdir="${3}/$origfile"
newdir=${newdir#"/"}
VerboseOutput 1 "Entering $newdir"
CreateFileList "${1}/$origfile" "${2}/$relfile" "$newdir"
elif [[ "${1}/$origfile" != *".m3u" ]] && [[ ! -f "${2}/$origfile" || "${1}/$origfile" -nt "${2}/$relfile" ]]; then
echo ${3}/$origfile >> /tmp/music-sync-filelist
VerboseOutput 0 "${origfile} is newer in source"
VerboseOutput 2 "Added: ${3}/${origfile}"
fi
if [[ "${1}/$origfile" == *".m3u" ]]; then
VerboseOutput 0 "${origfile} is playlist"
fi
done
}
ConvertFiles() {
curline=0
percentage=0
while read -r line
do
if [[ ! -f "${source}/$line" ]]; then
VerboseOutput 5 "Source-file ${source}/$line Unreachable"
ExecTime
exit 3
fi
curline=$(expr ${curline} + 1)
total=$(cat /tmp/music-sync-filelist | wc -l)
percentage=$(echo "scale=4;${curline}/${total}" | bc)
percentage=$(echo "scale=2;${percentage}*100" | bc)
VerboseOutput 1 "Converting: $line"
VerboseOutput 2 "Progress: $curline / $total (${percentage%00}%) Step 1 of 2"
if [[ "$temp/$line" = */* ]]; then
VerboseOutput 0 "Creating folder $temp/${line%/*}"
mkdir -p "$temp/${line%/*}";
fi;
if [[ ! -f "$temp/$line" || "${source}/$line" -nt "$temp/$line" ]]; then
VerboseOutput 0 "Converting MP3-file $temp/${line%/*}"
lame -b ${bitrate} $source/$line $temp/$line 1>/dev/null 2>/dev/null
if [[ $convertart == true ]]; then
VerboseOutput 0 "Creating folder $temp/$line-images/"
mkdir -p "$temp/$line-images/"
VerboseOutput 0 "Extracted albumart"
eyeD3 --write-images "$temp/$line-images/" "$temp/$line" 1>/dev/null 2>/dev/null
VerboseOutput 0 "Converting albumart"
convert "$temp/$line-images/FRONT_COVER.*" -resize ${coverartsize}x${coverartsize} "$temp/$line-images/FRONT_COVER.jpg" 1>/dev/null 2>/dev/null
eyeD3 --remove-all-images "$temp/$line" 1>/dev/null 2>/dev/null
VerboseOutput 0 "Embedding albumart"
eyeD3 --add-image "$temp/$line-images/FRONT_COVER.jpg:FRONT_COVER" "$temp/$line" 1>/dev/null 2>/dev/null
VerboseOutput 1 "Converted cover art: $line"
fi
VerboseOutput 2 "Converted: $line"
else
VerboseOutput 3 "$line already converted"
fi;
done < "/tmp/music-sync-filelist"
VerboseOutput 2 "Done converting files"
}
CopyFiles() {
curline=0
percentage=0
while read -r line
do
if [[ ! -d "$dest" ]]; then
VerboseOutput 5 "Destination unreachable"
ExecTime
exit 4
fi
if [[ ! -f "${temp}/$line" ]]; then
VerboseOutput 5 "Source-file ${temp}/$line Unreachable"
ExecTime
exit 3
fi
curline=$(expr ${curline} + 1)
total=$(cat /tmp/music-sync-filelist | wc -l)
percentage=$(echo "scale=4;${curline}/${total}" | bc)
percentage=$(echo "scale=2;${percentage}*100" | bc)
destline=$(echo $line | sed -e 's/\.*\//\//g')
VerboseOutput 1 "Copying: $line"
VerboseOutput 2 "Progress: $curline / $total (${percentage%00}%) Step 2 of 2"
if [[ "$dest/$line" = */* ]]; then
VerboseOutput 0 "Creating folder $dest/${line%/*}"
mkdir -p "$dest/${destline%/*}";
fi;
cp -f $temp/$line $dest/$destline 1>/dev/null 2>/dev/null
VerboseOutput 2 "Copied: $line"
done < "/tmp/music-sync-filelist"
VerboseOutput 2 "Done copying files"
}
CleanUp() {
VerboseOutput 1 "Cleaning Up"
if [[ -f /tmp/music-sync-filelist ]]; then
VerboseOutput 1 "Removing filelist"
rm "/tmp/music-sync-filelist"
fi
VerboseOutput 1 "Done"
}
ExecTime() {
termin=$(date +"%s")
difftimelps=$(($termin-$begin))
VerboseOutput 1 "$(($difftimelps / 60)) minutes and $(($difftimelps % 60)) seconds elapsed for Script Execution."
}
ErrorHandler() {
VerboseOutput 5 "Error while executing $1"
CleanUp
exit 5
}
ExitHandler() {
VerboseOutput 5 "Aborted"
CleanUp
exit 0
}
trap 'ExitHandler' SIGINT
trap 'ErrorHandler $BASH_COMMAND' ERR
CheckDeps 0
GetOptions $@
CheckDeps 1
if [[ $help == true ]]; then
Usage
exit
fi
if [[ -f /tmp/music-sync-filelist ]]; then
rm /tmp/music-sync-filelist
fi
VerboseOutput 2 "Scanning for new or updated files"
CreateFileList $source $dest ""
if [[ ! -f /tmp/music-sync-filelist ]]; then
VerboseOutput 2 "Nothing to do!"
CleanUp
exit 0
fi
if [[ $convert == true ]]; then
ConvertFiles
else
if [[ $temp != "/tmp/converted" ]]; then
VerboseOutput 2 "Conversion not enabled. Ignoring cache folder"
fi
temp=$source
fi
CopyFiles
CleanUp
ExecTime