#!/bin/bash
SCRIPTDIR="$(dirname $(which "${0}"))"

_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:
    -s TIME    (choose a custom start time (in seconds) within file)
    -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)
    -h         (Displays this help)
EOF
}

OPTIND=1
while getopts ":s:mxgho:" OPT ; do
    case "${OPT}" in
        s) START_TIME="${OPTARG}";;
        m) MASK_ONLY="Y" ;;
        x) ERROR_REVIEW="Y" ;;
        g) GIF_OUTPUT="Y" ;;
        h) _usage ;;
        o) USER_OUTPUT="${OPTARG}";;
        *) echo "bad option -${OPTARG}" ; _usage ; exit 1 ;;
        :) echo "Option -${OPTARG} requires an argument" ; exit 1 ;;
    esac
done
shift $(( ${OPTIND} - 1 ))

cat > /tmp/dvplay_display.xsl << '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>
                    </dl>
                  </td>
                  <td>
                    <dl>
                      <xsl:if test="@video_rate"><dt>Video 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

if [[ -n "${START_TIME}" ]] ; then
  INPUT_OPTS+=(-ss "${START_TIME}")
fi

# 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
}

# report formatting function
_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 NC="$(tput sgr0)"        # No Color
    local COLOR=""
    local STARTMESSAGE=""
    local ECHOOPT=""
    OPTIND=1
    while getopts "qdwstn" opt ; do
        case "${opt}" in
            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}"
    echo ${ECHOOPT} "${COLOR}${STARTMESSAGE[@]}${MESSAGE}${NC}"
}

# font selection
if [[ -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 -wt "$(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
TILE_V="8"
TILE_H="4"
LESSONE="$((${TILE_V}*${TILE_H}-1))"
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"

_play_dv(){
  ffmpeg "${INPUT_OPTS[@]}" -i "${1}" -c:v copy -f rawvideo - | \
  xxd -p -c 80 | \
  perl -pe "s|${FIND}|${FILL}|g" | \
  xxd -r -p | \
  ffplay - -vf "${FILTER}"
}

_make_dvrescue_xml(){
  if [[ ! -f "${DVRESCUE_XML}" ]] ; then
    echo "Assessing $(basename "${DVFILE}")..."
    _mkdir2 "${SIDECAR_DIR}"
    dvrescue "${DVFILE}" > "${DVRESCUE_XML}"
  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
}

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

while [[ "${@}" != "" ]] ; do
  DVFILE="${1}"
  shift
  BASENAME="$(basename "${DVFILE}")"
  if [[ -n "${USER_OUTPUT}" ]] && [[ -d "${USER_OUTPUT}" ]] ; then
    SIDECAR_DIR="${USER_OUTPUT}/${BASENAME}_dvrescue"
  elif [[ -n "${USER_OUTPUT}" ]] ; then
    echo "User selected output is not a valid directory. Exiting"
    exit 1
  else
    SIDECAR_DIR="${DVFILE}_dvrescue"
  fi
  DVRESCUE_XML="${SIDECAR_DIR}/${BASENAME}.dvrescue.xml"

  if [[ "${ERROR_REVIEW}" = "Y" ]] || [[ "${GIF_OUTPUT}" = "Y" ]] ; then
    _make_dvrescue_xml
    echo "Making jpegs of errors within $(basename "${DVFILE}")..."
    TOTAL_FRAMES="$(xmlstarlet sel -N d="https://mediaarea.net/dvrescue" -t -m "d:dvrescue/d:media" -v "sum(d:frames/@count)" -n "${DVRESCUE_XML}")"
    COUNTER=1
    xmlstarlet 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_C="$(echo "${PTS}" | sed 's|:|-|g')"
      PTS_E="$(echo "${PTS}" | sed 's|:|\\:|g')"
      if [[ "${GIF_OUTPUT}" = "Y" ]] ; then
        _mkdir2 "${SIDECAR_DIR}/dvrescue_tmp"
        JPG_OUTPUT="${SIDECAR_DIR}/dvrescue_tmp/${BASENAME}_$(echo "$COUNTER" | awk '{ printf("%06i", $1) }').jpg"
      else
        JPG_OUTPUT="${SIDECAR_DIR}/${BASENAME}_${TC_FILENAME_SAFE}.jpg"
      fi
      ffmpeg -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 -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}'" "${JPG_OUTPUT}"
      ((COUNTER=COUNTER+1))
    done
    if [[ "${GIF_OUTPUT}" = "Y" ]] ; then
      GIF_PALETTE="$(_maketemp)"
      ffmpeg -nostdin -f image2 -i "${SIDECAR_DIR}/dvrescue_tmp/${BASENAME}_%06d.jpg" -vf palettegen "${GIF_PALETTE}.png"
      ffmpeg -nostdin -f image2 -i "${SIDECAR_DIR}/dvrescue_tmp/${BASENAME}_%06d.jpg" -i "${GIF_PALETTE}.png" -filter_complex paletteuse "${SIDECAR_DIR}/${BASENAME}_error.gif"
      rm -r "${SIDECAR_DIR}/dvrescue_tmp" "${GIF_PALETTE}.png"
    else
      xsltproc /tmp/dvplay_display.xsl "${DVRESCUE_XML}" > "${DVRESCUE_XML%.*}.html"
    fi
  else
    _play_dv "${DVFILE}"
  fi
done
