#!/bin/bash

# dependencies: dvrescue, xmlstarlet

_usage(){
  cat <<EOF
dvpackager

Rewrap a DV stream. This script is part of the dvrescue project.

Usage:
dvpackager [options] file.dv [file2.dv file3.dv file4.dv]

Options:

 By default, dvpackager will split the output files so that each time
 significant technical characteristics of the dv stream change (such as aspect
 ratio, frame rate, audio channel count, or audio sample rate) a new output file
 will be written. The following flags adjust the way dvpackager will split the
 output.

 -f       (forces dvpackager to ignore changes in significant technical
           characteristics of the dv stream when splitting the output)
 -s       (split the output file at recording start markers)
 -d       (split the output file at non-consecutive recording timestamps)
 -t       (split the output file at non-consecutive timecode values)

 -e <ext> (specify the extension of the container to use. Tested with:
           mkv, mov. Defaults to mkv.)
 -S       (enables technical subtitle track to show timecode,
           recording time, and dv error data and a caption track if
           there are captions to represent in the source DV)
 -n       (do not repackage, simply generate a dvrescue xml if one doesn't
           already exist, and report on what the output files would be)
 -v       (shows ffmpeg stderr output, otherwise this is hidden)

 For example, the following command:

 dvpackager -t INPUT.dv

 will read INPUT.dv and rewrap those dv frames into an output file while making
 one new output file whenever there is a change in significant technical
 characteristics or a non-consecutive timecode value within INPUT.dv.

 dvpackager also has an 'unpackage' mode

 -u       (export the dv stream from each provided file into a single dv stream)

 For example, the following command:

 dvpackager -u INPUT_1.mkv INPUT_2.mkv INPUT_3.mkv

 will create one dv stream that contains all the DV of each input file.

EOF
}
if [ "${#}" = 0 ] ; then
  _usage
  exit 0
fi

_maketemp(){
    mktemp -q "/tmp/$(basename "${0}").XXXXXX"
    if [ "${?}" -ne 0 ]; then
        echo "${0}: Can't create temp file, exiting..."
        _writeerrorlog "_maketemp" "was unable to create the temp file, so the script had to exit."
        exit 1
    fi
}

_get_ranges(){
    if [[ -n "${1}" ]] ; then
        RANGE_MATCH="${1}"
    else
        RANGE_MATCH="${MATCH_FRAMES}"
    fi
    xmlstarlet sel -N "d=https://mediaarea.net/dvrescue" -t -m "${RANGE_MATCH}" \
        -v "@pts" -o "|" \
        -v "parent::d:frames/@end_pts" -o "|" \
        -v "@tc" -o "|" \
        -v "@rdt" -o "|" \
        -v "parent::d:frames/@size" -o "|" \
        -v "parent::d:frames/@video_rate" -o "|" \
        -v "parent::d:frames/@chroma_subsampling" -o "|" \
        -v "parent::d:frames/@aspect_ratio" -o "|" \
        -v "parent::d:frames/@audio_rate" -o "|" \
        -v "parent::d:frames/@channels" -o "|" \
        -v "@rec_start" -o "|" \
        -v "@rdt_nc" -o "|" \
        -v "@tc_nc" -o "|" \
        -v "count(../preceding-sibling::d:frames)" -o "|" \
        -v "count(preceding-sibling::d:frame)" -o "|" \
        -v "parent::d:frames/@pkt_pos" -o "|" \
        -v "$FILESIZE" -n "${DVRESCUE_XML}" |\
        awk  -F "|" 'BEGIN{OFS="|";}{if(end_pts){print $1,R,$16};pts=$1;end_pts=$2;pkt_pos=$16;R=$0}END{print end_pts,R,$17}'
}

_ranges_2_table(){
    RANGES="${1}"
    echo
    echo "|     # | Seq | PTS Range                         | Timecode    | Recording Timestamp | Size      | Fr Rate    | AsRat | ChSub | SampF | Ch.s | Start Rec Flag | TC Jump | Rec Time Jump | IsFirst |"
    echo "${RANGES}" | awk -F "|" '{END_PTS=$1; PTS=$2; SEQ_END_PTS=$3; TC=$4; RDT=$5; SIZE=$6; VIDEO_RATE=$7; CH_SUB=$8; AR=$9; AUDIO_RATE=$10; CH=$11; REC_ST=$12; RDT_NC=$13; TC_NC=$14; SEQ_NO=$15; FRAME_NO_IN_SEQ=$16; \
        if(FRAME_NO_IN_SEQ == "0") FIRST="1" ; else FIRST="";}\
        {printf "| %5i | %3i | %15s - %15s | %11s | %19s | %9s | %10s | %5s | %5s | %5i | %4s | %14s | %7s | %13s | %7s |\n", \
        NR, SEQ_NO, PTS, END_PTS, TC, RDT, SIZE, VIDEO_RATE, AR, CH_SUB, AUDIO_RATE, CH, REC_ST, TC_NC, RDT_NC, FIRST }'
    echo
}

_convert_hhmmssmmm2s(){
    TS="${1}"
    H="$(echo "${TS}" | cut -d ":" -f1)"
    M="$(echo "${TS}" | cut -d ":" -f2)"
    S="$(echo "${TS}" | cut -d ":" -f3)"
    awk "BEGIN {print $H * 60 * 60 + $M * 60 + $S}"
}

_duration_from_pts_range(){
    START="${1}"
    END="${2}"
    START_SEC="$(_convert_hhmmssmmm2s "${START}")"
    END_SEC="$(_convert_hhmmssmmm2s "${END}")"
    awk "BEGIN {print $END_SEC - $START_SEC}"
}

_make_chapter_metadata_file(){
    echo ";FFMETADATA1"
    _get_ranges "d:dvrescue/d:media/d:frames/d:frame[1]|d:dvrescue/d:media/d:frames/d:frame[@rec_start='1']|d:dvrescue/d:media/d:frames/d:frame[@rdt_nc='1']|d:dvrescue/d:media/d:frames/d:frame[@tc_nc='1']" | \
    while IFS="|" read PTS_END PTS_START FRAMES_PTS_END TC RECDATE SIZE VIDEO_RATE CHROMA ASPECT AUDIO_RATE CHANNELS REC_START RDT_NC TC_NC SEQ_NO FRAME_IN_SEQ START_OFFSET FILESIZE END_OFFSET ; do
         START_CHAPTER="$(_convert_hhmmssmmm2s "${PTS_START}" | awk '{printf "%i", $1 * 100000}')"
         END_CHAPTER="$(_convert_hhmmssmmm2s "${PTS_END}" | awk '{printf "%i", $1 * 100000}')"
         echo "[CHAPTER]"
         echo "TIMEBASE=1/100000"
         echo "START=${START_CHAPTER}"
         echo "END=${END_CHAPTER}"
         echo "title=${TC} - ${RECDATE}"
    done
}

MATCH_FRAMES="d:dvrescue/d:media/d:frames/d:frame[1]|"
FFMPEG_VERBOSE=(-v 0)
REPORT_ONLY="N"
EXT="mkv"
SUBS="N"
FRAMES_SPLIT_ONLY="Y"

# command-line options to set media id and original variables
OPTIND=1
while getopts ":fsdte:Snvuh" opt ; do
  case "${opt}" in
    f) MATCH_FRAMES="" ;;
    s) MATCH_FRAMES+="d:dvrescue/d:media/d:frames/d:frame[@rec_start='1']|" ; FRAMES_SPLIT_ONLY="N" ;;
    d) MATCH_FRAMES+="d:dvrescue/d:media/d:frames/d:frame[@rdt_nc='1']|" ; FRAMES_SPLIT_ONLY="N" ;;
    t) MATCH_FRAMES+="d:dvrescue/d:media/d:frames/d:frame[@tc_nc='1']|" ; FRAMES_SPLIT_ONLY="N" ;;
    e) EXT="${OPTARG}" ;;
    n) REPORT_ONLY="Y" ;;
    S) SUBS="Y" ;;
    v) unset FFMPEG_VERBOSE ;;
    u) UNPACKAGER="Y" ;;
    h) _usage ; exit 0 ;;
    :) echo "Option -${OPTARG} requires an argument" ; _usage ; exit 1 ;;
    *) echo "bad option -${OPTARG}" ; _usage ; exit 1 ;;
  esac
done
shift "$((OPTIND-1))"
MATCH_FRAMES="${MATCH_FRAMES%?}"

if [[ "${UNPACKAGER}" == "Y" ]] ; then
    OUTPUTNAME="unpackaged_$(uuidgen).dv"
    echo "Unpackaging mode. Unpackaging the input files into ${OUTPUTNAME}."
    for i in "${@}" ; do
        ffmpeg -nostdin "${FFMPEG_VERBOSE[@]}" -i "$i" -map 0:v:0 -c copy -f rawvideo - >> "${OUTPUTNAME}"
    done
    exit
fi

OPT_INPUT=(-y)
OPT_INPUT+=(-nostdin)
OPT_INPUT+=(-hide_banner)
OPT_OUTPUT+=(-map 0)
OPT_OUTPUT+=(-c:v copy)
OPT_OUTPUT+=(-c:a copy)
case "${EXT}" in
    "mov")
        FORMAT="mov"
        EXTENSION="mov"
        ;;
    "mkv")
        FORMAT="matroska"
        EXTENSION="mkv"
        ;;
    *)
        _report -w "Error: ${EXT} is an invalid extension option."
        _usage
        exit 1
        ;;
esac
OPT_OUTPUT+=(-f "$FORMAT")

while [[ "${@}" != "" ]] ; do
    unset OUTPUTOPTS
    DVFILE="${1}"
    BASENAME="$(basename "${DVFILE}")"
    SOURCE_EXT="${BASENAME##*.}"
    FILESIZE="$(ls -l "${DVFILE}" | awk '{print $5}')"
    SIDECAR_DIR="${DVFILE}_dvrescue"
    DVRESCUE_XML_TEMP="$(_maketemp)"
    DVRESCUE_XML="${SIDECAR_DIR}/${BASENAME}.dvrescue.xml"
    DVRESCUE_TECHSUBS_TEMP="$(_maketemp).vtt"
    DVRESCUE_TECHSUBS="${SIDECAR_DIR}/${BASENAME}.dvrescue.techsubs.vtt"
    DVRESCUE_SCC_TEMP="$(_maketemp).scc"
    DVRESCUE_SCC="${SIDECAR_DIR}/${BASENAME}.dvrescue.scc"
    DVRESCUE_SCC_VTT="${SIDECAR_DIR}/${BASENAME}.dvrescue.scc.vtt"
    shift
    if [[ -f "${DVFILE}" ]] ; then
        if [[ "${REPORT_ONLY}" = "Y" ]] ; then
            echo -n "Analyzing ${BASENAME}"
        else
            echo -n "Packaging ${BASENAME}"
        fi
        # check if the dvrescue xml is already made
        if [[ ! -f "${DVRESCUE_XML}" ]] ; then
            echo -n ", making dvrescue xml file"
            if [[ "${SUBS}" = "Y" ]] ; then
                OUTPUTOPTS+=(--webvtt-output "${DVRESCUE_TECHSUBS_TEMP}")
                OUTPUTOPTS+=(--cc-format scc)
                OUTPUTOPTS+=(--cc-output "${DVRESCUE_SCC_TEMP}")
            fi
            dvrescue "${DVFILE}" "${OUTPUTOPTS[@]}" --xml-output "${DVRESCUE_XML_TEMP}"
            xmlstarlet sel -N "d=https://mediaarea.net/dvrescue" -t -m "/d:dvrescue/d:media" -v "@ref" -n "${DVRESCUE_XML_TEMP}"
            if [[ -z "$(xmlstarlet sel -N "d=https://mediaarea.net/dvrescue" -t -m "/d:dvrescue/d:media" -v "@ref" -n "${DVRESCUE_XML_TEMP}")" ]] ; then
                echo ". This first video codec of ${BASENAME} is not DV, skipping."
                continue
            else
                # check if the sidecar directory is there
                if [[ ! -d "${SIDECAR_DIR}" ]] ; then
                    mkdir -p "${SIDECAR_DIR}"
                fi
                mv "${DVRESCUE_XML_TEMP}" "${DVRESCUE_XML}"
                if [[ -s "${DVRESCUE_TECHSUBS_TEMP}" ]] ; then
                    mv "${DVRESCUE_TECHSUBS_TEMP}" "${DVRESCUE_TECHSUBS}"
                fi
                if [[ -s "${DVRESCUE_SCC_TEMP}" ]] ; then
                    mv "${DVRESCUE_SCC_TEMP}" "${DVRESCUE_SCC}"
                    ffmpeg -nostdin -v 0 -i "${DVRESCUE_SCC}" "${DVRESCUE_SCC_VTT}"
                fi
            fi
        fi
        _ranges_2_table "$(_get_ranges)"
        #DVRESCUE_VERSION="$(xmlstarlet sel -N "d=https://mediaarea.net/dvrescue" -t -v "d:dvrescue/d:creator/d:program" -o "-" -v "d:dvrescue/d:creator/d:version" -n "${DVRESCUE_XML}")"
        #MUXER="${LAVF_VERSION} + ${DVRESCUE_VERSION}"
        if [[ "$REPORT_ONLY" != "Y" ]] ; then

            case "${EXT}" in
                "mov")
                    if [[ -s "${DVRESCUE_TECHSUBS}" ]] ; then
                        OPT_OUTPUT+=(-map 1)
                        OPT_OUTPUT+=(-c:s mov_text)
                        OPT_OUTPUT+=(-metadata:s:s:0 "title=dvrescue techsubs")
                        OPT_OUTPUT+=(-metadata:s:s:0 "language=zxx")
                    fi
                    if [[ -s "${DVRESCUE_SCC_VTT}" ]]; then
                        OPT_OUTPUT+=(-map 2)
                        OPT_OUTPUT+=(-c:s mov_text)
                        OPT_OUTPUT+=(-metadata:s:s:1 "title=Captions")
                        OPT_OUTPUT+=(-metadata:s:s:1 "language=und")
                    fi
                    ;;
                "mkv")
                    if [[ -s "${DVRESCUE_TECHSUBS}" ]] ; then
                        OPT_OUTPUT+=(-map 1)
                        OPT_OUTPUT+=(-c:s copy)
                        OPT_OUTPUT+=(-metadata:s:s:0 "title=dvrescue techsubs")
                        OPT_OUTPUT+=(-metadata:s:s:0 "language=zxx")
                    fi
                    if [[ -s "${DVRESCUE_SCC_VTT}" ]]; then
                        OPT_OUTPUT+=(-map 2)
                        OPT_OUTPUT+=(-c:s copy)
                        OPT_OUTPUT+=(-metadata:s:s:1 "title=Captions")
                        OPT_OUTPUT+=(-metadata:s:s:1 "language=und")
                    fi
                    ;;
            esac
            _get_ranges | \
            while IFS="|" read PTS_END PTS_START FRAMES_PTS_END TC RECDATE SIZE VIDEO_RATE CHROMA ASPECT AUDIO_RATE CHANNELS REC_START RDT_NC TC_NC SEQ_NO FRAME_IN_SEQ START_OFFSET FILESIZE END_OFFSET ; do
                unset START_TIME END_TIME METADATA
                PTS_START_FILENAME_SAFE="${PTS_START//:/-}"
                TC_FILENAME_SAFE="$(echo "${TC}" | sed 's|:|-|g')"
                START_TIME+=(-ss "${PTS_START}")
                INPUT_COUNT="1"
                if [[ -n "${TC}" ]] ; then
                    METADATA+=(-metadata "timecode=${TC}")
                fi
                #OPT_OUTPUT+=(-metadata "encoder=${MUXER}")
                # to do, update output name pattern
                OUTPUT_FILE="${SIDECAR_DIR}/${BASENAME%.*}_${SEQ_NO}_${TC_FILENAME_SAFE}.${EXTENSION}"
                if [[ "${SUBS}" = "Y" ]] ; then
                    SUB_INPUT=(-ss "${PTS_START}" -i "${DVRESCUE_TECHSUBS}")
                    ((INPUT_COUNT++))
                    if [[ -s "${DVRESCUE_SCC_VTT}" ]]; then
                        SUB_INPUT+=(-ss "${PTS_START}" -i "${DVRESCUE_SCC_VTT}")
                        ((INPUT_COUNT++))
                    fi
                else
                    unset SUB_INPUT
                fi
                if [[ -n "${PTS_END}" ]] ; then
                    END_TIME+=(-to "${PTS_END}")
                    DURATION="$(_duration_from_pts_range "${PTS_START}" "${PTS_END}")"
                    SUB_INPUT+=(-t "${DURATION}")
                fi
                if [[ "${FRAMES_SPLIT_ONLY}" = "Y" ]] ; then
                    CHAPTER_FFMETADATA="$(_maketemp).ffmetadata"
                    _make_chapter_metadata_file > "${CHAPTER_FFMETADATA}"
                    CHAPTER_PROCESS=(-i "${CHAPTER_FFMETADATA}")
                    CHAPTER_PROCESS+=(-map_metadata "${INPUT_COUNT}")
                    ((INPUT_COUNT++))
                else
                    unset CHAPTER_PROCESS
                fi
                if [[ "${SOURCE_EXT}" == "dv" ]] && [[ "${FRAMES_SPLIT_ONLY}" = "Y" ]] ; then
                    BYTE_LENGTH="$(echo "${END_OFFSET}" - "${START_OFFSET}" | bc)"
                    tail -c +"${START_OFFSET}" "${DVFILE}" | head -c "${BYTE_LENGTH}" | ffmpeg "${FFMPEG_VERBOSE[@]}" "${OPT_INPUT[@]}" -i - "${SUB_INPUT[@]}" "${CHAPTER_PROCESS[@]}" "${OPT_OUTPUT[@]}" "${METADATA[@]}" "${OUTPUT_FILE}"
                else
                    ffmpeg "${FFMPEG_VERBOSE[@]}" "${OPT_INPUT[@]}" "${START_TIME[@]}" -i "${DVFILE}" -copyts "${END_TIME[@]}" -map 0:v -c:v copy -f rawvideo - | ffmpeg "${FFMPEG_VERBOSE[@]}" "${OPT_INPUT[@]}" -i - "${SUB_INPUT[@]}" "${CHAPTER_PROCESS[@]}" "${OPT_OUTPUT[@]}" "${METADATA[@]}" "${OUTPUT_FILE}"
                fi
            done
        fi
    else
        echo "$(basename "${DVFILE}" is not a file, skipping.)"
    fi
done
