#!/bin/bash
# Reference final-media audit delegate for SDTK_MARKETING_VIDEO_AUDIT_CMD.
#
# Usage:
#   export SDTK_MARKETING_VIDEO_AUDIT_CMD='/absolute/path/reference-video-audit.sh {out}'
#
# Prints one JSON object with video and audio measurements. It never reads credentials, uploads,
# modifies media, or writes outside a temporary directory.
set -eu

file="${1:?usage: reference-video-audit.sh <video-file>}"
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height,r_frame_rate:format=duration -of json "$file" > "$tmpdir/meta.json"
ffprobe -v error -select_streams a -show_entries stream=index -of csv=p=0 "$file" > "$tmpdir/audio-streams.txt" || true
ffmpeg -hide_banner -i "$file" -vf "scale=320:-1,scdet=s=1:t=0,metadata=print:file=$tmpdir/mafd.txt" -an -f null - >/dev/null 2>&1
ffmpeg -hide_banner -i "$file" -vf "fps=2,scale=480:-1,edgedetect=low=0.1:high=0.3,signalstats,metadata=print:file=$tmpdir/edge.txt" -an -f null - >/dev/null 2>&1
ffmpeg -hide_banner -i "$file" -vf "fps=2,scale=480:-1,signalstats,metadata=print:file=$tmpdir/luma.txt" -an -f null - >/dev/null 2>&1

if [ -s "$tmpdir/audio-streams.txt" ]; then
  ffmpeg -hide_banner -i "$file" -af "loudnorm=I=-16:TP=-1:LRA=11:print_format=json" -f null - >/dev/null 2> "$tmpdir/loudnorm.txt"
  ffmpeg -hide_banner -i "$file" -af "silencedetect=n=-50dB:d=1.5" -f null - >/dev/null 2> "$tmpdir/silence.txt"
else
  : > "$tmpdir/loudnorm.txt"
  : > "$tmpdir/silence.txt"
fi

node - "$tmpdir" <<'NODEJS'
const fs = require('fs');
const path = require('path');
const root = process.argv[2];
const text = (name) => fs.readFileSync(path.join(root, name), 'utf8');
const values = (name, key) => [...text(name).matchAll(new RegExp(key + '=([0-9.]+)', 'g'))].map((m) => Number(m[1]));
const mean = (items) => items.length ? items.reduce((sum, item) => sum + item, 0) / items.length : null;
const median = (items) => {
  if (!items.length) return null;
  const sorted = [...items].sort((a, b) => a - b);
  const mid = Math.floor(sorted.length / 2);
  return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
};
const meta = JSON.parse(text('meta.json'));
const stream = meta.streams && meta.streams[0] || {};
const [numerator, denominator] = String(stream.r_frame_rate || '0/1').split('/').map(Number);
const mafd = values('mafd.txt', 'lavfi\\.scd\\.mafd');
const edge = values('edge.txt', 'lavfi\\.signalstats\\.YAVG');
const luma = values('luma.txt', 'lavfi\\.signalstats\\.YAVG');
const audioStreamCount = text('audio-streams.txt').trim() ? text('audio-streams.txt').trim().split(/\s+/).length : 0;
let integratedLufs = null;
let truePeak = null;
if (audioStreamCount) {
  const match = text('loudnorm.txt').match(/\{\s*"input_i"[\s\S]*?\}/);
  if (match) {
    const loudness = JSON.parse(match[0]);
    integratedLufs = Number(loudness.input_i);
    truePeak = Number(loudness.input_tp);
  }
}
const starts = [...text('silence.txt').matchAll(/silence_start: ([0-9.]+)/g)].map((m) => Number(m[1]));
const ends = [...text('silence.txt').matchAll(/silence_end: ([0-9.]+)/g)].map((m) => Number(m[1]));
const silenceIntervals = starts.map((start, index) => {
  const end = ends[index];
  return Number.isFinite(end) ? { start_seconds: start, end_seconds: end, duration_seconds: end - start } : { start_seconds: start, duration_seconds: null };
});
process.stdout.write(JSON.stringify({
  video: {
    width: Number(stream.width), height: Number(stream.height),
    fps: denominator ? numerator / denominator : null,
    duration_seconds: Number(meta.format && meta.format.duration),
    median_mafd: median(mafd),
    frozen_ratio: mafd.length ? mafd.filter((value) => value < 0.5).length / mafd.length : null,
    edge_density: mean(edge), luma: mean(luma),
  },
  audio: { stream_count: audioStreamCount, integrated_lufs: integratedLufs, true_peak_dbtp: truePeak, silence_intervals: silenceIntervals },
}) + '\n');
NODEJS
