#!/bin/zsh

emulate -L zsh
function e2log()
{
    echo "${(j.\n.)@}" >> "${LOGFILE}"
    return $?
}
function runloggedcommand()
{
    local -r P="$1"
    shift
    e2log "$(date +"%s") ${P} {{{"
    e2log ">>> command=«${(j. .)${(q)@}}»"
    local COMMANDOUTPUT
    COMMANDOUTPUT="$( $@ 2>&1 )"
    local -ir ERRCODE=$?
    echo "${COMMANDOUTPUT}"
    e2log "${COMMANDOUTPUT}"
    e2log "}}} Returned ${ERRCODE}"
    return ${ERRCODE}
}
function e2info()
{
    echo "${(j.\n.)@}" >> "${INFOFILE}"
    local -ir ERRCODE=$?
    echo "${(j.\n.)@}"
    return ${ERRCODE}
}
function eerror()
{
    local -ir ERRCODE=$1
    shift
    echo $@ >&2
    e2log "Error: $@"
    (( ERRCODE )) && exit ${ERRCODE}
}

function GetAudio()
{
    local -a AIDLIST
    set -A AIDLIST \
            $(echo ${MIDENTIFYTEXT} | \
              grep -oP "(?<=ID_AUDIO_ID=)[[:digit:]]+")

    local AID OGGENCOUTPUT BITRATE
    typeset -ag AFNAMES
    for AID in ${AIDLIST} ; do
        set -A AFNAMES ${AFNAMES} \
              "${AID}-$(echo ${MIDENTIFYTEXT} | \
                        grep -oP "(?<=ID_AID_${AID}_LANG=).*")"

        runloggedcommand "${AFNAMES[-1]}.pcm:" \
        mplayer ${INARGS} -vc dummy -vo null -ao pcm:file="${AFNAMES[-1]}.pcm"

        (( NORMALIZE )) && {
            runloggedcommand "${AFNAMES[-1]}.pcm: normalization:" \
            normalize "${AFNAMES[-1]}.pcm"
        }

        OGGENCOUTPUT="$(LANG=C \
            runloggedcommand "Oggenc:" \
            oggenc -q${OGGQUALITY} \
                      "${AFNAMES[-1]}.pcm" \
                   -o "${AFNAMES[-1]}.ogg")" && \
        BITRATE="$(echo "${OGGENCOUTPUT}" | \
                   grep -oP "(?<=Average bitrate: ).*")" && \
        e2info "${AFNAMES[-1]}.pcm->${AFNAMES[-1]}.ogg: ${BITRATE}" && \
        rm -f "${AFNAMES[-1]}.pcm" || \
        eerror 0 "Failed to get ${AID} audio!"
    done
    typeset -r AFNAMES
    return 0
}
function GetSubs()
{
    local -a SIDLIST
    set -A SIDLIST \
            $(echo ${MIDENTIFYTEXT} | \
              grep -oP "(?<=ID_SUBTITLE_ID=)[[:digit:]]+")

    local SID
    typeset -ag SFNAMES
    for SID in ${SIDLIST} ; do
        set -A SFNAMES ${SFNAMES} \
              "${SID}-$(echo ${MIDENTIFYTEXT} | \
                        grep -oP "(?<=ID_SID_${SID}_LANG=).*")"

        runloggedcommand "${SFNAMES[-1]}:" \
        mencoder ${INARGS} -nosound \
                           -ovc frameno \
                           -o /dev/null \
                           -sid ${SID} \
                           -vobsubout ${SFNAMES[-1]}
        e2info "Sub: ${SFNAMES[-1]}"
    done
    typeset -r SFNAMES
    return 0
}
function GetVideo()
{
    local -a MENCCOMMONOPTS
    set -A MENCCOMMONOPTS tmp.raw -noskip -nosound -ovc x264 -of rawvideo
    [[ -z "${VIDEOFILTERS}" ]] || \
        MENCCOMMONOPTS=( ${MENCCOMMONOPTS} -vf "${VIDEOFILTERS}" )
    local -r MENCCOMMONOPTS
    # mplayer ${INARGS} -v -dumpvideo -dumpfile tmp.vob
    runloggedcommand "tmp.raw:" \
    mencoder ${INARGS} -noskip -nosound -ovc raw -o tmp.raw || \
        eerror 18 "Getting raw video failed!"
    if (( PASSES > 1 )) ; then
        runloggedcommand "tmp.avi: pass 1:" \
        mencoder ${MENCCOMMONOPTS} \
                -x264encopts "${X264OPTS}:turbo=1:pass=1" \
                -o /dev/null && \
        runloggedcommand "tmp.avi: pass 2:" \
        mencoder ${MENCCOMMONOPTS} \
                 -x264encopts "${X264OPTS}:pass=${PASSES}" \
                 -o /dev/null && \
        (( PASSES==3 )) && runloggedcommand "tmp.avi: pass 3" \
            mencoder ${MENCCOMMONOPTS} \
                     -x264encopts "${X264OPTS}:pass=3" \
                     -o tmp.avi
        return $?
    else
        runloggedcommand "tmp.avi:" \
        mencoder ${MENCCOMMONOPTS} -x264encopts "${X264OPTS}" -o tmp.avi
        return $?
    fi
}
function GetMKV()
{
    GetAudio
    GetSubs
    GetVideo || eerror 18 "Failed to get video!"
    local -a MKVOPTS
    set -A MKVOPTS -o "${FNAME}" tmp.avi
    local SUB
    for SUB in ${SFNAMES} ; do
        MKVOPTS=( ${MKVOPTS} "${SUB}.idx" )
    done
    local AF
    for AF in ${AFNAMES} ; do
        MKVOPTS=( ${MKVOPTS} --language "0:${AF##*-}" "${AF}.ogg" )
    done
    runloggedcommand "${FNAME}:" \
    mkvmerge ${MKVOPTS}
    local F
    for F in ${SFNAMES} ; do
        rm "${F}.idx" "${F}.sub"
    done
    for F in ${AFNAMES} ; do
        rm "${F}.ogg"
    done
    rm tmp.avi tmp.raw divx2pass.log
}
function Usage()
{
    local PRSPACES="$(printf "%${#PROGNAME}s" "")"
    echo "\
${PROGNAME} [-l logfile] [-i infofile] [-o outputfile]
${PRSPACES} [-q oggquality] [-Q qp] [-C crf] [-n] [-1 | -2 | -3]
${PRSPACES} [-m x264encopts] [-a x264opts]
${PRSPACES} [-f videofilters] [-F videofilters]
${PRSPACES} [--] source

    -l logfile
        Установить файл, в который будет записаны выводы всех команд, результат 
        выполенния, время запуска и сами команды. Значение по умолчанию 
        определяется из имени программы и источника видео.

    -i infofile
        Установить файл, в который будет записана некоторая информация 
        о перекодированном видео.

    -o outputfile
        Файл mkv с перекодированным видео.

    -q oggquality
        Качество кодирования звука. См. ключ -q в man oggenc.

    -Q qp, -C crf
        Качество кодирования видео.
        См. man mplayer -> x264encopts -> опции qp и crf

    -n
        Не делать нормализацию звука.

    -1, -2, -3
        Кодировать в один, два или три прохода соответственно.

    -m x264encopts
        Использовать x264encopts в качестве настроек x264.
        Значение по умолчанию; ${X264OPTS}

    -a x264encopts
        Добавить x264encopts к существующим настройкам.

    -F videofilters, -f videofilters
        Добавить видеофильтры до или после существующих видеофильтров 
        соответственно.
"
    exit 0
}
function ParseArgs()
{
    typeset -g  LOGFILE="" INFOFILE="" FNAME=""
    typeset -gi NORMALIZE=1 PASSES=2 OGGQUALITY=6 QP=-1
    typeset -gF CRF
    typeset -ga INARGS
    typeset -T X264OPTS x264opts ":"
    # threads=auto
    # psnr
    # # ssim
    # # b_adapt=2 (?)
    # # deblock (default)
    # # cabac   (default)
    # # chroma_me (default)
    # # subq=9
    # partitions=all
    # 8x8dct
    # me=umh
    # # nofast_pskip
    # # nodct_decimate
    # # me_range=32
    # # trellis=2
    # # frameref=16
    # # bframes=16
    # b_pyramid
    # weight_b
    # mixed_refs
    # direct_pred=auto
    # # keyint=750
    # # keyint_min=25 (default)
    # # direct_8x8=-1 (??)
    # # aq_mode=1
    # # aq_strength=1.0
    # # psy_rd=1.0,0.0 (default)

# subq 6->9
# bframes 4->16
# frameref 6->16
# trellis 1->2
# +deblock
# +cabac
# +aq_mode
# +aq_strength
# +chroma_me
# +me_range
# +nofast_pskip
# +nodct_decimate
# +keyint
# +keyint_min
# +psy_rd
# +b_adapt
# +direct_8x8
    set -A x264opts "subq=9" \
               "bframes=16"   \
               "b_pyramid"     \
               "weight_b"       \
               "trellis=2"       \
               "partitions=all"   \
               "8x8dct"            \
               "me=umh"             \
               "me_range=32"         \
               "frameref=16"          \
               "mixed_refs"            \
               "direct_pred=auto"       \
               "threads=auto"            \
               "deblock"                  \
               "cabac"                     \
               "aq_mode=1"                  \
               "aq_strength=1.0"             \
               "chroma_me"                    \
               "nofast_pskip"                  \
               "nodct_decimate"                 \
               "keyint=750"                      \
               "keyint_min=25"                    \
               "psy_rd=1.0,0.0"                    \
               "b_adapt=2"                          \
               "direct_8x8=-1"                       \
               "psnr" "ssim"
    typeset -T VIDEOFILTERS videofilters ","

    for (( I=1 ; I<=${#@} ; I++ )) ; do
        arg=${@[I]}
        case ${arg} in
            "-n")    NORMALIZE=0 ;;
            "-q")   OGGQUALITY="${@[++I]}" ;;
            "-l")      LOGFILE="${@[++I]}" ;;
            "-i")     INFOFILE="${@[++I]}" ;;
            "-Q")         { QP="${@[++I]}" } always \
                              { (( TRY_BLOCK_ERROR )) && \
                                  eerror 8 "QP must be integer!" } ;;
            "-C")        { CRF="${@[++I]}" } always \
                              { (( TRY_BLOCK_ERROR )) && \
                                  eerror 9 "CRF must be float!" } ;;
            "-m")     X264OPTS="${@[++I]}" ;;
            "-o")        FNAME="${@[++I]}" ;;
            "-a")     x264opts=( ${x264opts} "${@[++I]}" )     ;;
            "-f") videofilters=( ${videofilters} "${@[++I]}" ) ;;
            "-F") videofilters=( "${@[++I]}" ${videofilters} ) ;;
            "-1")       PASSES=1 ;;
            "-2")       PASSES=2 ;;
            "-3")       PASSES=3 ;;
            "-h") Usage ;;
            "--") (( I++ )) ; break ;;
               *)             break ;;
        esac
    done
    INARGS=( ${@[I,-1]} )

    [[ -z "${INARGS}" ]] && eerror 1 "No input!"

    [[ -z "${FNAME}"  ]] && FNAME="${INARGS//\//\\}.x264.mkv"

    (( -1<=OGGQUALITY && OGGQUALITY<=10 )) || \
        eerror 2 "OGGQUALITY must be a number between -1 and 10!"

    [[ -z "${LOGFILE}" ]] && LOGFILE="${PROGNAME//\//\\}-${INARGS//\//\\}.log"
    echo > "${LOGFILE}" || \
        eerror 3 "Cannot write to ${LOGFILE}!"

    [[ -z "${INFOFILE}" ]] && INFOFILE="${PROGNAME//\//\\}-${INARGS//\//\\}.log"
    echo -n > ${INFOFILE}  || eerror 4 "Cannot write to ${INFOFILE}!"

    (( QP == -1 )) || { \
        (( QP >= 0 && QP <= 51 )) || \
            X264OPTS="${X264OPTS}:qp=${QP}" || \
                eerror 5 "QP must be a number between 0 and 51!" }

    (( CRF == 0.0 )) || { \
        (( CRF >= 1.0 && CRF <= 50.0 )) || \
            X264OPTS="${X264OPTS}:crf=${CRF}" || \
                eerror 6 "CRF must be a number between 1.0 and 50.0!" }

    echo -n > ${FNAME} || eerror 7 "Cannot write to ${FNAME}!"

    typeset -gr QP       \
                CRF       \
                OGGQUALITY \
                PASSES      \
                NORMALIZE    \
                x264opts      \
                videofilters   \
                LOGFILE         \
                INFOFILE         \
                FNAME             \
                INARGS
    e2log "$(date +"%s") ${PROGNAME} started with arguments ${(j. .)${(q)@}}."
    e2log "Options: {{{"                   \
          "         CRF=${CRF}"             \
          "          QP=${QP}"               \
          "  OGGQUALITY=${OGGQUALITY}"        \
          "   NORMALIZE=${NORMALIZE}"          \
          "    X264OPTS=${X264OPTS}"            \
          "       FNAME=${FNAME}"                \
          "VIDEOFILTERS=${VIDEOFILTERS}"          \
          "      PASSES=${PASSES}"                 \
          "     LOGFILE=${(q)${LOGFILE//\\/\\\\}}"  \
          "    INFOFILE=${(q)${INFOFILE//\\/\\\\}}"  \
          "      INARGS="${(j. .)${(q)INARGS}}        \
          "}}}"

    typeset -gr \
        MIDENTIFYTEXT="$(runloggedcommand "Midentify:" midentify ${INARGS})" || \
        eerror 17 "Failed to run midentify!"

    GetMKV
    e2log "$(date +"%s") END"
}
typeset -rg PROGNAME="$0"
ParseArgs $@
