Playrsync

Description: Simple Script To Sync + Transcode a Lossless Music Library To A Portable Device
Dependencies: vorbis-tools, imagemagick, rsync, awk, coreutils (These are Arch Packages, The names may differ across distros.)
Download: playrsync.sh
Source Code [SH]:
#!/bin/zsh
### Playrsync: A simple script for syncing filesystem-based music players
### Dependencies: vorbis-tools, imagemagick, rsync, awk, coreutils

## Settings
inputdir=$HOME/Music
outputdir=/media/CLIPZIP_SD/MUSIC
tmpdir=/tmp/playrsync
# -q parameter to oggenc
oggquality='4.5'
# -scale parameter to convert
coversize='150x150'

# Make Temporary Directory
if [ -e "$tmpdir" ]; then
  rm -rf "${tmpdir}"
fi
mkdir -p "$tmpdir"
cd "$tmpdir"

# If outdir doesn't exist, create it.
if [ ! -e "$outputdir" ]; then
  mkdir -p "$outputdir"
fi

# Copy directory structure ONLY
rsync -a -f"+ */" -f"- *" "${inputdir}/" "${outputdir}/"

## $inputdir File Lists
# List all files in $inputdir
find "$inputdir" > musiclibrary

# List flac files from input dir and change the extension (for comparison) 
awk '/.flac$/{gsub(/.flac$/,".ogg"); print}' musiclibrary > inflacs

# List everything that is not a flac in $inputdir
awk  '!/.flac$/{print}' musiclibrary > notaflac

# .jpg covers
awk '/cover.jpg/{print}' musiclibrary > injpgcovers

# .png covers 
awk '/cover.png/{print}' musiclibrary > inpngcovers

# All covers 
awk '/cover.png|cover.jpg/{print}' musiclibrary > inallcovers

# Get files that are natively lossy from $inputdir
awk '/.mp3$|.ogg$/{print}' musiclibrary > nativelossy

# List EVERYTHING in $inputdir for comparison at the end (with comparison mods)
awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(inpath,outpath);gsub(/.flac$/,".ogg");gsub(/cover.jpg|cover.png/, "folder.jpg");print}' musiclibrary > incomplist

## $outputdir File Lists
# List all files in $outputdir
find "$outputdir" > targetfiles

# List covers in $outputdir
awk '/.jpg$/{print}' targetfiles > outcovers

## Comparison Lists
# Remove everything that is not a flac from the output directory's list (these will be handled separately)
comm -13 <(awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(inpath,outpath);gsub(/cover.jpg|cover.png/,"folder.jpg");print}' notaflac | sort) <(sort targetfiles) > outflacs 

# Files to Convert
comm -23 <(sort inflacs) <(awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(outpath,inpath);print}' outflacs | sort) | awk '{gsub(/.ogg$/,".flac");print}' > flacstosync

# Covers to convert?
comm -23 <(awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(inpath,outpath);gsub(/cover.jpg|cover.png/,"folder.jpg");print}' inallcovers | sort) <(sort outcovers) | awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(outpath,inpath); print}' > diffcovers

# If there are covers to convert, figure out which format they are so that their source path is correct.
if [ -n "$(cat diffcovers)" ]; then
  comm -12 <(awk '{gsub(/cover.jpg/,"folder.jpg");print}' injpgcovers | sort) <(sort diffcovers) | awk '{gsub(/folder.jpg/,"cover.jpg");print}' > coverstosync
  comm -12 <(awk '{gsub(/cover.png/,"folder.jpg");print}' inpngcovers | sort) <(sort diffcovers) | awk '{gsub(/folder.jpg/,"cover.png");print}' >> coverstosync
else
  touch coverstosync
fi

# Lossy Files To Copy
comm -23 <(sort nativelossy) <(awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(outpath,inpath);print}' targetfiles | sort) > lossytosync

# Foreign Files
comm -31 <(sort incomplist) <(sort targetfiles) > foreignfiles

## Arrays
# Turn file lists into arrays
saveIFS="$IFS" 
IFS='
'
syncfiles=($(sort flacstosync))
synccovers=($(sort coverstosync))
synclossy=($(sort lossytosync))
foreignfiles=($(sort foreignfiles))
IFS="$saveIFS"

## Begin Sync
# Convert and Sync FLACs
if [ -n "${syncfiles}" ]; then
  echo "Converting and Syncing FLACs"
  for i in "${syncfiles[@]}"; do
    outfile=$(echo "$i" | awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(inpath,outpath);gsub(/.flac$/,".ogg");print}')
    oggenc -q "${oggquality}" -o "${outfile}" "${i}"
  done
else 
  echo "Converted FLAC files are up-to-date."
fi

# Convert and Sync covers
if [ -n "${synccovers}" ]; then
  echo "Converting and Syncing Covers"
  for i in "${synccovers[@]}"; do
    outfile=$(echo "${i}" | awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(inpath,outpath);gsub(/cover.jpg|cover.png/,"folder.jpg");print}')
    convert -scale "$coversize" "${i}" "${outfile}"
  done
else 
  echo "Converted Covers are up-to-date."
fi

# Copy Lossy Music
if [ -n "${synclossy}" ]; then 
  echo "Syncing Lossy Music"
  for i in "${synclossy[@]}"; do
    outfile=$(echo "$i" | awk -v "inpath=${inputdir}" -v "outpath=${outputdir}" '{gsub(inpath,outpath);print}')
    cp "$i" "$outfile"
  done
else 
  echo "Lossy Files are up-to-date."
fi

# Function to delete foreign files
rmconfirm() {
  echo -n "This will PERMANENTLY delete all foreign files. Continue? [y/N] "
  read confirmation
  case "$confirmation" in
    y|Y|yes|YES|Yes) rm -rf "${foreignfiles[@]}"; echo "All foreign files have been removed." ;;
    *) echo "Foreign files were not deleted.";;
  esac
}

# If foreign files are found, prompt for action
if [ -n "${foreignfiles}" ]; then
  echo -e "Foreign Files Detected!"
  while true; do
    echo -e -n "\nWhat do you want to do?\n[L]ist Files, [D]elete Files, or [I]gnore? "
    read foreignaction
    echo
    case "$foreignaction" in 
      l|L|list|LIST|List) cat foreignfiles;;
      d|D|delete|DELETE|Delete) rmconfirm; break;; 
      i|I|ignore|IGNORE|Ignore) break;;
      *) echo "Invalid option, Try again.";;
    esac
  done
fi

# Clean up tempfiles
rm -rf "${tmpdir}"

echo -e "\nMusic Library Has Been Synced!"