#!/bin/sh
# lang2voice
#
# Note: You may wish to change some of the settings below.
#
# A script to automatically generate an audio .voice file from a Rockbox
# localization file (.lang). It first produces wav clips containing the texts
# of all voiced entries in the .lang file and converts them into MP3 format
# with the help of a third party application (lame). The resulting mp3 files
# are then assembled into a .voice file using the voicefont utility.
#
# A log of all the commands executed by the script is written to the file
# <filename>.log, which can later be executed as a shell script to "replay"
# all the commands
#
# NOTE: If you don't already have them installed, you may obtain the Festival
# text to speech system and several voices at:
#
# http://www.cstr.ed.ac.uk/projects/festival.html
# http://festvox.org/festival/
#
# The most pleasant freely available Festival voice I know of is the slt_arctic
# voice from HST at http://hts.ics.nitech.ac.jp/

# whether to overwrite existing mp3 files or only create missing ones (Y/N)
OVERWRITE_MP3=Y
# whether, when overwriting mp3 files, also to regenerate all the wav files
OVERWRITE_WAV=N
# whether to remove the intermediary wav files after creating the mp3 files
REMOVE_WAV=N
# whether to remove the intermediary mp3 files after creating the voice file
REMOVE_MP3=N

# the directory where the Festival binaries are installed
FESTIVAL_DIR=/home/daniel/speech/festival/bin
# whether to start the Festival server locally (Y/N)
FESTIVAL_START=Y
# the Festival server configuration file
FESTIVAL_CONFIG=$FESTIVAL_DIR/defaults.scm
# the host of the Festival server
# this is set to localhost automatically when FESTIVAL_START is Y
FESTIVAL_HOST=localhost
# the port of the Festival server
FESTIVAL_PORT=1314
# where to log the Festival client output
FESTIVAL_LOG=/dev/null

# lame encoding options: VBR, independent frames
# Tune the -V value to get ~1.5 MB output, remove --nores if -V 9 is still too big
# If this is still too big, you'll have to resample to a lower sample rate
LAME_OPTS="--resample 32 -V 9 -t --vbr-new --nohist"
# where to log the lame output
LAME_LOG=/dev/null

# the file into which all the commands are logged
LOGFILE=`basename $0 sh`log
echo "#!/bin/sh" >> $LOGFILE

if [ X$FESTIVAL_START = XY ]
then
  FESTIVAL_HOST=localhost
  if [ -z "$FESTIVAL_CONFIG" ]
  then
    echo "$FESTIVAL_DIR/festival_server -p $FESTIVAL_PORT $FESTIVAL_DIR/festival &" >> $LOGFILE
    $FESTIVAL_DIR/festival_server -p $FESTIVAL_PORT $FESTIVAL_DIR/festival &
  else
    echo "$FESTIVAL_DIR/festival_server -p $FESTIVAL_PORT -c $FESTIVAL_CONFIG $FESTIVAL_DIR/festival &">> $LOGFILE
    $FESTIVAL_DIR/festival_server -p $FESTIVAL_PORT -c $FESTIVAL_CONFIG $FESTIVAL_DIR/festival &
  fi
  echo sleep 5 >> $LOGFILE
  sleep 5
fi

# needs two arguments, $1 is the id string, $2 is the text to speak
function createWav() {
  TO_SPEAK=$2
  WAV_FILE=$1.wav
  MP3_FILE=$1.mp3
  echo "# id=$1 string=$2" >> $LOGFILE
  if [ X$OVERWRITE_MP3 = XY -o ! -f $MP3_FILE ]
  then
    if [ X$OVERWRITE_WAV = XY -o ! -f $WAV_FILE ]
    then
      echo "echo $TO_SPEAK | $FESTIVAL_DIR/festival_client --server $FESTIVAL_HOST --port $FESTIVAL_PORT --otype riff --output $WAV_FILE --ttw >>$FESTIVAL_LOG 2>&1" >> $LOGFILE
      echo $TO_SPEAK | $FESTIVAL_DIR/festival_client \
        --server $FESTIVAL_HOST --port $FESTIVAL_PORT \
        --otype riff --output $WAV_FILE --ttw >>$FESTIVAL_LOG 2>&1
      if [ -f $WAV_FILE ]
      then
        echo "`dirname $0`/wavtrim $WAV_FILE" >> $LOGFILE
        `dirname $0`/wavtrim $WAV_FILE
      fi
    fi
    if [ -f $WAV_FILE ]
    then
      echo "lame $LAME_OPTS $WAV_FILE $MP3_FILE >>$LAME_LOG 2>&1" >> $LOGFILE
      lame $LAME_OPTS $WAV_FILE $MP3_FILE >>$LAME_LOG 2>&1
    else
      echo "# warning: missing $WAV_FILE" >> $LOGFILE
    fi
  fi
  if [ X$REMOVE_WAV = XY -a -f $WAV_FILE ]
  then
    echo rm $WAV_FILE >> $LOGFILE
    rm $WAV_FILE
  fi
}

function readLang() {
  exec 3<$1
  while read -r -u3;
  do
    FIRSTCHAR=`echo $REPLY | cut -c1`
    if [ X${FIRSTCHAR} = X\# ]
    then
      # comment
      continue
    fi
    ID_PREFIX=`echo $REPLY | cut -c1-4`
    if [ "${ID_PREFIX}" = "id: " ]
    then
      LANG_ID=`echo $REPLY | cut -c5-`
      continue
    fi
    VOICE_PREFIX=`echo $REPLY | cut -c1-8`
    if [ "${VOICE_PREFIX}" = "voice: \"" ]
    then
      VOICE_LENGTH=`echo -n $REPLY | wc -c`
      VOICE_LENGTH=`expr $VOICE_LENGTH - 1`
      if [ $VOICE_LENGTH -gt 8 ]
      then
        LANG_STRING=`echo $REPLY | cut -c9-$VOICE_LENGTH`
        createWav $LANG_ID "$LANG_STRING"
      fi
    fi
  done
}

if [ $# -gt 0 ]
then
  for i in $*
  do
    readLang $i
    echo `dirname $0`/voicefont $1 ./ voicefont.bin >> $LOGFILE
    `dirname $0`/voicefont $1 ./ voicefont.bin
    if [ X$REMOVE_MP3 = XY ]
    then
      echo rm LANG_*.mp3 VOICE*.mp3 >> $LOGFILE
      rm LANG_*.mp3 VOICE*.mp3
    fi
  done
else
  echo Usage: lang2wav \<path to .lang file\>
fi

if [ X$FESTIVAL_START = XY ]
then
  echo $FESTIVAL_DIR/festival_server_control -p $FESTIVAL_PORT exit >> $LOGFILE
  $FESTIVAL_DIR/festival_server_control -p $FESTIVAL_PORT exit
  echo rm festival_wrapper_pid festival_sleep_pid festival_server.scm festival_server.log >> $LOGFILE
  rm festival_wrapper_pid festival_sleep_pid festival_server.scm festival_server.log
fi

