#!/bin/bash
#####################################################################
##                                                                 ##
##    audio filesorting v0.2                                       ##
##    this script can be u$SED to sort recovered audio files       ##
##                                                                 ##
#####################################################################

#####################################################################
##                                                                 ##
## General:                                                        ##
##  This Script is free; You can do whatever you want with it,     ##
##  except the following:                                          ##
##    - You're not allowed to publish it under any license.        ##
##    - You're not allowed to remove these lines.                  ##
##    - You're not allowed to remove or modify the author line.    ##
## Disclaimer of Warranty and Liability:                           ##
##  THIS SCRIPT IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTY  ##
##  OF ANY KIND, EITHER EXPRES$SED OR IMPLIED, INCLUDING, WITHOUT  ##
##  LIMITATION, WARRANTIES THAT THE SCRIPT IS FREE OF DEFECTS,     ##
##  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.  ##
##  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE       ##
##  SCRIPT IS WITH YOU.                                            ##
##  SHOULD THIS SCRIPT PROVE DEFECTIVE IN ANY RESPECT, YOU         ##
##  (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME    ##
##  THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION.     ##
##                                                                 ##
##    Author: Jean Michel Bruenn <jean.bruenn@jeanbruenn.info>     ##
##    Date:  31th December 2008                                    ##
##                                                                 ##
#####################################################################

###
# ToDo
###
#    - dots in a filenames causes errors.
#    - we cannot differ between a sampler (containing many
#      different artists) and an album by ONE specific
#      artist yet.
#    - some check like "is the file corrupted" would be nice.

###
# Changelog
###
# 0.2
#    - Added ogg support
#    - replaced some sed rules with tr
#    - fixed some bugs due to newlines in tags
#
# 0.1
#    - Initial release, used imageSorting 0.5 as template.
###

###
# Variables
###
PATT='.*\.\(mp3\|ogg\)';
GREP=$(which grep);
SED=$(which sed);
CUT=$(which cut);
IDTAG=$(which id3info);
OGGTAG=$(which ogginfo);
TOSTRIP="[:blank:][:punct:]\n\r\t ";

#####################################################################
##                                                                 ##
##    DO NOT CHANGE ANYTHING BELOW THIS LINE                       ##
##        ...except you know what you are doing of course.         ##
##                                                                 ##
#####################################################################

###
# imsCheck : tests whether needed tools exist
###
imsCheck() {

  if [ -e $GREP ]; then
    if [ $DEBUG -eq 1 ] ; then
      echo "++ grep found in $GREP";
    fi
  else
    echo "+ grep not found. exiting...";
    exit 0
  fi

  if [ -e $SED ]; then
    if [ $DEBUG -eq 1 ] ; then
      echo "++ sed found in $SED";
    fi
  else
    echo "+ sed not found. exiting...";
    exit 0
  fi

  if [ -e $CUT ]; then
    if [ $DEBUG -eq 1 ] ; then
      echo "++ cut found in $CUT";
    fi
  else
    echo "+ cut not found. exiting...";
    exit 0
  fi

}

###
# this is the main function, containing the IMSTHREADS-skelleton
# and calling the other functions
###
imsMain() {

	imsCheck

   n=0

   # preinitialize the slots
   for ((i=0; i<IMSTHREADS; i++)); do
      SLOT[i]=-1
   done

   while sleep 0.5 && ((n < NUMFILES)); do
      echo "DEBUG: n = $n" >&2
      # find free slots and use them
      for ((i = 0; i < IMSTHREADS; i++)); do
         if (( ${SLOT[i]} == -1 )); then
            imsProcess "Slot $i":"${FILES[n]}" &
            ((n++))
            SLOT[i]=$!
            echo "+ Slot $i taken" >&2
            (((n+1) > NUMFILES)) && break
         fi
      done

      # find terminated slots and tag them 'free'
      for ((i = 0; i < IMSTHREADS; i++)); do
         if ! kill -0 ${SLOT[i]} >/dev/null 2>&1; then
            echo "+ Slot $i released" >&2
            SLOT[i]=-1
         fi
      done
   done

   # wait for remaining slots to run out
   exit=0
   while ! ((exit)); do
      exit=1
      for ((i = 0; i < IMSTHREADS; i++)); do
         if ! kill -0 ${SLOT[i]} >/dev/null 2>&1; then
            echo "+ Slot $i released" >&2
            SLOT[i]=-1
         fi
      done

      for ((i = 0; i < IMSTHREADS; i++)); do
         ((${SLOT[i]} != -1)) && exit=0
      done
   done

}

imsGetTagsMpeg() {

	# first split the data we got
	local SLOT=$(echo "$@" | $CUT -d: -f1);
	local FILENAME=$(echo "$@" | $CUT -d: -f2);

	# now let's run id3info on our mp3
	local IALBUM=$($IDTAG "$FILENAME" | grep "=== TALB" | $SED -r 's|.*: ||');
	local IARTIST=$($IDTAG "$FILENAME" | grep "=== TPE1" | $SED -r 's|.*: ||');
	local ICONTENT=$($IDTAG "$FILENAME" | grep "=== TCON" | $SED -r 's|.*: ||');
	local ITITLE=$($IDTAG "$FILENAME" | grep "=== TIT2" | $SED -r 's|.*: ||');
	local IYEAR=$($IDTAG "$FILENAME" | grep "=== TYER" | $SED -r 's|.*: ||');
	local ITRACK=$($IDTAG "$FILENAME" | grep "=== TRCK" | $SED -r 's|.*: ||');

	# Now we need to fix the stuff a bit by removing whitespaces
	local IALBUM=$(echo "$IALBUM" | tr -d "$TOSTRIP");
	local IARTIST=$(echo "$IARTIST" | tr -d "$TOSTRIP");
	local ITITLE=$(echo "$ITITLE" | tr -d "$TOSTRIP");
	local ICONTENT=$(echo "$ICONTENT" | tr -d "$TOSTRIP");
	local ITRACK=$(echo "$ITRACK" | tr -d "$TOSTRIP");
	local IYEAR=$(echo "$IYEAR" | tr -d "$TOSTRIP");

	# No album found?
	if [ -z "$IALBUM" ]; then
		local IALBUM=$($IDTAG "$FILENAME" | grep "=== TOAL" | $SED -r 's|.*: ||');
	fi
	if [ -z "$IALBUM" ]; then
		local IALBUM="UnknownAlbum";
	fi

	# No artist found?
	if [ -z "$IARTIST" ]; then
		local IARTIST="UnknownArtist";
	fi

	# No year found?
	if [ -z "$IYEAR" ]; then
		local IYEAR="0000";
	fi

	# No title found?
	if [ -z "$ITITLE" ]; then
		local ITITLE="UnknownTitle";
	fi

	# No content found?
	if [ -z "$ICONTENT" ]; then
		local ICONTENT="UnknownContent";
	else
		# well.. a try worth, if we dont have an album name
		if [[ $IALBUM == "UnknownAlbum" ]]; then
			local IALBUM="$ICONTENT";
		fi
	fi

	# yes.. known bug comes here, we cannot differ
	# between mixed cd's containing a lot of artists
	# and cds from ONE artist. at least i dont know
	# how.
	local NEWPATH="$TODIR"/"$IARTIST"/"$IYEAR"_"$IALBUM"
	if [ -z $ITRACK ]; then

		local NEWFILENAME="$ITRACK""$ITITLE".mp3
		
	else

		local NEWFILENAME="$ITITLE".mp3

	fi

	# now let's look at it
	echo "+ (Slot $SLOT): Creating $NEWPATH";
	mkdir -p $NEWPATH

	# now let's make sure that the file is not existing
	if [ -f "$NEWPATH/$NEWFILENAME" ] ; then

		# file exists let's add a number to the filename
		# and check whether that file exists too (we set
		# this number higher until we found a not existing
		# filename.
		echo "+ (Slot $SLOT): Copy detected. Renaming...";

		local CURRFILENAME=$(echo "$NEWFILENAME" | $SED -r 's|.*/||' | $CUT -d. -f1);
		local CURREXTENSION=$(echo "$NEWFILENAME" | $SED -r 's|.*/||' | $CUT -d. -f2);

		for (( I=1; $I \<= 9999; I++ )) ; do

			if [ ! -f "$NEWPATH/$CURRFILENAME.$I.$CURREXTENSION" ] ; then

				# wow, we got a free filename. Let's break the
				# for and give back the new filename
				local NEWFILENAME="$CURRFILENAME.$I.$CURREXTENSION";
				# to make sure its not overwritten by another running parallel process
				touch "$NEWPATH/$CURRFILENAME.$I.$CURREXTENSION"
				break;

			fi

		done

		echo "+ (Slot $SLOT): Renamed to: $CURRFILENAME.$I.$CURREXTENSION";

	else

		# the filename is free already.
		local NEWFILENAME="$NEWFILENAME";
		touch "$NEWPATH/$NEWFILENAME";

	fi
	echo "+ (Slot $SLOT): Moving $FILENAME to $NEWPATH/$NEWFILENAME";
	cp "$FILENAME" "$NEWPATH/$NEWFILENAME"

}

imsGetTagsOgg() {

	# first split the data we got
	local SLOT=$(echo "$@" | $CUT -d: -f1);
	local FILENAME=$(echo "$@" | $CUT -d: -f2);

	# now let's run id3info on our mp3
	local IALBUM=$($OGGTAG "$FILENAME" | grep "ALBUM=" | $SED -r 's|.*: ||');
	local IARTIST=$($OGGTAG "$FILENAME" | grep "ARTIST=" | $SED -r 's|.*: ||');
	local ICONTENT=$($OGGTAG "$FILENAME" | grep "GENRE=" | $SED -r 's|.*: ||');
	local ITITLE=$($OGGTAG "$FILENAME" | grep "TITLE=" | $SED -r 's|.*: ||');
	local IYEAR=$($OGGTAG "$FILENAME" | grep "DATE=" | $SED -r 's|.*: ||');
	local ITRACK=$($OGGTAG "$FILENAME" | grep "TRACKNUMBER=" | $SED -r 's|.*: ||');

	# Now we need to fix the stuff a bit by removing whitespaces
	local IALBUM=$(echo "$IALBUM" | tr -d "$TOSTRIP");
	local IARTIST=$(echo "$IARTIST" | tr -d "$TOSTRIP");
	local ITITLE=$(echo "$ITITLE" | tr -d "$TOSTRIP");
	local ICONTENT=$(echo "$ICONTENT" | tr -d "$TOSTRIP");
	local ITRACK=$(echo "$ITRACK" | tr -d "$TOSTRIP");
	local IYEAR=$(echo "$IYEAR" | tr -d "$TOSTRIP");

	# No album found?
	if [ -z "$IALBUM" ]; then
		local IALBUM="UnknownAlbum";
	fi

	# No artist found?
	if [ -z "$IARTIST" ]; then
		local IARTIST="UnknownArtist";
	fi

	# No year found?
	if [ -z "$IYEAR" ]; then
		local IYEAR="0000";
	fi

	# No title found?
	if [ -z "$ITITLE" ]; then
		local ITITLE="UnknownTitle";
	fi

	# No genre found?
	if [ -z "$ICONTENT" ]; then
		local ICONTENT="UnknownContent";
	else
		# well.. a try worth, if we dont have an album name
		if [[ $IALBUM == "UnknownAlbum" ]]; then
			local IALBUM="$ICONTENT";
		fi
	fi

	# yes.. known bug comes here, we cannot differ
	# between mixed cd's containing a lot of artists
	# and cds from ONE artist. at least i dont know
	# how.
	local NEWPATH="$TODIR"/"$IARTIST"/"$IYEAR"_"$IALBUM"
	if [ -z $ITRACK ]; then

		local NEWFILENAME="$ITRACK""$ITITLE".ogg
		
	else

		local NEWFILENAME="$ITITLE".ogg

	fi

	# now let's look at it
	echo "+ (Slot $SLOT): Creating $NEWPATH";
	mkdir -p $NEWPATH

	# now let's make sure that the file is not existing
	if [ -f "$NEWPATH/$NEWFILENAME" ] ; then

		# file exists let's add a number to the filename
		# and check whether that file exists too (we set
		# this number higher until we found a not existing
		# filename.
		echo "+ (Slot $SLOT): Copy detected. Renaming...";

		local CURRFILENAME=$(echo "$NEWFILENAME" | $SED -r 's|.*/||' | $CUT -d. -f1);
		local CURREXTENSION=$(echo "$NEWFILENAME" | $SED -r 's|.*/||' | $CUT -d. -f2);

		for (( I=1; $I \<= 9999; I++ )) ; do

			if [ ! -f "$NEWPATH/$CURRFILENAME.$I.$CURREXTENSION" ] ; then

				# wow, we got a free filename. Let's break the
				# for and give back the new filename
				local NEWFILENAME="$CURRFILENAME.$I.$CURREXTENSION";
				# to make sure its not overwritten by another running parallel process
				touch "$NEWPATH/$CURRFILENAME.$I.$CURREXTENSION"
				break;

			fi

		done

		echo "+ (Slot $SLOT): Renamed to: $CURRFILENAME.$I.$CURREXTENSION";

	else

		# the filename is free already.
		local NEWFILENAME="$NEWFILENAME";
		touch "$NEWPATH/$NEWFILENAME";

	fi
	echo "+ (Slot $SLOT): Moving $FILENAME to $NEWPATH/$NEWFILENAME";
	cp "$FILENAME" "$NEWPATH/$NEWFILENAME"

}

###
# this function is more a "caller" than a function.
# it calls other functions :)
###
imsProcess() {

	local SLOT=$(echo "$@" | $SED -r 's|:.*||' | $SED -r 's|Slot ||');
	local CURRFILE=$(echo "$@" | $SED -r 's|.*:||');
	echo "+ (Slot $SLOT): imsProcess(): got '$1'" >&2

	# We need to differ between the different file formats
	# so let's first split the filename to get the
	# extension
	local CURRFILENAME=$(echo "$CURRFILE" | $SED -r 's|.*/||' | $CUT -d. -f1);
	local CURREXTENSION=$(echo "$CURRFILE" | $SED -r 's|.*/||' | $CUT -d. -f2);

	if [[ "$CURREXTENSION" == "mp3" ]]; then

		# we got an mp3, let's call the needed functions
		imsGetTagsMpeg "$SLOT":"$CURRFILE"

	elif [[ "$CURREXTENSION" == "ogg" ]]; then

		# we got an ogg, let's call the needed functions
		imsGetTagsOgg "$SLOT":"$CURRFILE"

	else

		local TO="$TODIR/unknown"
		mkdir -p $TO
		# something bad happened. probably a dot in the
		# filename.

		local NEWFILENAME=$(echo "$CURRFILE" | $SED -r 's|.*/||' | tr -d "$TOSTRIP" | $CUT -d. -f1);
		# now let's make sure that the file is not existing
		if [ -f "$TO/$NEWFILENAME" ] ; then

			# file exists let's add a number to the filename
			# and check whether that file exists too (we set
			# this number higher until we found a not existing
			# filename.
			echo "+ (Slot $SLOT): Copy detected. Renaming...";

			local CURRFILENAME=$(echo "$NEWFILENAME" | $SED -r 's|.*/||' | $CUT -d. -f1);
			local CURREXTENSION=$(echo "$NEWFILENAME" | $SED -r 's|.*/||' | $CUT -d. -f2);

			for (( I=1; $I \<= 9999; I++ )) ; do

				if [ ! -f "$TO/$CURRFILENAME.$I.$CURREXTENSION" ] ; then

					# wow, we got a free filename. Let's break the
					# for and give back the new filename
					local NEWFILENAME="$CURRFILENAME.$I.$CURREXTENSION";
					# to make sure its not overwritten by another running parallel process
					touch "$TO/$CURRFILENAME.$I.$CURREXTENSION"
					break;

				fi

			done

			echo "+ (Slot $SLOT): Renamed to: $CURRFILENAME.$I.$CURREXTENSION";

		else

			# the filename is free already.
			local NEWFILENAME="$NEWFILENAME";
			touch "$TO/$NEWFILENAME";

		fi
		echo "+ (Slot $SLOT): Moving $CURRFILE to $TO/$NEWFILENAME";
		cp "$CURRFILE" "$TO/$NEWFILENAME"

	fi


}

###
# Command line .. 
###

while getopts ":f:t:p:hd" opt; do
  case $opt in
    f)

      # -f was triggered, let's check whether the directory exists.
      if [ -d "$OPTARG" ]; then
        FROMDIR="$OPTARG"
      else
        echo "-f was triggered, with parameter: $OPTARG" >&2
        echo "but the directory $OPTARG does not exist - exit" >&2
        exit 0
      fi
      ;;

    t)

      # -t was triggered
      TODIR="$OPTARG"
      ;;

    p)

      # -p was triggered
      IMSTHREADS="$OPTARG"
      ;;

    h)

      echo "Usage: audio filesorting.sh [-p X] -f [DIRECTORY] -t [DIRECTORY]"
      echo "Example: audio filesorting.sh -p 2 -f /recovered -t /sorted"
      echo "this would sort all audio files from /recovered to /sorted with"
      echo "two parallel running threads."
      echo ""
      echo "Usage:"
      echo "  -f                DIRECTORY from where to get audio files"
      echo "  -t                DIRECTORY where to store audio files"
      echo "  -p                threads 2-12 is a safe choice"
      echo "  -d                will print a lot :)"
      echo "  -h                displays this help and exit"
      echo ""
      echo "See http://jeanbruenn.info if you liked this script"
      exit 0
      ;;

    d)
      DEBUG=1
      ;;

    \?)

      echo "Invalid option: -$OPTARG try -h for help" >&2
      exit 0
      ;;

    :)

      echo "Option -$OPTARG requires an argument." >&2
      exit 0
      ;;

  esac
done

if [ -z $DEBUG ] ; then
   DEBUG=0
fi

# Check whether all required settings have been set:
if [ ! $FROMDIR ] || [ ! $TODIR ]; then
  echo "The options -f and -t are required! exit"
  exit 0
fi

if [ ! $IMSTHREADS ]; then
    # threads wasn't set, let's try autodetection
    PROCS=$(cat /proc/cpuinfo | grep "processor" | wc -l);
    if [ -z $PROCS ]; then
      IMSTHREADS=2
    else
      if (( $PROCS -eq 1 )) || (( $PROCS -eq 0 )); then
        IMSTHREADS=2
      else
        IMSTHREADS=$PROCS
      fi
    fi
fi

###
# Start everything
###

clear

echo " ============================================================================== "
echo "     audio Sorting Utility       "
echo " ============================================================================== "
echo " : started                   "

# Let's make sure that no copy of this script is running at the same
# time - Otherwise it will probably result in many broken audio files.
if mkdir /var/lock/audioSorting; then
  if [ $DEBUG -eq 1 ] ; then
    echo "+ locking succeeded" >&2
  fi
else
  echo "+ locking failed (audioSorting already running?) - exit" >&2
  exit 1
fi

# Let's get a filelist to work with
echo "+ getting filelist"
A=0
while read -r; do
  FILES[A++]="$REPLY";
done < <(find "$FROMDIR" -type f -iregex "$PATT")
NUMFILES=${#FILES[@]}

# Got the filelist - Let's start
imsMain

# Done - Finish up:
rm -rf /var/lock/audioSorting

if [ $DEBUG -eq 1 ] ; then
  echo "++ removed lock"
fi

echo " =============================== "
echo "     audio Sorting Utility       "
echo " =============================== "
echo " : finished                      "

