Add HyperFrames skills
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011h6NB8JQtfPc6eRCefEzBB
This commit is contained in:
964 files changed
+142321
No files matched your search
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# frame_strip.sh — produce a side-by-side comparison strip from two videos.
|
||||
#
|
||||
# Used to debug failing render_diff.sh runs visually: pick a sample timestamp
|
||||
# range, extract frames from both videos, lay them out as a grid for review.
|
||||
#
|
||||
# Usage:
|
||||
# frame_strip.sh <baseline.mp4> <translated.mp4> [output-dir] [samples]
|
||||
#
|
||||
# Defaults: output-dir=./strip-out, samples=8 (evenly spaced across duration).
|
||||
# Output:
|
||||
# strip.png — single PNG with `samples` rows, each row is
|
||||
# (baseline frame | translated frame) at one timestamp
|
||||
# timestamps.txt — the timestamps sampled
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 2 || $# -gt 4 ]]; then
|
||||
echo "usage: $0 <baseline.mp4> <translated.mp4> [output-dir] [samples]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
BASELINE="$1"
|
||||
TRANSLATED="$2"
|
||||
OUTDIR="${3:-./strip-out}"
|
||||
SAMPLES="${4:-8}"
|
||||
|
||||
if ! command -v ffmpeg >/dev/null 2>&1 || ! command -v ffprobe >/dev/null 2>&1; then
|
||||
echo "error: ffmpeg/ffprobe not on PATH" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# Build the strip in a single ffmpeg invocation. Two inputs (baseline and
|
||||
# translated) are sampled at N evenly-spaced timestamps via the `select`
|
||||
# filter, then assembled with hstack (per-row pairs) + vstack (rows).
|
||||
# Earlier versions spawned 3 ffmpeg calls per timestamp + a final vstack;
|
||||
# this is one call regardless of N.
|
||||
python3 - "$BASELINE" "$TRANSLATED" "$OUTDIR" "$SAMPLES" <<'PY'
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
baseline, translated, outdir, samples = sys.argv[1], sys.argv[2], Path(sys.argv[3]), int(sys.argv[4])
|
||||
|
||||
# Read fps + duration from the baseline so we can map timestamps to frame
|
||||
# indexes for the `select` filter (frame-accurate, doesn't depend on
|
||||
# keyframe alignment).
|
||||
probe = subprocess.run(
|
||||
["ffprobe", "-v", "error", "-select_streams", "v:0",
|
||||
"-show_entries", "stream=r_frame_rate,nb_read_frames,duration",
|
||||
"-show_entries", "format=duration",
|
||||
"-of", "json", "-count_frames", "--", baseline],
|
||||
check=True, capture_output=True, text=True,
|
||||
)
|
||||
data = json.loads(probe.stdout)
|
||||
stream = data["streams"][0]
|
||||
num, den = stream["r_frame_rate"].split("/")
|
||||
fps = float(num) / float(den)
|
||||
nb_frames = int(stream.get("nb_read_frames") or 0)
|
||||
if nb_frames <= 0:
|
||||
duration = float(stream.get("duration") or data["format"]["duration"])
|
||||
nb_frames = int(duration * fps)
|
||||
|
||||
# Even-spaced sample frames in the 5%-95% window (skip fade-in/out noise).
|
||||
start = max(0, int(nb_frames * 0.05))
|
||||
end = max(start, int(nb_frames * 0.95) - 1)
|
||||
if samples == 1:
|
||||
frames = [start]
|
||||
else:
|
||||
step = (end - start) / (samples - 1)
|
||||
frames = [int(start + i * step) for i in range(samples)]
|
||||
|
||||
(outdir / "timestamps.txt").write_text(
|
||||
"\n".join(f"{f / fps:.3f}" for f in frames) + "\n"
|
||||
)
|
||||
|
||||
# select='eq(n,F1)+eq(n,F2)+...' picks exactly the listed frames from each
|
||||
# input. We then hstack per-frame pairs and vstack the result.
|
||||
select_expr = "+".join(f"eq(n,{f})" for f in frames)
|
||||
n = len(frames)
|
||||
filter_parts = [
|
||||
f"[0:v]select='{select_expr}',setpts=N/FRAME_RATE/TB,split={n}"
|
||||
+ "".join(f"[b{i}]" for i in range(n)),
|
||||
f"[1:v]select='{select_expr}',setpts=N/FRAME_RATE/TB,split={n}"
|
||||
+ "".join(f"[t{i}]" for i in range(n)),
|
||||
]
|
||||
for i in range(n):
|
||||
filter_parts.append(f"[b{i}][t{i}]hstack=inputs=2[row{i}]")
|
||||
filter_parts.append(
|
||||
"".join(f"[row{i}]" for i in range(n)) + f"vstack=inputs={n}[out]"
|
||||
)
|
||||
filter_graph = ";".join(filter_parts)
|
||||
|
||||
cmd = [
|
||||
"ffmpeg", "-y", "-hide_banner", "-loglevel", "error",
|
||||
"-i", baseline, "-i", translated,
|
||||
"-filter_complex", filter_graph,
|
||||
"-map", "[out]", "-frames:v", "1",
|
||||
str(outdir / "strip.png"),
|
||||
]
|
||||
subprocess.run(cmd, check=True)
|
||||
print(f"wrote {outdir / 'strip.png'} ({n} samples)")
|
||||
PY
|
||||
Reference in new issue
Block a user