#!/bin/sh # --------------------------------------------------------------------- # DESCRIPTION: # Maintains an OGG Vorbis mirror of a file tree of FLAC audio files # # AUTHOR: # Simon Kinsella, November 2009 # Chris Quirk, January 2010 # # LICENSE: BSD license # WARRANTY: NONE. Not rigorously tested, but it works for me! # Use at your own risk. # # --------------------------------------------------------------------- # # IMPORTANT - paths to master and slave trees MUST be absolute! # # Syntax: # ------ # ogg-sync.sh # where is an absolute path to the base of the FLAC audio # tree. # The OGG mirrors will be created under a directory 'q' under # absolute path where n is defined by the QUALITY variable # e.g.: # ogg-sync.sh /music/flac-audio /music/ogg-mirrors # will create an OGG mirror of '/music/flac-audio' under # '/music/ogg-mirrors/q2/' # # FINAL WARNING: Please back-up your files and test the operation # of this script before using day-to-day. # # TODO: # 1) Make the syntax more forgiving, possibly # 2) Better error handling / recovery, the usual stuff. # 3) Find a way to dispatch encoding jobs concurrently for SMP systems. # Tried xjobs but was unsuccessful. # 4) Make stat commands portable between linux and fbsd or clean up the conditionals # 5) tag dump, reinsert all non-null tags into the new ogg as part of encoding (inserts new dependancy on metaflac) # 6) test for absolute paths OGGENC="$(which oggenc)" SCRIPT="$(basename $0)" ARCH="$(uname)" #set quality to a value between or equal to -1 and 10. #See: http://en.wikipedia.org/wiki/Vorbis QUALITY=5 #override the quality argument if [ -z "$QUALITY" ]; then while getopts ":q:" Option do case $Option in q ) QUALITY="${OPTARG}" if [ ${QUALITY} -lt -1 -o ${QUALITY} -gt 10 ] then echo "${SCRIPT}: Invalid Quality value '${QUALITY}'" exit 1 fi esac done shift $(($OPTIND - 1)) fi if [ "$1" ] then if [ ! -d "$1" ] then echo "${SCRIPT}: Invalid master directory '$1'" exit 1 fi MASTER_DIR="$1" else echo "${SCRIPT}: Master directory not specified" exit 1 fi if [ "$2" ] then SLAVE_DIR="$2" else echo "${SCRIPT}: Slave directory not specified" exit 1 fi #============================================================================= #1) For each ogg file in the slave tree: # 1.1) Look for flac file of the same path and name in the master tree: # 1.1.1) If not found, delete the ogg echo "Removing stale ogg files..." mkdir -p "${SLAVE_DIR}/q${QUALITY}" cd "${SLAVE_DIR}/q${QUALITY}" SLAVE_DIR="$(pwd)" find . -type f -name '*.ogg' | while read FILE do if [ ! -f "${MASTER_DIR}/${FILE%.*}.flac" ] then echo "Master file ${MASTER_DIR}/${FILE%.*}.flac not found - deleting ${SLAVE_DIR}/${FILE}" rm -vf "${SLAVE_DIR}/${FILE}" fi done # 2) Look for existing manifest file in MASTER_DIR/ogg-sync-q${QUALITY}.manifest cd "${MASTER_DIR}" echo "Preparing manifests..." MANIFEST="${MASTER_DIR}/ogg-sync-q${QUALITY}.manifest" touch "${MANIFEST}" rm -f "${MANIFEST}.new" #in case the script didnt complete successfully on last invocation touch "${MANIFEST}.new" #3) For each entry in the manifest: # 3.1) If no matching flac file exists, remove the manifest entry # 3.2) If it does, reinsert into new manifest to be parsed in step 4 # Manifest lines consist of size date and filename, delimited by tabs echo "Cleaning manifest..." cat ${MANIFEST}|while IFS=$(echo '\t') read OLD_SIZE OLD_DATE FILE do if [ -f "${FILE}" ]; then if [ "$ARCH" = "FreeBSD" ]; then NEW_SIZE="$(stat -f "%z" "${FILE}")" NEW_DATE="$(stat -f "%Sm" "${FILE}")" elif [ "$ARCH" = "Linux" ]; then NEW_SIZE="$(stat -c %s "${FILE}")" NEW_DATE="$(stat -c %y "${FILE}")" #gnu coreutils date output is ugly, check others for consistency else echo "Unknown arch: $ARCH" && exit 1 fi #if the size and date are the same, add it back to the manifest if [ "${OLD_SIZE}" = "${NEW_SIZE}" -a "${OLD_DATE}" = "${NEW_DATE}" ]; then echo "${NEW_SIZE}"'\t'"${NEW_DATE}"'\t'"${FILE}" >> "${MANIFEST}.new" fi fi done rm "${MANIFEST}" mv "${MANIFEST}.new" "${MANIFEST}" #4) For each flac file in the master tree: # 4.1) If file entry doesnt exists in the manifest (same size, date, and filename; tab delimited): # 4.1.1) Dump tags # 4.1.2) Create a new ogg w/ tags # 4.1.3) Add a manifest entry echo "Generating OGG files..." cd ${MASTER_DIR} find . -type f -name '*.flac' \ | while read FILE do if [ "$ARCH" = "FreeBSD" ]; then SIZE="$(stat -f "%z" "${FILE}")" DATE="$(stat -f "%Sm" "${FILE}")" elif [ "$ARCH" = "Linux" ]; then SIZE="$(stat -c %s "${FILE}")" DATE="$(stat -c %y "${FILE}")" else echo "Unknown arch: $ARCH" && exit 1 fi #if grep exits with 1, process file, add file to manifest ENTRY="$(echo "${SIZE}"'\t'"${DATE}"'\t'"${FILE}")" grep -qF "${ENTRY}" "${MANIFEST}" if [ $? -ne 0 ]; then mkdir -p "${FILE%/*}" \ && "${OGGENC}" -Qq"${QUALITY}" -o "${SLAVE_DIR}/${FILE%.*}.ogg" "${FILE}" \ && echo "${SIZE}"'\t'"${DATE}"'\t'"${FILE}" >> "${MANIFEST}" fi done exit 0