chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:35 +08:00
commit 85453da49f
4031 changed files with 710987 additions and 0 deletions
+107
View File
@@ -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
+358
View File
@@ -0,0 +1,358 @@
#!/usr/bin/env python3
"""Lint a Remotion project for patterns that don't translate cleanly to HyperFrames.
The skill should run this *before* attempting a translation. If any blocker
findings come back, the recommendation is to use the runtime interop pattern
from PR #214 instead of producing broken HTML.
Usage:
lint_source.py <path-to-remotion-src> [--json]
Output (default human-readable, --json for machine-readable):
For each .ts/.tsx file, a list of findings with:
- severity: blocker | warning | info
- line, column
- rule id
- message
- recommendation
Blockers (skill should refuse to translate):
- r2hf/use-state React state machine drives animation
- r2hf/use-effect-deps useEffect/useLayoutEffect with non-empty deps (side effects)
- r2hf/use-reducer useReducer drives animation
- r2hf/async-metadata calculateMetadata returns a Promise
- r2hf/third-party-react-ui Imports a React UI library (shadcn, mui, antd, mantine, chakra)
Warnings (translate but flag — drop the construct, keep the rest):
- r2hf/lambda-import @remotion/lambda configuration — drop, HF is single-machine
- r2hf/delay-render delayRender() — HF handles asset loading differently
- r2hf/use-callback useCallback — usually decorative, drop
- r2hf/use-memo useMemo — usually decorative, drop
- r2hf/custom-hook Custom hook (use*) defined locally; may need manual rewrite
Info (translate and document):
- r2hf/static-file staticFile("x") — convert to relative path
- r2hf/interpolate-colors interpolateColors — translate to GSAP color tween
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from collections.abc import Iterable
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Callable
BLOCKER = "blocker"
WARNING = "warning"
INFO = "info"
THIRD_PARTY_UI_PACKAGES = {
"@mui/material",
"@mui/icons-material",
"@chakra-ui/react",
"@mantine/core",
"antd",
"@shadcn/ui",
"@radix-ui",
"@nextui-org/react",
}
@dataclass
class Finding:
file: str
line: int
column: int
severity: str
rule: str
message: str
recommendation: str
@dataclass
class Rule:
"""A lint rule: a matcher that yields hits, plus the metadata each hit gets.
A matcher is a function `src -> Iterable[(offset, override_message)]`. If
`override_message` is None, the rule's default `message` is used; matchers
that need to embed the matched text (custom-hook name, third-party package
name) return the customized message instead.
"""
rule_id: str
severity: str
matcher: Callable[[str], Iterable[tuple[int, str | None]]]
message: str
recommendation: str
def _regex_matcher(pattern: re.Pattern[str]) -> Callable[[str], Iterable[tuple[int, str | None]]]:
def _match(src: str) -> Iterable[tuple[int, str | None]]:
for m in pattern.finditer(src):
yield m.start(), None
return _match
def _use_effect_with_deps(src: str) -> Iterable[tuple[int, str | None]]:
# Find use(Layout)?Effect(, walk to its matching ), and check if the call
# ends with `, [<non-empty>])`. Empty `[]` is mount-only, allowed.
for m in re.finditer(r"\buse(?:Layout)?Effect\s*\(", src):
end = _find_matching_paren(src, m.end() - 1)
if end is None:
continue
call = src[m.start() : end + 1]
m2 = re.search(r",\s*\[([^\]]*)\]\s*$", call[:-1])
if m2 and m2.group(1).strip():
yield m.start(), None
_CUSTOM_HOOK_DECL = re.compile(
r"^\s*(?:export\s+(?:default\s+)?)?(?:function|const|let|var)\s+(use[A-Z]\w+)\b",
re.MULTILINE,
)
_REMOTION_BUILTIN_HOOKS = {"useCurrentFrame", "useVideoConfig"}
def _custom_hook(src: str) -> Iterable[tuple[int, str | None]]:
for m in _CUSTOM_HOOK_DECL.finditer(src):
name = m.group(1)
if name in _REMOTION_BUILTIN_HOOKS:
continue
yield m.start(), f"Custom hook `{name}` defined locally — may need manual rewrite"
_IMPORT_FROM = re.compile(r"from\s+['\"]([^'\"]+)['\"]")
def _third_party_react_ui(src: str) -> Iterable[tuple[int, str | None]]:
for m in _IMPORT_FROM.finditer(src):
pkg = m.group(1)
if any(pkg.startswith(p) for p in THIRD_PARTY_UI_PACKAGES):
yield m.start(), f"Imports `{pkg}` — third-party React UI library has no HF equivalent"
RULES: list[Rule] = [
Rule(
"r2hf/use-state",
BLOCKER,
_regex_matcher(re.compile(r"\buseState\s*[(<]")),
"useState detected — Remotion compositions that drive animation via React state are not deterministic frame-capture targets in HyperFrames",
"Use the runtime interop pattern from PR #214 instead of attempting a translation",
),
Rule(
"r2hf/use-reducer",
BLOCKER,
_regex_matcher(re.compile(r"\buseReducer\s*[(<]")),
"useReducer detected — same issue as useState",
"Use the runtime interop pattern from PR #214",
),
Rule(
"r2hf/use-effect-deps",
BLOCKER,
_use_effect_with_deps,
"useEffect/useLayoutEffect with non-empty deps — side effects don't translate to HF's seek-driven model",
"Move the side-effect work into a build step, or use the runtime interop pattern",
),
Rule(
"r2hf/async-metadata",
BLOCKER,
_regex_matcher(
re.compile(
r"calculateMetadata[^=]*=\s*async\b|async\s+calculateMetadata\b|calculateMetadata\s*:\s*async"
)
),
"calculateMetadata returns a Promise — HF needs composition metadata up front",
"Resolve metadata at build time and pass concrete values, or use runtime interop",
),
Rule(
"r2hf/third-party-react-ui",
BLOCKER,
_third_party_react_ui,
"Imports a third-party React UI library — no HF equivalent",
"Use runtime interop, or rewrite the affected components as HTML+CSS",
),
# Lambda is a warning, not a blocker: it's deployment config, orthogonal
# to the rendered composition. The skill drops the import and translates
# the rest. See references/escape-hatch.md.
Rule(
"r2hf/lambda-import",
WARNING,
_regex_matcher(re.compile(r"from\s+['\"]@remotion/lambda['\"]")),
"@remotion/lambda is Remotion-specific distributed rendering — no HF equivalent today",
"Drop the Lambda config; HF runs single-machine. Document the gap in TRANSLATION_NOTES.md.",
),
Rule(
"r2hf/delay-render",
WARNING,
_regex_matcher(re.compile(r"\bdelayRender\s*\(")),
"delayRender() — HF waits on asset readiness via the Frame Adapter pattern",
"Drop the call; HF handles this transparently",
),
Rule(
"r2hf/use-callback",
WARNING,
_regex_matcher(re.compile(r"\buseCallback\s*\(")),
"useCallback — typically decorative for render performance, no HF equivalent needed",
"Drop the wrapper, inline the function",
),
Rule(
"r2hf/use-memo",
WARNING,
_regex_matcher(re.compile(r"\buseMemo\s*\(")),
"useMemo — typically decorative, no HF equivalent needed",
"Drop the wrapper, compute inline",
),
Rule(
"r2hf/custom-hook",
WARNING,
_custom_hook,
"Custom hook defined locally — may need manual rewrite",
"Inline the hook body if pure; bow out to runtime interop if it uses useState/useEffect",
),
Rule(
"r2hf/static-file",
INFO,
_regex_matcher(re.compile(r"\bstaticFile\s*\(")),
"staticFile() reference — convert to a relative path in the HF composition",
"Replace `staticFile(\"x.png\")` with `\"x.png\"` and copy the asset alongside the HTML",
),
Rule(
"r2hf/interpolate-colors",
INFO,
_regex_matcher(re.compile(r"\binterpolateColors\s*\(")),
"interpolateColors() — translate to a GSAP color tween",
"See references/timing.md for the GSAP equivalent",
),
]
def _find_matching_paren(src: str, open_idx: int) -> int | None:
"""Given the index of an open `(`, return the index of its matching `)`.
Skips parens that appear inside `'...'`, `"..."`, or `` `...` `` string
literals. Returns None if no matching close paren is found.
This is good enough for hand-written Remotion source. It does not handle
template-literal interpolations `${...}` recursively or comments — both
are uncommon in Remotion code we expect to lint and would only matter
if the unbalanced paren landed inside such a region.
"""
if open_idx >= len(src) or src[open_idx] != "(":
return None
depth = 0
i = open_idx
in_str: str | None = None
while i < len(src):
c = src[i]
if in_str is not None:
if c == "\\":
i += 2
continue
if c == in_str:
in_str = None
i += 1
continue
if c in ("'", '"', "`"):
in_str = c
i += 1
continue
if c == "(":
depth += 1
elif c == ")":
depth -= 1
if depth == 0:
return i
i += 1
return None
def lint_file(path: Path) -> list[Finding]:
src = path.read_text()
findings: list[Finding] = []
def loc(offset: int) -> tuple[int, int]:
line = src.count("\n", 0, offset) + 1
col = offset - (src.rfind("\n", 0, offset) + 1) + 1
return line, col
for rule in RULES:
for offset, override_message in rule.matcher(src):
line, col = loc(offset)
findings.append(
Finding(
str(path),
line,
col,
rule.severity,
rule.rule_id,
override_message or rule.message,
rule.recommendation,
)
)
findings.sort(key=lambda f: (f.file, f.line, f.column))
return findings
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("path", type=Path, help="Directory or file to lint")
ap.add_argument("--json", action="store_true", help="Emit JSON instead of human-readable output")
args = ap.parse_args()
if not args.path.exists():
print(f"error: {args.path} does not exist", file=sys.stderr)
return 2
files: list[Path]
if args.path.is_file():
files = [args.path]
else:
files = sorted(
p
for p in args.path.rglob("*")
if p.is_file()
and p.suffix in {".ts", ".tsx", ".jsx", ".js"}
and "node_modules" not in p.parts
)
all_findings: list[Finding] = []
for f in files:
all_findings.extend(lint_file(f))
blockers = sum(1 for f in all_findings if f.severity == BLOCKER)
warnings = sum(1 for f in all_findings if f.severity == WARNING)
infos = sum(1 for f in all_findings if f.severity == INFO)
if args.json:
json.dump(
{
"files_scanned": len(files),
"blockers": blockers,
"warnings": warnings,
"infos": infos,
"findings": [asdict(f) for f in all_findings],
},
sys.stdout,
indent=2,
)
sys.stdout.write("\n")
else:
for f in all_findings:
print(f"{f.file}:{f.line}:{f.column} [{f.severity}] {f.rule}: {f.message}")
print(f" -> {f.recommendation}")
print()
print(f"{len(files)} files scanned · {blockers} blocker · {warnings} warning · {infos} info")
if blockers:
print("RECOMMENDATION: do not attempt translation. Use the runtime interop pattern from PR #214.")
return 1 if blockers else 0
if __name__ == "__main__":
sys.exit(main())
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# render_diff.sh — compute per-frame SSIM between two video files.
#
# The eval primitive for the remotion-to-hyperframes skill: given a Remotion
# render and a HyperFrames render of the same composition, report whether the
# translation is visually equivalent.
#
# Usage:
# render_diff.sh <baseline.mp4> <translated.mp4> [output-dir]
#
# Output (in output-dir, defaults to ./diff-out):
# ssim.log — per-frame SSIM lines from ffmpeg
# summary.json — { mean, min, p05, p95, frame_count, pass, threshold }
#
# Exit codes:
# 0 — pass (mean SSIM >= threshold)
# 1 — fail (mean SSIM < threshold)
# 2 — usage / setup error
#
# Threshold defaults to 0.85 (loose; tier-specific thresholds are applied by
# the orchestrator). Override with R2HF_SSIM_THRESHOLD=0.95 in the environment.
set -euo pipefail
THRESHOLD="${R2HF_SSIM_THRESHOLD:-0.85}"
if [[ $# -lt 2 || $# -gt 3 ]]; then
echo "usage: $0 <baseline.mp4> <translated.mp4> [output-dir]" >&2
exit 2
fi
BASELINE="$1"
TRANSLATED="$2"
OUTDIR="${3:-./diff-out}"
if [[ ! -f "$BASELINE" ]]; then
echo "error: baseline not found: $BASELINE" >&2
exit 2
fi
if [[ ! -f "$TRANSLATED" ]]; then
echo "error: translated not found: $TRANSLATED" >&2
exit 2
fi
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "error: ffmpeg not on PATH" >&2
exit 2
fi
mkdir -p "$OUTDIR"
SSIM_LOG="$OUTDIR/ssim.log"
SUMMARY="$OUTDIR/summary.json"
# ffmpeg's ssim filter writes one line per frame to stats_file and a single
# Mean SSIM line to stderr. We capture both — per-frame for distribution
# stats, and the mean for the headline number.
ffmpeg -hide_banner -nostats -loglevel info \
-i "$BASELINE" -i "$TRANSLATED" \
-lavfi "[0:v]scale=iw:ih[ref];[1:v]scale=iw:ih[main];[main][ref]ssim=stats_file=$SSIM_LOG" \
-f null - 2>"$OUTDIR/ffmpeg.stderr"
# Parse: each line in ssim.log looks like
# n:1 Y:0.987655 U:0.992345 V:0.991234 All:0.989012 (19.512345)
# We want the All:N column.
python3 - "$SSIM_LOG" "$SUMMARY" "$THRESHOLD" <<'PY'
import json, math, re, sys
from pathlib import Path
log_path = Path(sys.argv[1])
out_path = Path(sys.argv[2])
threshold = float(sys.argv[3])
values = []
pattern = re.compile(r"All:([\d.]+)")
for line in log_path.read_text().splitlines():
m = pattern.search(line)
if m:
try:
values.append(float(m.group(1)))
except ValueError:
pass
if not values:
print(f"error: no SSIM samples parsed from {log_path}", file=sys.stderr)
sys.exit(2)
values.sort()
n = len(values)
mean = sum(values) / n
p_idx = lambda p: min(n - 1, max(0, int(math.floor(p * n))))
summary = {
"frame_count": n,
"mean": round(mean, 6),
"min": round(values[0], 6),
"max": round(values[-1], 6),
"p05": round(values[p_idx(0.05)], 6),
"p95": round(values[p_idx(0.95)], 6),
"threshold": threshold,
"pass": bool(mean >= threshold),
}
out_path.write_text(json.dumps(summary, indent=2) + "\n")
print(json.dumps(summary, indent=2))
sys.exit(0 if summary["pass"] else 1)
PY
@@ -0,0 +1,50 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import { useCurrentFrame, AbsoluteFill, delayRender, continueRender } from "remotion";
import { Button } from "@mui/material";
// Custom hook in `export const useFoo = ...` form — earlier custom-hook
// regex anchored to `^\s*(?:function|const|let)` and missed the `export`
// prefix. This covers the regression.
export const useFadeMixed = (n: number) => {
const f = useCurrentFrame();
return f / n;
};
export const BadComposition: React.FC = () => {
const frame = useCurrentFrame();
const [data, setData] = useState<string | null>(null);
const [handle] = useState(() => delayRender());
// Multi-line useEffect body with commas inside (fillRect args) — regression
// coverage for r2hf/use-effect-deps. An earlier regex `[^,]+` would stop at
// the first comma inside the body and miss the deps array entirely.
useEffect(() => {
fetch("/api/data")
.then((r) => r.json())
.then((d) => {
const ctx = document.createElement("canvas").getContext("2d");
ctx?.fillRect(0, 0, 100, 100);
setData(d.text);
continueRender(handle);
});
}, [handle]);
// Expression-bodied useEffect — the form `useEffect(() => fetch(...), [deps])`
// has no closing `}`, which an earlier regex anchored on. This and the
// useLayoutEffect below cover the false-negative cases Miguel surfaced.
useEffect(() => fetch("/api/heartbeat"), [frame]);
useLayoutEffect(() => (document.title = `frame ${frame}`), [frame]);
return (
<AbsoluteFill>
<Button>{data ?? "loading"}</Button>
<span>{frame}</span>
</AbsoluteFill>
);
};
export const calculateMetadata = async () => {
const res = await fetch("/api/duration");
const { duration } = await res.json();
return { durationInFrames: duration };
};
@@ -0,0 +1,46 @@
import React, { useEffect } from "react";
import {
useCurrentFrame,
useVideoConfig,
AbsoluteFill,
interpolate,
spring,
Sequence,
staticFile,
Audio,
Img,
} from "remotion";
// Mount-only useEffect with empty deps + a later expression containing a
// non-empty array — regression coverage for the over-match Miguel reported:
// the earlier regex spanned past `[]` and matched `[frame]` from `pick(...)`,
// falsely flagging this clean fixture as having a blocker.
function pick<T>(_key: string, items: T[]): T {
return items[0];
}
const TitleCard: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
useEffect(() => {
console.log("mounted");
}, []);
const _picked = pick("x", [frame]);
const opacity = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: "clamp" });
const scale = spring({ frame, fps, config: { damping: 12 } });
return (
<AbsoluteFill style={{ justifyContent: "center", alignItems: "center" }}>
<div style={{ fontSize: 72, opacity, transform: `scale(${scale})` }}>Hello</div>
<Img src={staticFile("logo.png")} />
</AbsoluteFill>
);
};
export const MyComposition: React.FC = () => (
<AbsoluteFill>
<Sequence from={0} durationInFrames={90}>
<TitleCard />
</Sequence>
<Audio src={staticFile("music.mp3")} volume={0.5} />
</AbsoluteFill>
);
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# smoke.sh — exercise the eval harness scripts against synthetic inputs.
#
# Generates two synthetic videos with ffmpeg's testsrc filter, runs render_diff
# and frame_strip against them, and runs lint_source against fixture .tsx files.
# Asserts the harness produces sensible output without depending on a real
# Remotion or HyperFrames render pipeline being installed.
#
# Usage: ./smoke.sh
# Exit 0 on pass.
set -euo pipefail
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPTS_DIR="$(cd "$THIS_DIR/.." && pwd)"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
echo "==> smoke: render_diff.sh against identical inputs"
# Generate the same test pattern twice. Identical inputs → SSIM should be ~1.0.
ffmpeg -y -hide_banner -loglevel error \
-f lavfi -i "testsrc=duration=2:size=320x240:rate=30" \
-pix_fmt yuv420p "$WORK/baseline.mp4"
cp "$WORK/baseline.mp4" "$WORK/translated.mp4"
R2HF_SSIM_THRESHOLD=0.99 "$SCRIPTS_DIR/render_diff.sh" \
"$WORK/baseline.mp4" "$WORK/translated.mp4" "$WORK/diff" >/dev/null
MEAN=$(python3 -c "import json,sys; print(json.load(open('$WORK/diff/summary.json'))['mean'])")
PASS=$(python3 -c "import json,sys; print(json.load(open('$WORK/diff/summary.json'))['pass'])")
if [[ "$PASS" != "True" ]]; then
echo "FAIL: identical inputs failed pass check (mean=$MEAN)"
exit 1
fi
echo " identical inputs → mean SSIM=$MEAN (pass=True)"
echo "==> smoke: render_diff.sh against different inputs"
# Different test pattern → SSIM should be lower. With a high threshold it should fail.
ffmpeg -y -hide_banner -loglevel error \
-f lavfi -i "testsrc2=duration=2:size=320x240:rate=30" \
-pix_fmt yuv420p "$WORK/different.mp4"
set +e
R2HF_SSIM_THRESHOLD=0.99 "$SCRIPTS_DIR/render_diff.sh" \
"$WORK/baseline.mp4" "$WORK/different.mp4" "$WORK/diff2" >/dev/null
RC=$?
set -e
if [[ "$RC" -eq 0 ]]; then
echo "FAIL: different inputs unexpectedly passed at threshold 0.99"
exit 1
fi
DIFF_MEAN=$(python3 -c "import json; print(json.load(open('$WORK/diff2/summary.json'))['mean'])")
echo " different inputs → mean SSIM=$DIFF_MEAN (correctly failed at 0.99)"
echo "==> smoke: frame_strip.sh produces a strip"
"$SCRIPTS_DIR/frame_strip.sh" "$WORK/baseline.mp4" "$WORK/different.mp4" "$WORK/strip" 4 >/dev/null
if [[ ! -f "$WORK/strip/strip.png" ]]; then
echo "FAIL: frame_strip.sh did not produce strip.png"
exit 1
fi
echo " strip.png written ($(stat -c%s "$WORK/strip/strip.png" 2>/dev/null || stat -f%z "$WORK/strip/strip.png") bytes)"
echo "==> smoke: lint_source.py on clean fixture (expect exit 0)"
set +e
python3 "$SCRIPTS_DIR/lint_source.py" "$THIS_DIR/fixtures/clean.tsx" --json >"$WORK/clean.json"
RC=$?
set -e
BLOCKERS=$(python3 -c "import json; print(json.load(open('$WORK/clean.json'))['blockers'])")
if [[ "$RC" -ne 0 || "$BLOCKERS" -ne 0 ]]; then
echo "FAIL: clean fixture reported $BLOCKERS blockers (rc=$RC)"
exit 1
fi
INFOS=$(python3 -c "import json; print(json.load(open('$WORK/clean.json'))['infos'])")
echo " clean.tsx → 0 blockers, $INFOS info findings"
echo "==> smoke: lint_source.py on blocker fixture (expect exit 1)"
set +e
python3 "$SCRIPTS_DIR/lint_source.py" "$THIS_DIR/fixtures/blocker.tsx" --json >"$WORK/blocker.json"
RC=$?
set -e
BLOCKERS=$(python3 -c "import json; print(json.load(open('$WORK/blocker.json'))['blockers'])")
if [[ "$RC" -eq 0 || "$BLOCKERS" -lt 3 ]]; then
echo "FAIL: blocker fixture reported $BLOCKERS blockers, expected >=3 (rc=$RC)"
cat "$WORK/blocker.json"
exit 1
fi
echo " blocker.tsx → $BLOCKERS blockers detected (correctly refused)"
echo
echo "✅ smoke tests passed"