#!/bin/bash

_usage(){
cat <<EOF
"$(basename "${0}")"

Visually display and/or generate xml and sidecars of errors in DV file.
In visual display, error concealment data  in frames will be displayed
as yellow. This script is part of the dvrescue project.

Usage:
 $(basename "${0}") [options] file.dv

Options:
 -m         (Inverse of standard display)
 -x         (create output xml and jpgs)
 -g         (create gif from error jpgs)
 -o OUTPUT  (select a custom location for output files)

 -O FILE    (provide a path to a single output file. When set a single frame will be exported, depending on
             if -b or -t is set. If 'FILE' is set to '-', dvplay will output a jpeg to stdout.)
 -b OFFSET  (provide a byte offset of the frame to examine, otherwise the first frame is default)
 -t PTS     (provide a timestamp to examine, otherwise the first frame is default. Ignored if '-b OFFSET' is set)

 -h         (Displays this help)
 -B BLOCKS  (draw a box around a specific DIF Block(s). Use a comma-delimited list for more than one,
             such as '-B 0,1348,1349')

 -F <path> (provide a custom ffmpeg path)
 -M <path> (provide a custom mediainfo path)
 -D <path> (provide a custom dvrescue path)
 -X <path> (provide a custom xmlstarlet path)
 -d <path> (provide a custom path to a DVRescue XML that corresponds to the
            input file)

To vizualize the relationship between inputs and a merged output (-s, -S, and -b are required):
 -s <list> (provide a pipe-delimited list of the input files for the merge)
 -S <list> (provide a pipe-delimited list of the offsets to the input files listed in '-s')

 For example 'dvplay -s 'file1.dv|file2.dv' -S '120000|240000' -b 240000 output.dv' would show
 an image that depicts the input frames (at the corresponding offsets of the corresponding input
 file) with the corresponding merged output frame at the corresponding offset.

 -z       (disable colored terminal output)
EOF
exit
}

_report(){
    local RED="$(tput setaf 1)"    # Red      - For Warnings
    local GREEN="$(tput setaf 2)"  # Green    - For Declarations
    local BLUE="$(tput setaf 4)"   # Blue     - For Questions
    local MAGENTA="$(tput setaf 5)" # Magenta - For Verbose Statements
    local NC="$(tput sgr0)"        # No Color
    local COLOR=""
    local STARTMESSAGE=()
    local ECHOOPT=""
    local VERBOSE_CHECK=false
    OPTIND=1
    while getopts "vqdwstn" opt ; do
        case "${opt}" in
            v) VERBOSE_CHECK=true ;
               COLOR="${MAGENTA}" ;
               STARTMESSAGE+="#" ;; # only output the message if DV_VERBOSE=true
            q) COLOR="${BLUE}" ;;                        # question mode, use color blue
            d) COLOR="${GREEN}" ;;                       # declaration mode, use color green
            w) COLOR="${RED}" ;;                         # warning mode, use color red
            s) STARTMESSAGE+=([${SCRIPTNAME}] ) ;;       # prepend scriptname to the message
            t) STARTMESSAGE+=($(_get_iso8601) '- ' ) ;;  # prepend timestamp to the message
            n) ECHOOPT="-n" ;;                           # to avoid line breaks after echo
        esac
    done
    shift "$((OPTIND-1))"
    MESSAGE="${1}"
    if ! "${VERBOSE_CHECK}" || ( "${DV_VERBOSE}" && "${VERBOSE_CHECK}" ) ; then
        if [[ "${DISABLE_COLORED_LOGGING}" = "Y" ]] ; then
            >&2 echo ${ECHOOPT} "${STARTMESSAGE[@]}${MESSAGE}"
        else
            >&2 echo ${ECHOOPT} "${COLOR}${STARTMESSAGE[@]}${MESSAGE}${NC}"
        fi
    fi
}

_maketemp(){
    mktemp -q -t "$(basename "${0}").XXXXXX"
    if [ "${?}" -ne 0 ]; then
        echo "${0}: Can't create temp file, exiting..."
        exit 1
    fi
}

OPTIND=1
FFMPEG_VERBOSE=(-v quiet -stats)
DV_VERBOSE=false
while getopts ":t:b:mxgF:M:D:X:d:hvzB:o:O:s:S:" OPT ; do
    case "${OPT}" in
        t) FFMPEG_INPUT_OPTS=(-ss ${OPTARG}) ; PTS="$OPTARG" ;;
        b) FFMPEG_INPUT_OPTS=(-skip_initial_bytes ${OPTARG}) ; OFFSET="$OPTARG" ;;
        m) MASK_ONLY="Y" ;;
        x) ERROR_REVIEW="Y" ;;
        g) GIF_OUTPUT="Y" ;;
        F) FFMPEG_PATH="${OPTARG}" ;;
        M) MEDIAINFO_PATH="${OPTARG}" ;;
        D) DVRESCUE_PATH="${OPTARG}" ;;
        X) XMLSTARLET_PATH="${OPTARG}" ;;
        d) DVRESCUE_XML="${OPTARG}" ;;
        h) _usage ;;
        v) unset FFMPEG_VERBOSE ; DV_VERBOSE=true ;;
        z) DISABLE_COLORED_LOGGING="Y" ;;
        B) BLOCKS="${OPTARG}" ;;
        o) OUTPUTDIR="${OPTARG}";;
        O) OUTPUTFILE="${OPTARG}" ; ERROR_REVIEW="Y" ;;
        s) INPUTFILE_PATHS="${OPTARG}" ;;
        S) INPUTFILE_OFFSETS="${OPTARG}" ;;
        :) echo "Option -${OPTARG} requires an argument" ; exit 1 ;;
        *) echo "bad option -${OPTARG}" ; _usage ; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

if [[ -z "${MEDIAINFO_PATH}" ]] ; then
    MEDIAINFO_PATH="$(which mediainfo)"
fi
if [[ ! -f "${MEDIAINFO_PATH}" ]] ; then
    _report -d "The mediainfo command-line tool is not found, but is used to verify that the output is well synchronized."
    _report -d "Please install a mediainfo CLI from https://mediaarea.net/en/MediaInfo/Download or provide a path to mediainfo with the -M argument."
fi
if [[ -z "${FFMPEG_PATH}" ]] ; then
    FFMPEG_PATH="$(which ffmpeg)"
fi
if [[ ! -f "${FFMPEG_PATH}" ]] ; then
    _report -d "The ffmpeg command-line tool is not found, but needed."
    _report -d "Please install a ffmpeg CLI  or provide a path to ffmpeg with the -F argument."
else
    FFPLAY_PATH="$(dirname "${FFMPEG_PATH}")/ffplay"
    # the minimum recommanded version of libavformat, otherwise will use a hack to visualize errors
    LAVF_MAJ_REQ=59
    LAVF_MIN_REQ=19
    LAVF_MIC_REQ=100
    LAVF_VERSION="$("${FFMPEG_PATH}" -version | grep "libavformat" | head -n 1 | sed 's/[^0-9./]//g' | cut -d "/" -f 1)"
    LAVFversion_maj="$(echo "${LAVF_VERSION}" | cut -d "." -f1)"
    LAVFversion_min="$(echo "${LAVF_VERSION}" | cut -d "." -f2)"
    LAVFversion_mic="$(echo "${LAVF_VERSION}" | cut -d "." -f3)"
    if ! { \
          [[ ${LAVFversion_maj} -gt ${LAVF_MAJ_REQ} ]] || \
        { [[ ${LAVFversion_maj} -eq ${LAVF_MAJ_REQ} ]] && [[ ${LAVFversion_min} -gt ${LAVF_MIN_REQ} ]]; } || \
        { [[ ${LAVFversion_maj} -eq ${LAVF_MAJ_REQ} ]] && [[ ${LAVFversion_min} -eq ${LAVF_MIN_REQ} ]] && [[ ${LAVFversion_mic} -ge ${LAVF_MIC_REQ} ]]; }; \
       } ; then
        _report -w "FFmpeg's libavformat version  ${LAVF_MAJ_REQ}.${LAVF_MIN_REQ}.${LAVF_MIC_REQ} or greater is recommended and ${FFMPEG_PATH} is running ${LAVFversion_maj}.${LAVFversion_min}.${LAVFversion_mic}."
        _report -w "Please update ffmpeg."
        BITSTREAM_FILTER_METHOD="hack"
    else
        BITSTREAM_FILTER_METHOD="ffmpeg"
    fi
fi

DVPLAY_DISPLAY_TEMP="$(_maketemp)"

_set_up_xsl(){
cat > "${DVPLAY_DISPLAY_TEMP}" << 'DISPLAY_XSL'
<?xml version="1.0"?>
<!-- WIP -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dv="https://mediaarea.net/dvrescue" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/1999/xhtml" version="1.0" extension-element-prefixes="xsi" exclude-result-prefixes="dv x" >
<xsl:output encoding="UTF-8" method="html" version="1.0" indent="yes" doctype-system="about:legacy-compat"/>
<xsl:template name="substring-after-last">
  <xsl:param name="string"/>
  <xsl:param name="char"/>
  <xsl:choose>
    <xsl:when test="contains($string, $char)">
      <xsl:call-template name="substring-after-last">
        <xsl:with-param name="string" select="substring-after($string, $char)"/>
        <xsl:with-param name="char" select="$char"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$string"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
<xsl:template match="/dv:dvrescue">
  <html>
    <head>
      <style type="text/css">
        body {
          font-family: "PT Sans", Helvetica, Arial, sans-serif;
          display: grid;
          justify-content: center;
          justify-items: center;
          overflow-wrap: anywhere;
        }
        h1 {
          font-weight: 800;
          font-style: italic;
        }
        table {
          border: 0.2em solid black;
        }
        img {
          max-width: 350px;
          height: inherit;
        }
        th {
          background-color: black;
          color: white;
          font-weight: bold;
          font-size: 1.5em;
        }
        td {
          padding-left: 2em;
          padding-right: 2em;
        }
        dt {
          font-weight: 800;
          font-style: italic;
        }
        .frames {
          display: flex;
          flex-wrap: wrap;
          justify-content: center;
        }
        .frameError {
          border: 0.2em solid black;
          margin: 0.1em;
          width: 350px;
        }
        .frameError p {
          padding-left: 0.5em;
          padding-right: 0.5em;
          margin: 0.25em;
        }
        .tc {
          background-color: black;
          color: white;
          font-weight: bold;
          font-size: 1.5em;
          text-align: center;
          padding: 0;
          margin: 0 !important;
        }
        .green {
          border-top: 0.1em solid green;
          display: grid;
          grid-template-columns: repeat(2, auto);
        }
        .red {
          border: 0.1em solid red;
          color: red;
        }
        .errorNum {
          text-decoration: underline wavy;
        }
        .errorNum:hover .tooltip {
          display: block;
        }
        .tooltip {
          display: none;
          background-color: black;
          color: white;
          font-weight: bold;
          margin-left: 1em;
          border-radius: 5px;
          padding: 0.3em;
          position: absolute;
          z-index: 1000;
        }
      </style>
    </head>
    <body>
      <xsl:for-each select="dv:media">
        <header>
          <h1>DVRescue Report</h1>
          <h2><xsl:value-of select="@ref"/></h2>
        </header>
        <xsl:for-each select="dv:frames">
          <section class="metadata">
            <table>
              <thead><tr><th colspan="4">Frames metadata</th></tr></thead>
              <tbody>
                <tr>
                  <td>
                    <dl>
                      <xsl:if test="@count"><dt>Count</dt><dd><xsl:value-of select="@count"/></dd></xsl:if>
                      <xsl:if test="@scan_type"><dt>Scan type</dt><dd><xsl:value-of select="@scan_type"/></dd></xsl:if>
                      <xsl:if test="@size"><dt>Size</dt><dd><xsl:value-of select="@size"/></dd></xsl:if>
                    </dl>
                  </td>
                  <td>
                    <dl>
                      <xsl:if test="@pts"><dt>Start timestamp</dt><dd><xsl:value-of select="@pts"/></dd></xsl:if>
                      <xsl:if test="@end_pts"><dt>End timestamp</dt><dd><xsl:value-of select="@end_pts"/></dd></xsl:if>
                      <xsl:if test="@tc"><dt>Start timecode</dt><dd><xsl:value-of select="dv:frame[@tc!=''][1]/@tc"/></dd></xsl:if>
                      <xsl:if test="dv:frame/@abst"><dt>ABST Range</dt><dd><xsl:value-of select="dv:frame[@abst!=''][1]/@abst"/> - <xsl:value-of select="dv:frame[@abst!=''][last()]/@abst"/></dd></xsl:if>
                      <xsl:if test="dv:frame/@abst"><dt>ABST Frame Rate</dt><dd><xsl:value-of select="(dv:frame[@abst!=''][last()]/@abst - dv:frame[@abst!=''][1]/@abst) div (dv:frame[@abst!=''][last()]/@n - dv:frame[@abst!=''][1]/@n)"/></dd></xsl:if>
                    </dl>
                  </td>
                  <td>
                    <dl>
                      <xsl:if test="@video_rate"><dt>Frame rate</dt><dd><xsl:value-of select="@video_rate"/></dd></xsl:if>
                      <xsl:if test="@aspect_ratio"><dt>Aspect ratio</dt><dd><xsl:value-of select="@aspect_ratio"/></dd></xsl:if>
                      <xsl:if test="@chroma_subsampling"><dt>Chroma subsampling</dt><dd><xsl:value-of select="@chroma_subsampling"/></dd></xsl:if>
                    </dl>
                  </td>
                  <td>
                    <dl>
                      <xsl:if test="@audio_rate"><dt>Audio rate</dt><dd><xsl:value-of select="@audio_rate"/></dd></xsl:if>
                      <xsl:if test="@channels"><dt>Channels</dt><dd><xsl:value-of select="@channels"/></dd></xsl:if>
                    </dl>
                  </td>
                </tr>
              </tbody>
            </table>
          </section>
          <section class="frames">
            <xsl:for-each select="dv:frame">
              <xsl:variable name="filename">
                <xsl:call-template name="substring-after-last">
                  <xsl:with-param name="string" select="../../@ref"/>
                  <xsl:with-param name="char" select="'/'"/>
                </xsl:call-template>
              </xsl:variable>
              <xsl:variable name="tc_display">
                <xsl:choose>
                  <xsl:when test="@tc">
                      <xsl:value-of select="@tc"/>
                  </xsl:when>
                  <xsl:otherwise>
                      <xsl:text>XX:XX:XX:XX</xsl:text>
                  </xsl:otherwise>
                </xsl:choose>
              </xsl:variable>
              <xsl:variable name="tc_filename_safe">
                <xsl:value-of select="translate($tc_display,':','-')"/>
              </xsl:variable>
              <xsl:variable name="jpg_name">
                <xsl:value-of select="$filename"/>
                <xsl:text>_n</xsl:text>
                <xsl:value-of select="substring(string(1000000 + @n), 2)"/>
                <xsl:text>_</xsl:text>
                <xsl:value-of select="$tc_filename_safe"/>
                <xsl:text>.jpg</xsl:text>
              </xsl:variable>
              <div class="frameError">
                <xsl:if test="dv:dseq">
                  <img>
                    <xsl:attribute name="src">
                      <xsl:value-of select="$jpg_name"/>
                    </xsl:attribute>
                  </img>
                </xsl:if>
                <xsl:if test="$tc_display"><p class="tc"><xsl:value-of select="$tc_display"/></p></xsl:if>
                <xsl:if test="@n"><p class="tc"><xsl:value-of select="@n"/></p></xsl:if>
                <xsl:if test="@rec_start"><p>Recording start</p></xsl:if>
                <xsl:if test="@rec_end"><p>Recording end</p></xsl:if>
                <xsl:if test="@rdt">
                  <p><strong>Recorded Date Time </strong>
                    <xsl:value-of select="@rdt"/>
                    <xsl:if test="@rdt_r"> (repeating) </xsl:if>
                    <xsl:if test="@rdt_nc"> (non-consecutive) </xsl:if>
                  </p>
                </xsl:if>
                <xsl:if test="@arb">
                  <p><strong>Arbitrary data </strong>
                    <xsl:value-of select="@arb"/>
                    <xsl:if test="@arb_r"> (repeating)</xsl:if>
                    <xsl:if test="@arb_nc"> (non-consecutive)</xsl:if>
                  </p>
                </xsl:if>
                <xsl:for-each select="dv:sta">
                    <xsl:call-template name="staType"/>
                </xsl:for-each>
                <xsl:for-each select="dv:aud">
                    <xsl:call-template name="audType"/>
                </xsl:for-each>
              </div>
            </xsl:for-each>
          </section>
        </xsl:for-each>
      </xsl:for-each>
      <footer><xsl:value-of select="dv:creator/dv:program"/> v.<xsl:value-of select="dv:creator/dv:version"/></footer>
    </body>
  </html>
  </xsl:template>
  <xsl:template match="dv:sta" name="staType">
    <xsl:if test="@t">
      <p class="errorNum">Error #<xsl:value-of select="@t"/>
        <span class="tooltip">
          <xsl:choose>
            <xsl:when test="@t=0">No error, what a nice DV macroblock.</xsl:when>
            <xsl:when test="@t=2">Replaced a macroblock with the one of the same position of the previous frame (guaranteed continuity).</xsl:when>
            <xsl:when test="@t=4">Replaced a macroblock with the one of the same position of the next frame (guaranteed continuity).</xsl:when>
            <xsl:when test="@t=6">A concealment method is used but not specified (guaranteed continuity).</xsl:when>
            <xsl:when test="@t=7">Error with an error code within the macro block.</xsl:when>
            <xsl:when test="@t=10">Replaced a macroblock with the one of the same position of the previous frame (no guaranteed continuity).</xsl:when>
            <xsl:when test="@t=12">Replaced a macroblock with the one of the same position of the next frame (no guaranteed continuity).</xsl:when>
            <xsl:when test="@t=14">A concealment method is used but not specified (no guaranteed continuity).</xsl:when>
            <xsl:when test="@t=15">Error with unknown position.</xsl:when>
            <xsl:otherwise>
              Unknown error
            </xsl:otherwise>
          </xsl:choose>
        </span>
      </p>
    </xsl:if>
    <xsl:if test="(number(@n) - number(@n_even)) > 1">
      <p class="red"><strong>Error within playback device</strong></p>
    </xsl:if>
  </xsl:template>
  <xsl:template match="dv:aud" name="audType">
      <p class="green"><strong>Frame Audio</strong></p>
      <xsl:if test="(number(@n) - number(@n_even)) > 1">
        <p class="red"><strong>Error within playback device</strong></p>
      </xsl:if>
  </xsl:template>
</xsl:stylesheet>
DISPLAY_XSL
}

# fill color handling
_convert_array_to_macroblock(){
    LUMA_AC="ffffffffffffffffffffff"
    CHROMA_AC="ffffffffffffff"
    COLOR_POINT="${1}"
    echo "${COLOR_POINT:0:6}${LUMA_AC}${COLOR_POINT:0:6}${LUMA_AC}${COLOR_POINT:0:6}${LUMA_AC}${COLOR_POINT:0:6}${LUMA_AC}${COLOR_POINT:6:6}${CHROMA_AC}${COLOR_POINT:12:6}${CHROMA_AC}"
    # get dv color data via
# ffmpeg -f lavfi -i color=s=720x480:r=30000/1001:color=yellow -vframes 1 -f rawvideo -c:v dvvideo -pix_fmt yuv411p - | xxd -ps -c 80 | tail -n 1 | cut -c 9-14,121-126,141-146
}

_get_iso8601(){
    date +%FT%T
}

# font selection
if [[ -n "${DVRESCUE_FONT}" ]] ; then
    DEFAULTFONT="${DVRESCUE_FONT}"
elif [[ -f "/Library/Fonts/Andale Mono.ttf" ]] ; then
    DEFAULTFONT="/Library/Fonts/Andale Mono.ttf"
elif [[ -f "/System/Library/Fonts/Supplemental/Andale Mono.ttf" ]] ; then
    DEFAULTFONT="/System/Library/Fonts/Supplemental/Andale Mono.ttf"
elif [[ -f "/System/Library/Fonts/Monaco.dfont" ]] ; then
    DEFAULTFONT="/System/Library/Fonts/Monaco.dfont"
elif [[ -f "/Library/Fonts/Microsoft/Lucida Console.ttf" ]] ; then
    DEFAULTFONT="/Library/Fonts/Microsoft/Lucida\Console.ttf"
elif [[ -f "/Library/Fonts/LetterGothicStd.otf" ]] ; then
    DEFAULTFONT="/Library/Fonts/LetterGothicStd.otf"
elif [[ -f "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf" ]] ; then
    DEFAULTFONT="/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
else
    _report -w "$(basename "${0}") can't find a preferred font to use :("
fi

DV_YELLOW="5206ff1216ff9016ff"
DV_RED="d106ff7016ffda16ff"
FILL=$(_convert_array_to_macroblock "${DV_YELLOW}")
if [[ "${MASK_ONLY}" = "Y" ]] ; then
    FIND="(?<=9[0-9a-f]{5}[0][0-9a-f])[0-9a-f]{152}"
else
    FIND="(?<=9[0-9a-f]{5}[^0][0-9a-f])[0-9a-f]{152}"
fi

# filter handling
_get_filters(){
MEDIAINFO_STATS="$("${MEDIAINFO_PATH}" --Inform="Video;%Format%|%Height%|%ChromaSubsampling%" "${DVFILE}")"
CODEC_NAME="$(echo "${MEDIAINFO_STATS}" | cut -d "|" -f1)"
HEIGHT="$(echo "${MEDIAINFO_STATS}" | cut -d "|" -f2)"
PIX_FMT="$(echo "${MEDIAINFO_STATS}" | cut -d "|" -f3)"
TILE_V="8"
TILE_H="4"
LESSONE="$((${TILE_V}*${TILE_H}-1))"
if [[ -n "${BLOCKS}" ]] ; then
    IFS=, read -ra BLOCK <<< "${BLOCKS}"
    FORMAT="format=yuv444p,"
    for BLOCK in "${BLOCK[@]}" ; do
        BLO=$((BLOCK%135))
        SEQ=$((BLOCK/135))
        if [[ "${HEIGHT}" == "576" ]] ; then
            SEQCOUNT=12
        else
            SEQCOUNT=10
        fi
        BLO_M5=$((BLO%5))
        if [[ "${BLO_M5}" == "0" ]] ; then
            I5=2 ; J=2
        elif [[ "${BLO_M5}" == "1" ]] ; then
            I5=6 ; J=1
        elif [[ "${BLO_M5}" == "2" ]] ; then
            I5=8 ; J=3
        elif [[ "${BLO_M5}" == "3" ]] ; then
            I5=0 ; J=0
        else
            I5=4 ; J=4
        fi
        I=$(((I5+SEQ)%SEQCOUNT))
        K=$(((BLOCK/5)%27))
        if [[ "${PIX_FMT}" = "4:1:1" && ( "${J}" -lt "4" || ( "${J}" -eq "4" && "${K}" -lt "24" )) ]] ; then
            W=32 ; H=8
        else
            W=16 ; H=16
        fi
        if [[ "${PIX_FMT}" = "4:1:1" ]] ; then
            WL=32 ; MACROBLOCK_ROWS=6 ; MBX_OFFSET=$(((J%2)*3))
        else
            WL=16 ; MACROBLOCK_ROWS=3 ; MBX_OFFSET=0
        fi
        MBx=$(((K+MBX_OFFSET)/MACROBLOCK_ROWS))
        if [[ "$((MBx%2))" -eq "0" ]] ; then
            MByt=$((K+MBX_OFFSET))
        else
            MByt=$((K+MBX_OFFSET+MACROBLOCK_ROWS-1-(((K+MBX_OFFSET)%MACROBLOCK_ROWS)*2)))
        fi
        MBy=$((MByt%MACROBLOCK_ROWS))
        if [[ "${PIX_FMT}" = "4:1:1" && "$((J%2))" -eq "1" ]] ; then
            SBx=$((J*144-16))
        else
            SBx=$((J*144))
        fi
        X=$((SBx+(WL*MBx)))
        Y=$(((I*48)+(MBy*H)))
        FILTER+="drawbox=w=${W}:h=${H}:x=${X}:y=${Y}:color=invert:t=1,drawbox=w=${W}:h=${H}:x=${X}:y=${Y}:color=purple@0.15:t=fill,"
    done
    # remove trailing comma
    FILTER="${FILTER%?}"
elif [[ -n "${OUTPUTFILE}" ]] ; then
    FILTER="copy"
else
    FILTER="scale=iw/4:ih/4,transpose=2,tile=layout=${TILE_V}x${TILE_H}:init_padding=${LESSONE}:overlap=${LESSONE}:padding=1,transpose=1,setsar=1/1"
fi
}

_play_dv(){
    if [[ "${BITSTREAM_FILTER_METHOD}" == "ffmpeg" ]] ; then
        "${FFMPEG_PATH}" "${INPUT_OPTS[@]}" -i "${1}" -bsf dv_error_marker -c:v copy -f rawvideo - | \
        "${FFPLAY_PATH}" - -vf "${FILTER}"
    else
        "${FFMPEG_PATH}" "${INPUT_OPTS[@]}" -i "${1}" -c:v copy -f rawvideo - | \
        xxd -p -c 80 | \
        perl -pe "s|${FIND}|${FILL}|g" | \
        xxd -r -p | \
        "${FFPLAY_PATH}" - -vf "${FILTER}"
    fi
}

_make_dvrescue_xml(){
    if [[ -z "${DVRESCUE_PATH}" ]] ; then
        DVRESCUE_PATH="$(which dvrescue)"
    fi
    if [[ ! -f "${DVRESCUE_PATH}" ]] ; then
        _report -w "The dvrescue command-line tool is not found."
        exit 1
    fi
    if [[ ! -f "${DVRESCUE_XML}" ]] ; then
        _report -d "Assessing $(basename "${DVFILE}")..."
        _mkdir2 "${SIDECAR_DIR}"
        "${DVRESCUE_PATH}" "${DVFILE}" > "${DVRESCUE_XML}"
    fi
}

_mkdir2(){
    local DIR2MAKE=""
    while [ "${*}" != "" ] ; do
        DIR2MAKE="${1}"
        if [ ! -d "${DIR2MAKE}" ] ; then
            mkdir -p "${DIR2MAKE}"
            if [ "${?}" -ne 0 ]; then
                _report -w "${0}: Can't create directory at ${DIR2MAKE}"
                exit 1
            fi
        fi
        shift
    done
}

while [[ "${@}" != "" ]] ; do
    DVFILE="${1}"
    shift
    if [[ -n "${INPUTFILE_PATHS}" ]] ; then
        # make a merge summary
        IFS="|" read -ra INPUTFILE_PATH <<< "${INPUTFILE_PATHS}"
        IFS="|" read -ra INPUTFILE_OFFSET <<< "${INPUTFILE_OFFSETS}"
        FIRST_NON_EMPTY_OFFSET_TMP="${INPUTFILE_OFFSETS%%[0-9]*}"
        FIRST_NON_EMPTY_OFFSET="${#FIRST_NON_EMPTY_OFFSET_TMP}"
        COUNT_INPUT_PATH="${#INPUTFILE_PATH[@]}"
        COUNT_INPUT_OFFSET="${#INPUTFILE_OFFSET[@]}"
        COLOR=(orange hotpink blue mediumpurple yellow cyan)
        INPUT_COUNTER=0
        MAP_COUNTER=0
        unset INPUT_SET MAP_SET FILTER_SET

        while [[ "${INPUT_COUNTER}" < "${COUNT_INPUT_PATH}" ]]; do
            if [[ -n "${INPUTFILE_OFFSET[${INPUT_COUNTER}]}" ]] ; then
                INPUT_SET+=(-skip_initial_bytes "${INPUTFILE_OFFSET[${INPUT_COUNTER}]}" -i "${INPUTFILE_PATH[${INPUT_COUNTER}]}")
                INPUT_SET+=(-skip_initial_bytes "${INPUTFILE_OFFSET[${INPUT_COUNTER}]}" -i "${INPUTFILE_PATH[${INPUT_COUNTER}]}")
                MAP_SET+=(-map "${MAP_COUNTER}:v")
                FILTER_SET+="[0:v:${MAP_COUNTER}]"
                MAP_COUNTER=$((MAP_COUNTER+1))
                MAP_SET+=(-map "${MAP_COUNTER}:v")
                MAP_SET+=("-bsf:v:${MAP_COUNTER}" "dv_error_marker=color=${COLOR[$INPUT_COUNTER]}")
                FILTER_SET+="[0:v:${MAP_COUNTER}]blend=all_expr='if(eq(mod(X,4),mod(Y,4)),B,A)',drawtext=fontfile=${DEFAULTFONT}:fontsize=24:x=8:y=8:fontcolor=white:shadowx=1:shadowy=2:text='Merge Input \#${INPUT_COUNTER}',drawtext=fontfile=${DEFAULTFONT}:fontsize=18:x=8:y=34:fontcolor=white:shadowx=1:shadowy=2:text='$(basename "${INPUTFILE_PATH[${INPUT_COUNTER}]}") @ ${INPUTFILE_OFFSET[${INPUT_COUNTER}]}'[v${INPUT_COUNTER}];"
            else
                # if missing frame then just use the first one and blank it out with a missing frame warning
                 v="||8" ; 
                INPUT_SET+=(-skip_initial_bytes "${INPUTFILE_OFFSET[${FIRST_NON_EMPTY_OFFSET}]}" -i "${INPUTFILE_PATH[${FIRST_NON_EMPTY_OFFSET}]}")
                MAP_SET+=(-map "${MAP_COUNTER}:v")
                FILTER_SET+="[0:v:${MAP_COUNTER}]lutyuv=y=0,drawtext=fontfile=${DEFAULTFONT}:fontsize=24:x=8:y=8:fontcolor=white:shadowx=1:shadowy=2:text='Merge Input \#${INPUT_COUNTER} - MISSING FRAME',drawtext=fontfile=${DEFAULTFONT}:fontsize=18:x=8:y=34:fontcolor=white:shadowx=1:shadowy=2:text='$(basename "${INPUTFILE_PATH[${INPUT_COUNTER}]}")'[v${INPUT_COUNTER}];"
            fi
            FILTER_OUTS+="[v${INPUT_COUNTER}]"
            MAP_COUNTER=$((MAP_COUNTER+1))
            INPUT_COUNTER=$((INPUT_COUNTER+1))
        done
        INPUT_SET+=(-skip_initial_bytes "${OFFSET}" -i "${DVFILE}")
        INPUT_SET+=(-skip_initial_bytes "${OFFSET}" -i "${DVFILE}")

        MAP_SET+=(-map "${MAP_COUNTER}:v")
        FILTER_SET+="[0:v:${MAP_COUNTER}]"
        MAP_COUNTER=$((MAP_COUNTER+1))
        MAP_SET+=(-map "${MAP_COUNTER}:v")
        MAP_SET+=("-bsf:v:${MAP_COUNTER}" "dv_error_marker=color=white")
        FILTER_SET+="[0:v:${MAP_COUNTER}]blend=all_expr='if(eq(mod(X,4),mod(Y,4)),B,A)',drawtext=fontfile=${DEFAULTFONT}:fontsize=24:x=8:y=8:fontcolor=white:shadowx=1:shadowy=2:text='Merged Output',drawtext=fontfile=${DEFAULTFONT}:fontsize=18:x=8:y=34:fontcolor=white:shadowx=1:shadowy=2:text='$(basename "${DVFILE}") @ ${OFFSET}',drawbox=color=white[v${INPUT_COUNTER}];"
        FILTER_OUTS+="[v${INPUT_COUNTER}]"
        INPUT_COUNTER=$((INPUT_COUNTER+1))
        MAP_COUNTER=$((MAP_COUNTER+1))
        if [[ -n "${OUTPUTFILE}" ]]  ; then
            if [[ "${OUTPUTFILE}" == "-" ]] ; then
                PIPE_FORMAT=(-c:v mjpeg -f image2)
            fi
            DVPLAY_OUTPUT="${OUTPUTFILE}"
            ffmpeg "${INPUT_SET[@]}" "${MAP_SET[@]}" -f nut -c copy -frames:v 1 - | \
            ffmpeg -i - -lavfi "${FILTER_SET%?};${FILTER_OUTS}xstack=inputs=${INPUT_COUNTER}:layout=0_0|w0_0|w0+w1_0|0_h0|w0_h0|w0+w1_h0:fill=gray,scale=iw/2:ih/2" -frames:v 1 "${PIPE_FORMAT[@]}" "${DVPLAY_OUTPUT}"
        else
            ffmpeg "${INPUT_SET[@]}" "${MAP_SET[@]}" -f nut -c copy -frames:v 1 - | \
            ffmpeg -i - -lavfi "${FILTER_SET%?};${FILTER_OUTS}xstack=inputs=${INPUT_COUNTER}:layout=0_0|w0_0|w0+w1_0|0_h0|w0_h0|w0+w1_h0:fill=gray,scale=iw/2:ih/2" -f nut -frames:v 1 -c:v rawvideo - | \
            ffplay -
        fi
    elif [[ -n "${OUTPUTFILE}" ]]  ; then
        _get_filters
        if [[ "${OUTPUTFILE}" == "-" ]] ; then
            PIPE_FORMAT=(-c:v mjpeg -f image2)
        fi
        DVPLAY_OUTPUT="${OUTPUTFILE}"
        if [[ "${BITSTREAM_FILTER_METHOD}" == "ffmpeg" ]] ; then
            "${FFMPEG_PATH}" -nostdin "${FFMPEG_INPUT_OPTS[@]}" -i "${DVFILE}" -vframes:v 1 -bsf dv_error_marker -c:v copy -f rawvideo - | \
            "${FFMPEG_PATH}" -nostdin -y -i - -frames:v 1 -vf "${FILTER}" "${PIPE_FORMAT[@]}" "${DVPLAY_OUTPUT}"
        else
            "${FFMPEG_PATH}" -nostdin "${FFMPEG_INPUT_OPTS[@]}" -i "${DVFILE}" -vframes:v 1 -c:v copy -f rawvideo - | \
            xxd -p -c 80 | \
            perl -pe "s|${FIND}|${FILL}|g" | \
            xxd -r -p | \
            "${FFMPEG_PATH}" -nostdin -y -i - -frames:v 1 -vf "${FILTER}" "${PIPE_FORMAT[@]}" "${DVPLAY_OUTPUT}"
        fi
    elif [[ "${ERROR_REVIEW}" = "Y" ]] || [[ "${GIF_OUTPUT}" = "Y" ]] ; then
        BASENAME="$(basename "${DVFILE}")"
        DV_IMAGE_DIR="${BASENAME//./_}_dvrescue_images"
        if [[ -n "${OUTPUTDIR}" ]] && [[ -d "${OUTPUTDIR}" ]] ; then
            SIDECAR_DIR="${OUTPUTDIR}"
        elif [[ -n "${OUTPUTDIR}" ]] ; then
            _report -w "User selected output is not a valid directory. Exiting"
            exit 1
        else
            SIDECAR_DIR="$(dirname "${DVFILE}")"
        fi
        IMAGE_DIR="${SIDECAR_DIR}/${DV_IMAGE_DIR}"
        DVRESCUE_HTML="${SIDECAR_DIR}/${DV_IMAGE_DIR}/${BASENAME}.dvrescue.html"
        if [[ ! -s "${DVRESCUE_XML}" ]] ; then
            DVRESCUE_XML="${SIDECAR_DIR}/${BASENAME}.dvrescue.xml"
        fi
        _make_dvrescue_xml
        if [[ -z "${XMLSTARLET_PATH}" ]] ; then
            XMLSTARLET_PATH="$(which xmlstarlet)"
        fi
        if [[ ! -f "${XMLSTARLET_PATH}" ]] ; then
            _report -w "The xmlstarlet command-line tool is not found."
            exit 1
        fi
        _report -d "Making jpegs of errors within $(basename "${DVFILE}")..."
        TOTAL_FRAMES="$("${XMLSTARLET_PATH}" sel -N d="https://mediaarea.net/dvrescue" -t -m "d:dvrescue/d:media" -v "sum(d:frames/@count)" -n "${DVRESCUE_XML}")"
        COUNTER=1
        "${XMLSTARLET_PATH}" sel -N d="https://mediaarea.net/dvrescue" -t -m "d:dvrescue/d:media/d:frames/d:frame[d:sta]" -v @n -o "," -v @pts -o "," -v @tc -n "${DVRESCUE_XML}" | while read error_frame ; do
            N="$(echo "${error_frame}" | cut -d "," -f1)"
            PTS="$(echo "${error_frame}" | cut -d "," -f2)"
            TC="$(echo "${error_frame}" | cut -d "," -f3)"
            if [[ -n "${TC}" ]] ; then
                TC_E="$(echo "${TC}" | sed 's|:|\\:|g')"
            else
                TC_E="XX\\:XX\\:XX\\:XX"
            fi
            TC_FILENAME_SAFE="n$(printf "%06d" ${N})_$(echo "${TC:-XX:XX:XX:XX}" | sed 's|:|-|g')"
            PTS_E="$(echo "${PTS}" | sed 's|:|\\:|g')"
            if [[ "${GIF_OUTPUT}" = "Y" ]] ; then
                _mkdir2 "${IMAGE_DIR}/dvrescue_tmp"
                DVPLAY_OUTPUT="${IMAGE_DIR}/dvrescue_tmp/${BASENAME}_$(echo "$COUNTER" | awk '{ printf("%06i", $1) }').jpg"
            else
                _mkdir2 "${IMAGE_DIR}"
                DVPLAY_OUTPUT="${IMAGE_DIR}/${BASENAME}_${TC_FILENAME_SAFE}.jpg"
            fi
            if [[ "${BITSTREAM_FILTER_METHOD}" == "ffmpeg" ]] ; then
                "${FFMPEG_PATH}" -nostdin -ss "${PTS}" -i "${DVFILE}" -vframes:v 1 -bsf dv_error_marker -c:v copy -f rawvideo - | \
                "${FFMPEG_PATH}" -nostdin -y -i - -vf "pad=w=iw:h=ih+24:x=0:y=24:color=gray,drawbox=t=fill:w=iw*(${N}/${TOTAL_FRAMES}):h=24:c=silver:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:y=4:x=4:text='PTS=${PTS_E} TC=${TC_E} Frame=${N}'" "${DVPLAY_OUTPUT}"
            else
                "${FFMPEG_PATH}" -nostdin -ss "${PTS}" -i "${DVFILE}" -vframes:v 1 -c:v copy -f rawvideo - | \
                xxd -p -c 80 | \
                perl -pe "s|${FIND}|${FILL}|g" | \
                xxd -r -p | \
                "${FFMPEG_PATH}" -nostdin -y -i - -vf "pad=w=iw:h=ih+24:x=0:y=24:color=gray,drawbox=t=fill:w=iw*(${N}/${TOTAL_FRAMES}):h=24:c=silver:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:y=4:x=4:text='PTS=${PTS_E} TC=${TC_E} Frame=${N}'" "${DVPLAY_OUTPUT}"
            fi
            ((COUNTER=COUNTER+1))
        done
        if [[ "${GIF_OUTPUT}" = "Y" ]] ; then
            GIF_PALETTE="$(_maketemp)"
            "${FFMPEG_PATH}" -nostdin -f image2 -i "${IMAGE_DIR}/dvrescue_tmp/${BASENAME}_%06d.jpg" -vf palettegen "${GIF_PALETTE}.png"
            "${FFMPEG_PATH}" -nostdin -f image2 -i "${IMAGE_DIR}/dvrescue_tmp/${BASENAME}_%06d.jpg" -i "${GIF_PALETTE}.png" -filter_complex paletteuse "${IMAGE_DIR}/${BASENAME}_error.gif"
            rm -r "${IMAGE_DIR}/dvrescue_tmp" "${GIF_PALETTE}.png"
        else
            _set_up_xsl
            _mkdir2 "${IMAGE_DIR}"
            xsltproc "${DVPLAY_DISPLAY_TEMP}" "${DVRESCUE_XML}" > "${DVRESCUE_HTML}"
        fi
    else
        _play_dv "${DVFILE}"
    fi
done
