#!/bin/bash
# Copyright (C) Dragon.Studio
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# ogg_wav Version 1.0
#
# This script can either extract Ogg stream from Ogg Vorbis WAV files
# or convert Ogg Vorbis WAV to uncompressed WAV files. Run ogg_wav -h
# for more info.

OggS_limit=2 # Maximum number of Ogg pages to process before giving up. Ideally this limit should not be reached as long as we find soon enough Ogg page which moggsplit accepted without complaining, but it practice some file may reach it, and moggsplit may unpack the same data up to $OggS_limit times, so do not set it too high.


# Random name for temporary files
generate_tmp_name() {
  tmp="ogg_wav_$(echo $@ | sha1sum | cut -d' ' -f1)_$$"
}
generate_tmp_name
# In very unlikely case we generate name similar to already existing file(s) try again and again...
until [[ -z "$(ls "$tmp"*.ogg* 2> /dev/null)" ]]; do
  generate_tmp_name; done

help() {
  echo "ogg_wav -[cedkslv] file1 [file2] ..."
  echo "ogg_wav -[h?]"
  echo "-h or -? will print this help."
  echo "-d Delete original WAV file"
  echo "-s Ignore non-Ogg WAV file(s) silently"
  echo "-v Print successfully processed file(s) and warnings"
  echo "-k Keep original WAV file (the default)"
  echo "-e Extract Ogg Vorbis stream; in other words, convert from .wav to .ogg (this is the default if no options provided)"
  echo "-l Create symbolic link from .ogg to original .wav (only useful with -e option); good idea to use this with -d"
  echo "-c to convert from Ogg Vorbis WAV to uncompressed WAV, for example:"
  echo "    ogg_wav -cd ogg_sample.wav"
  echo "...will replace Ogg Vorbis ogg_sample.wav with uncompressed .wav of the same name."
  echo "To losslessly extract Ogg Vorbis stream to .ogg file and delete original .wav:"
  echo "    ogg_wav -ed ogg_sample.wav"
  echo "To losslessly extract Ogg Vorbis stream to .ogg and keep original .wav:"
  echo "    ogg_wav -e ogg_sample.wav"
  echo "To process many files recursively it is recommended to use GNU parallel, for example to convert all Ogg Vorbis .wav to .ogg and replace original .wav with symlink to .ogg:"
  echo "    find DIRECTORY -type f -iname '*.wav' | parallel ogg_wav -sveld '{}'"
}

if [[ -z "$1" ]]; then
  help
  exit 0; fi

to_ogg=1 # convert to PCM .WAV if false, convert (losslessly) to .OGG if true
if [[ "extract" == "$(echo "$0" | perl -pe 's/.*(extract)/$1/')" ]]; then
  to_ogg=1; fi
delete_orig=0 # If true will delete original file, if false will keep original .WAV if converting to .OGG or rename it to .bak otherwise
verbose=0
link_to_orig=0
silently_ignore_non_ogg_wav=0
OPTIND=1

while getopts "h?cedkslv" opt; do
  case "$opt" in
  h|\?)
    help
    exit 0
    ;;
  c)
    to_ogg=0
    ;;
  e)
    to_ogg=1
    ;;
  d)
    delete_orig=1
    ;;
  k)
    delete_orig=0
    ;;
  s)
    silently_ignore_non_ogg_wav=1
    ;;
  l)
    link_to_orig=1
    ;;
  v)
    verbose=1
    ;;
  esac
done

shift $(($OPTIND-1))

if (type sha512sum &> /dev/null && \
    type readlink &> /dev/null && \
    type avconv &> /dev/null && \
    type ogginfo &> /dev/null && \
    type moggsplit &> /dev/null && \
    type perl &> /dev/null); then test
else
  echo "This script needs sha1sum, readlink, diff, avconv, ogginfo, moggsplit and perl. Some or all of them currently not installed. Usually they are part of coreutils, realpath, libav-tools, vorbis-tools, python-mutagen and perl packages."
  if (type apt-get &> /dev/null); then
    echo "Please allow me to use apt-get to install them for you."
    sudo apt-get install coreutils diffutils libav-tools vorbis-tools python-mutagen perl
  else
    echo "Please install them and try again."; fi; fi

trap ctrl_c INT
ctrl_c() {
  if [[ -e "$orig_noext.bak" ]]; then
    mv "$orig_noext.bak" "$orig"; fi
  rm -f "$tmp"*ogg
  exit 7
}

for orig; do
  if [[ -z "$(ogginfo "$orig" | grep "No Ogg data found")" ]]; then
    orig_folder="$(readlink -e "$orig" | perl -0777pe 's|(.*)/.*|$1|s')"
    orig_noext="$(echo -n "$orig" | perl -pe 's/\.wav$//i')"
    if [[ ! -e "$orig" ]]; then
      echo "ERROR: Source file does not exist: $orig"
      exit 1; fi
    if (touch "$tmp" && rm -f "$tmp" && mv "$orig" "$orig_noext.bak"); then test
    else
      echo "ERROR: No write permissions or file name too long (must be 4 bytes less than maximum file name length for files without .wav extension). This file was NOT processed: $orig";
      exit 2; fi

    c=1
    cat "$orig_noext.bak" | perl -p0777e 's/.*?(OggS)/$1/s' > "$tmp.$c.ogg"

    while ! moggsplit "$tmp.$c.ogg" &> /dev/null; do
      cat "$tmp.$c.ogg"  | perl -p0777e 's/OggS.*?(OggS)/$1/s' > "$tmp.$(($c+1)).ogg"
      if diff "$tmp.$(($c+1)).ogg" "$tmp.$c.ogg" &> /dev/null; then
        break; fi
      rm -f "$tmp.$c.ogg";
      c=$(($c+1));
      if [[ $c -ge $OggS_limit && $verbose == 1 ]]; then
        echo "WARNING: OggS_limit $OggS_limit was reached (probably too many small Ogg pages) on this file: $orig";
        break; fi; done

    find "$tmp".*-*.ogg -print0 | xargs -0 -IOGG bash -c 'oggdec "OGG" -Qo /dev/null &> /dev/null || rm -f "OGG"' # In some weird cases garbage OGG streams may be larger than playable OGG stream(s), so make sure we have only good streams before next step. We use oggdec and not ogginfo because ogginfo may return non-zero code on playable streams

    ogg="$(du -a "$tmp".*-*.ogg 2> /dev/null | sort -nr | cut -f2- | head -n1)" # Assume we need only largest OGG stream

    if [[ -z "$ogg" ]]; then
      echo "FAILED (all OGG streams are corrupted or unsupported by ogg_wav): $orig"
      mv "$orig_noext.bak" "$orig"
      rm -f "$tmp"*ogg
      exit 5; fi

    # Here we use avconv even if later will not need resulting WAV to double check that we really got good OGG stream. This is necessary check because oggdec sometimes returns 0 on "bad" streams.
    avconv -loglevel quiet -y -i "$ogg" "$ogg.wav" || {
      echo "FAILED (extracted Ogg stream is corrupted or not supported by avconv): $orig"
      mv "$orig_noext.bak" "$orig"
      rm -f "$tmp"*ogg*
      exit 4
    }
    if [[ $to_ogg == 1 ]]; then
      rm -f "$ogg.wav"
      mv "$ogg" "$orig_noext.ogg"
      if [[ $link_to_orig == 1 ]]; then
        cd "$orig_folder"
        ln -s "$(basename "$orig_noext.ogg")" "$(basename "$orig")"
        cd - &> /dev/null; fi
      if [[ $delete_orig == 1 ]]; then
        rm -f "$orig_noext.bak";
      elif [[ $link_to_orig == 0 ]]; then
         mv "$orig_noext.bak" "$orig"; fi;
    else
      mv "$ogg.wav" "$orig"
      if [[ $delete_orig == 1 ]]; then
        rm -f "$orig_noext.bak"; fi; fi
    rm -f "$tmp"*ogg;
    if [[ $verbose == 1 ]]; then
      echo "PROCESSED: $orig"; fi
  else
    if [[ $silently_ignore_non_ogg_wav == 0 ]]; then
      echo "ERROR: no Ogg stream found in $orig"; fi
    exit 3; fi; done
