Skip to content

Commit 5d264e1

Browse files
jrusso1020claude
andauthored
docs(lambda): document webm support + simplify-review fixes (#953)
* docs(lambda): document webm support in distributed mode PR 8.4 of the WebM distributed-rendering plan (v1.5 backlog #1; see DISTRIBUTED-RENDERING-PLAN.md §7.2). User-facing docs catch up with the shipped capability. Updates docs/deploy/migrating-to-hyperframes-lambda.mdx: - "Output format" row in the migration table now lists `webm` alongside mp4 / mov / png-sequence with a note that webm uses libvpx-vp9 + closed-GOP concat-copy. HDR mp4 remains the only refused format. - "No webm distributed" caveat replaced with "webm uses closed-GOP VP9" explainer covering the encoder args (`-g <chunkSize>`, `-keyint_min <chunkSize>`, `-auto-alt-ref 0`, `-cpu-used 2`), why alt-ref disable is load-bearing, and that the output preserves alpha via yuva420p with Opus audio. - Migration checklist no longer asks adopters to filter out webm compositions; only HDR-dependent renders need to stay on the previous framework. aws-lambda.mdx doesn't currently call out webm as unsupported (only HDR in the v1 surface list), so it gets no copy edits beyond the migration guide. The internal planning doc (DISTRIBUTED-RENDERING-PLAN.md §7.2, §8, §12 — kept outside the repo) gets matching updates: format support matrix flipped ✓, v1.5 backlog #1 marked shipped, HDR promoted to the new top item, and the rev-12 → rev-13 status line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: address simplify-review findings on webm stack Folds in cleanups identified by a multi-agent code-review pass over the 4-PR webm-distributed stack: - plan.ts: `resolveEncoderTriple()` webm case now calls `getEncoderPreset(quality, "webm")` for its preset string instead of hardcoding "good". The hardcode was wrong for `quality: "draft"` (`getEncoderPreset` returns "realtime" for that tier) — would have silently overridden the draft → realtime mapping for distributed webm renders. - chunkEncoder.ts: trim the new VP9 closed-GOP comment block from ~18 lines of WHY narration down to the 6 lines that actually explain why (alt-ref + cpu-used drift). Match the alpha branch's idempotent-push comment to the same standard. - chunkEncoder.test.ts: drop the duplicate WHY comment that restated the implementation comment in plain words. - webm-concat-copy.test.ts: rewrite the file-header docstring to describe the contract being tested instead of the PR-8.1-gating history; strip "PR 8.2 / Path A / Path B" references from error messages (they belong in PR bodies, not in test output). Consolidate the yuva420p alpha smoke into a single `it()` block (was a full 4-test describe with duplicated setup) — the yuv420p block already covers the probe/decode/frame-count contract; the alpha smoke only needs to prove the alpha args don't break concat-copy. - plan.test.ts: drop the "PR 8.1 proved the contract" comment. - webm-vp9 fixture: drop the aspirational "Other webm-with-audio fixtures cover the mux path separately when added" sentence (no other fixtures exist). Regenerated the baseline via `docker:test:update webm-vp9` to reflect the updated comment. - migrating-to-hyperframes-lambda.mdx: add a paragraph about distributed webm's perf cost — ~10-25% larger files at constant CRF due to forced keyframes, and slower per-chunk encode due to `-cpu-used 2` being more conservative than the libvpx default. All unit tests + the webm-vp9 distributed-simulated regression still pass after these changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(cli): accept --format=webm in `hyperframes lambda render` The CLI's `lambda render` subcommand's FORMATS allowlist and the `RenderArgs.format` type still narrowed to `mp4 | mov | png-sequence`, so even though the producer + aws-lambda packages now support webm end-to-end, the CLI surface rejected it with `--format must be mp4|mov| png-sequence`. Add webm to both spots and update the --help description. Surfaced during real-AWS deploy prep — the local lambda-local / distributed-simulated tests didn't go through the CLI so the gap went unnoticed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(producer): font cache writes to /tmp on Lambda (read-only \$HOME) The deterministic Google Fonts cache was rooted at `\$HOME/.cache/hyperframes/fonts`, which fails on AWS Lambda — the runtime's `\$HOME` resolves to a `/home/sbx_*` directory tree that's read-only. `mkdirSync(..., { recursive: true })` can't create that path and the plan stage trips with `ENOENT: no such file or directory, mkdir '/home/sbx_user1051/.cache/hyperframes/fonts/space-mono'` on every Lambda render that pulls a Google Font (i.e. every distributed fixture using `@import url("https://fonts.googleapis.com/...")`). Detect Lambda via `\$AWS_LAMBDA_FUNCTION_NAME` and route the cache to `tmpdir()/hyperframes/fonts` in that case. Lambda's `/tmp` survives across invocations on a warm container, so cache hit rate is the same as non-Lambda runs. Also honor an explicit `\$HYPERFRAMES_FONT_CACHE_DIR` override for adopters who want a different location regardless of the runtime. Surfaced while verifying webm distributed end-to-end on real AWS — the same bug affects mp4 fixtures using Google Fonts; webm just happened to be the one I tried first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: extract DistributedFormat type + trim font-cache resolver Second simplify-review pass on the webm stack flagged two cleanups: 1. **`DistributedFormat` type duplicated 10 times.** Every file in the distributed pipeline carried its own copy of `"mp4" | "mov" | "png-sequence" | "webm"` — adding a new format meant a 10-place edit with no compile-time guarantee they stayed in sync. Extract a single source of truth in `packages/producer/src/services/distributed/shared.ts`, re-export from `@hyperframes/producer/distributed` and `@hyperframes/aws-lambda/sdk`, and have all callers pull from there. The aws-lambda `ALLOWED_FORMATS` runtime tuple and the CLI's `FORMATS` tuple now both use `satisfies readonly DistributedFormat[]` so the compiler enforces the runtime allowlist stays in sync with the type. 2. **`deterministicFonts.ts` font-cache resolver was over-commented.** Trim the 7-line block to 4 lines (drop the aspirational "and other read-only-FS execution environments" — only Lambda is detected — and the warm-container `/tmp` persistence narration — anyone reading already knows Lambda /tmp semantics). Collapse the two-step `if (explicit && explicit.length > 0)` into a single nullish-coalesce expression now that the empty-string defensive check is gone (`process.env.X` is `string | undefined`, no third shape to guard against). Out-of-scope skips (called out by the agents, deferred): - In-process `RenderConfig.format` and the in-process CLI's `render.ts` format union still carry their own inline copies. The union happens to coincide today but they're separate concerns — leaving them alone limits this PR's blast radius. - `fontCacheDir(slug)` / `resolveFontCacheRoot()` naming asymmetry flagged as taste; skipping. - Pre-existing redundant `existsSync` before `mkdirSync({ recursive: true })` in `fontCacheDir` — out of scope. All tests + typecheck still pass. Lambda render still works end-to-end (no functional changes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(lambda): drop plan-doc reference from migration checklist PR review feedback: source/docs should not mention the distributed-rendering planning doc. Tighten the migration checklist sentence to describe the webm path directly rather than referencing the doc's version label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(producer): split resolveEncoderTriple into mp4 + non-mp4 helpers CI Fallow audit on PR #953 flagged `resolveEncoderTriple` at CRAP 31.6 — the function interleaved (a) mp4 codec validation + dispatch, (b) the non-mp4 codec-rejection throw, and (c) per-format dispatch. Splitting into `resolveMp4EncoderTriple` + `resolveNonMp4EncoderTriple` drops the top-level function's cyclomatic complexity below the threshold while preserving every error message and code path. Behavior unchanged. Also extracts an `EncoderTriple` type alias so the three functions share the return shape declaratively rather than repeating it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6d2569c commit 5d264e1

25 files changed

Lines changed: 337 additions & 351 deletions

docs/deploy/migrating-to-hyperframes-lambda.mdx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Most adopters' render config maps directly:
4242
| `fps` | `--fps=30` (CLI) or `config.fps` (SDK) | 24, 30, 60 only — non-integer NTSC rationals are an in-process-only feature. |
4343
| `width` / `height` | `--width` / `--height` flags, or `config.width` / `config.height` | Even integers ≤ 7680 (yuv420p parity). |
4444
| `codec: 'h264' / 'h265'` | `--codec=h264` or `--codec=h265` (mp4 only) | h265 uses libx265 with closed-GOP keyint params so chunked concat-copy round-trips losslessly. |
45-
| Output format | `--format=mp4 / mov / png-sequence` | Distributed mode refuses webm + HDR at plan time. |
45+
| Output format | `--format=mp4 / mov / webm / png-sequence` | webm uses libvpx-vp9 + closed-GOP concat-copy. Distributed mode still refuses HDR mp4 at plan time. |
4646
| Quality preset | `--quality=draft / standard / high` | Maps onto ffmpeg encoder presets. |
4747
| Chunk size in frames | `--chunk-size=240` (default 240) | ~8s at 30 fps; sized to fit Lambda's 15-min cap with headroom. |
4848
| Max parallel chunks | `--max-parallel-chunks=16` (default 16) | Caps the Map state's fan-out. |
@@ -64,9 +64,11 @@ HyperFrames refuses `data-gpu-mode="hardware"` in distributed mode — hardware
6464

6565
`hdrMode: 'force-hdr'` is rejected at plan time. The v1.5 backlog covers HDR mp4 via `-bsf:v hevc_metadata` re-application; for now, HDR renders use the in-process renderer outside Lambda.
6666

67-
### No webm distributed
67+
### webm uses closed-GOP VP9
6868

69-
VP9 in matroska doesn't round-trip cleanly through concat-copy (the moov-atom keyframe assumptions don't hold). webm renders use the in-process renderer or accept a controlled re-encode at the assemble stage — coming in v1.5. The Lambda handler refuses webm with `FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED` so the failure is loud.
69+
webm distributed renders go through libvpx-vp9 with `-g <chunkSize>`, `-keyint_min <chunkSize>`, `-auto-alt-ref 0`, and `-cpu-used 2`. The alt-ref disable is the load-bearing bit: libvpx-vp9's default non-displayable alt-ref frames can land anywhere in a GOP, which breaks concat-copy at chunk seams. Closed-GOP forces a keyframe at every chunk boundary so `ffmpeg -f concat -c copy` round-trips losslessly. Output is `yuva420p` to preserve alpha. Audio is muxed as Opus.
70+
71+
Distributed webm files are typically ~10-25% larger than the same composition rendered in-process at the same CRF, because closed-GOP forces more keyframes than the in-process single-pass would emit. Per-chunk encode is also slower than libvpx-vp9's default speed/quality tradeoff (`-cpu-used 2` is more conservative than the default for `-deadline good`). The single-machine in-process renderer remains the right choice for short webm renders; distributed pays for itself once a render's wall-clock exceeds what one machine delivers.
7072

7173
### State files are local by default
7274

@@ -78,7 +80,7 @@ The default policy doc emitted by `hyperframes lambda policies user/role` uses `
7880

7981
## Migration checklist
8082

81-
1. **Inventory** the compositions you want to migrate. Filter out anything that needs HDR or webm — those stay on your current framework for now.
83+
1. **Inventory** the compositions you want to migrate. Filter out anything that needs HDR — that stays on your current framework for now. webm renders distributed via closed-GOP VP9 + concat-copy (see the webm section above).
8284
2. **Translate** each composition to plain HTML. The `[Concepts](/concepts)` page covers the data-attribute conventions; the `/hyperframes` skill (`npx skills add heygen-com/hyperframes`) makes Claude / Cursor / Codex aware of them too.
8385
3. **Wire** the new composition into your build pipeline alongside the old one. HyperFrames doesn't need an external bundler — you can `npx hyperframes preview` against the HTML directly.
8486
4. **Deploy** in a separate AWS account or with a `--stack-name=hyperframes-staging` first. Run a real render with `--wait`; verify the output bytes.

packages/aws-lambda/src/events.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* results per §2.4).
1717
*/
1818

19-
import type { DistributedRenderConfig } from "@hyperframes/producer/distributed";
19+
import type { DistributedFormat, DistributedRenderConfig } from "@hyperframes/producer/distributed";
2020

2121
/** Discriminator for the three roles the one Lambda image fulfills. */
2222
export type LambdaAction = "plan" | "renderChunk" | "assemble";
@@ -65,7 +65,7 @@ export interface RenderChunkEvent {
6565
/** S3 URI prefix where the chunk output should be uploaded (`s3://bucket/{prefix}/`). */
6666
ChunkOutputS3Prefix: string;
6767
/** Output container format from the plan's encoder.json; drives file vs frame-dir handling. */
68-
Format: "mp4" | "mov" | "png-sequence" | "webm";
68+
Format: DistributedFormat;
6969
}
7070

7171
/** Activity C: fetch planDir + all chunks + audio, assemble, upload final. */
@@ -80,7 +80,7 @@ export interface AssembleEvent {
8080
/** Final output S3 URI (`s3://bucket/key.mp4`). */
8181
OutputS3Uri: string;
8282
/** Output container format; drives file vs frame-dir handling. */
83-
Format: "mp4" | "mov" | "png-sequence" | "webm";
83+
Format: DistributedFormat;
8484
}
8585

8686
/**
@@ -106,7 +106,7 @@ export interface PlanLambdaResult {
106106
Fps: 24 | 30 | 60;
107107
Width: number;
108108
Height: number;
109-
Format: "mp4" | "mov" | "png-sequence" | "webm";
109+
Format: DistributedFormat;
110110
HasAudio: boolean;
111111
AudioS3Uri: string | null;
112112
FfmpegVersion: string;

packages/aws-lambda/src/formatExtension.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
* looks like vs a png-sequence.
77
*/
88

9-
export type DistributedFormat = "mp4" | "mov" | "png-sequence" | "webm";
9+
import type { DistributedFormat } from "@hyperframes/producer/distributed";
10+
11+
export type { DistributedFormat } from "@hyperframes/producer/distributed";
1012

1113
// Closed-enum lookup table. TS enforces exhaustiveness via the
1214
// `Record<DistributedFormat, string>` annotation — adding a format to

packages/aws-lambda/src/handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
renderChunk,
2626
} from "@hyperframes/producer/distributed";
2727
import { resolveChromeExecutablePath } from "./chromium.js";
28-
import { formatExtension } from "./formatExtension.js";
28+
import { type DistributedFormat, formatExtension } from "./formatExtension.js";
2929
import type {
3030
AssembleEvent,
3131
AssembleLambdaResult,
@@ -433,7 +433,7 @@ async function downloadChunkObjects(
433433
s3: S3Client,
434434
uris: string[],
435435
workDir: string,
436-
format: "mp4" | "mov" | "png-sequence" | "webm",
436+
format: DistributedFormat,
437437
): Promise<string[]> {
438438
const chunksDir = join(workDir, "chunks");
439439
mkdirSync(chunksDir, { recursive: true });

packages/aws-lambda/src/sdk/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ export {
2424
} from "./costAccounting.js";
2525
export { InvalidConfigError, validateDistributedRenderConfig } from "./validateConfig.js";
2626
export type { SerializableDistributedRenderConfig } from "../events.js";
27+
export type { DistributedFormat } from "../formatExtension.js";

packages/aws-lambda/src/sdk/validateConfig.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* size cap, GPU mode at runtime) needs the actual planner.
1919
*/
2020

21+
import type { DistributedFormat } from "../formatExtension.js";
2122
import type { SerializableDistributedRenderConfig } from "../events.js";
2223

2324
/** Thrown for any client-side `SerializableDistributedRenderConfig` violation. */
@@ -32,7 +33,12 @@ export class InvalidConfigError extends Error {
3233
}
3334

3435
const ALLOWED_FPS = [24, 30, 60] as const;
35-
const ALLOWED_FORMATS = ["mp4", "mov", "png-sequence", "webm"] as const;
36+
const ALLOWED_FORMATS = [
37+
"mp4",
38+
"mov",
39+
"png-sequence",
40+
"webm",
41+
] as const satisfies readonly DistributedFormat[];
3642
const ALLOWED_CODECS = ["h264", "h265"] as const;
3743
const ALLOWED_QUALITIES = ["draft", "standard", "high"] as const;
3844
const ALLOWED_RUNTIME_CAPS = ["lambda", "temporal", "cloud-run-job", "k8s-job", "none"] as const;

packages/cli/src/commands/lambda.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
import { defineCommand } from "citty";
12+
import type { DistributedFormat } from "@hyperframes/aws-lambda/sdk";
1213
import type { Example } from "./_examples.js";
1314
import { c } from "../ui/colors.js";
1415

@@ -100,7 +101,7 @@ export default defineCommand({
100101
width: { type: "string", description: "Render width in pixels" },
101102
height: { type: "string", description: "Render height in pixels" },
102103
fps: { type: "string", description: "Render fps (24 | 30 | 60)" },
103-
format: { type: "string", description: "mp4 | mov | png-sequence (default: mp4)" },
104+
format: { type: "string", description: "mp4 | mov | png-sequence | webm (default: mp4)" },
104105
codec: { type: "string", description: "h264 | h265 (mp4 only)" },
105106
quality: { type: "string", description: "draft | standard | high" },
106107
"chunk-size": { type: "string", description: "Frames per chunk (default: 240)" },
@@ -325,7 +326,12 @@ function parseEnum<T extends string>(
325326
throw new Error(`${errorPrefix} must be ${allowed.join("|")}; got ${s}`);
326327
}
327328

328-
const FORMATS = ["mp4", "mov", "png-sequence"] as const;
329+
const FORMATS = [
330+
"mp4",
331+
"mov",
332+
"png-sequence",
333+
"webm",
334+
] as const satisfies readonly DistributedFormat[];
329335
const CODECS = ["h264", "h265"] as const;
330336
const QUALITIES = ["draft", "standard", "high"] as const;
331337
const CHROME_SOURCES = ["sparticuz", "chrome-headless-shell"] as const;

packages/cli/src/commands/lambda/render.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
*/
66

77
import { resolve as resolvePath } from "node:path";
8-
import type { SerializableDistributedRenderConfig } from "@hyperframes/aws-lambda/sdk";
8+
import type {
9+
DistributedFormat,
10+
SerializableDistributedRenderConfig,
11+
} from "@hyperframes/aws-lambda/sdk";
912
import { c } from "../../ui/colors.js";
1013
import { requireStack, stateFilePath } from "./state.js";
1114

@@ -23,7 +26,7 @@ export interface RenderArgs {
2326
fps: 24 | 30 | 60;
2427
width: number;
2528
height: number;
26-
format: "mp4" | "mov" | "png-sequence";
29+
format: DistributedFormat;
2730
codec?: "h264" | "h265";
2831
quality?: "draft" | "standard" | "high";
2932
chunkSize?: number;

packages/engine/src/services/chunkEncoder.test.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -551,10 +551,6 @@ describe("buildEncoderArgs lockGopForChunkConcat", () => {
551551
expect(args.indexOf("-x264-params")).toBe(-1);
552552
});
553553

554-
// Closed-GOP for libvpx-vp9 is required to make `ffmpeg -f concat -c copy`
555-
// stitch VP9 chunks losslessly: every chunk's first frame must be an
556-
// independently-decodable keyframe with no alt-ref references reaching
557-
// back across the seam.
558554
it("true appends closed-GOP args for libvpx-vp9", () => {
559555
const args = buildEncoderArgs(
560556
{
@@ -570,16 +566,9 @@ describe("buildEncoderArgs lockGopForChunkConcat", () => {
570566
);
571567
expect(args[args.indexOf("-g") + 1]).toBe("240");
572568
expect(args[args.indexOf("-keyint_min") + 1]).toBe("240");
573-
// Alt-ref frames are non-displayable references that break concat-copy
574-
// at chunk seams; closed-GOP must disable them.
575569
expect(args[args.indexOf("-auto-alt-ref") + 1]).toBe("0");
576-
// cpu-used is locked so workers with different libvpx-vp9 defaults
577-
// produce visually consistent output across chunk boundaries.
578570
expect(args[args.indexOf("-cpu-used") + 1]).toBe("2");
579-
// libvpx-vp9 uses `-deadline good` for non-ultrafast presets — the
580-
// closed-GOP path doesn't change that.
581571
expect(args[args.indexOf("-deadline") + 1]).toBe("good");
582-
// x264/x265-only params must not leak into the VP9 branch.
583572
expect(args.indexOf("-x264-params")).toBe(-1);
584573
expect(args.indexOf("-x265-params")).toBe(-1);
585574
expect(args.indexOf("-sc_threshold")).toBe(-1);

packages/engine/src/services/chunkEncoder.ts

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -255,25 +255,13 @@ export function buildEncoderArgs(
255255
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
256256
args.push("-row-mt", "1");
257257

258-
// Closed-GOP args for distributed chunk concat-copy. Mirrors the
259-
// libx264/libx265 branch above: `lockGopForChunkConcat=true` lays a
260-
// keyframe at every chunk boundary so `ffmpeg -f concat -c copy` can
261-
// stitch sibling chunks losslessly.
262-
//
263-
// VP9-specific: `-auto-alt-ref 0` is mandatory. Alt-ref (a.k.a.
264-
// "ARNR") frames are non-displayable references libvpx-vp9 inserts
265-
// anywhere in the GOP for compression; they break concat-copy at
266-
// chunk seams because the boundary frame is no longer the first
267-
// displayable reference. The alpha branch below already disables
268-
// alt-ref for an unrelated reason (alpha + alt-ref is unsupported);
269-
// closed-GOP extends that to every pixel format.
270-
//
271-
// `-cpu-used 2` pins the libvpx-vp9 speed/quality tradeoff so chunks
272-
// encoded on workers with different default cpu-used values still
273-
// produce visually consistent output across seams. libvpx-vp9's
274-
// default with `-deadline good` has drifted across versions
275-
// historically — locking it makes the planHash round-trip
276-
// deterministic.
258+
// `-auto-alt-ref 0` is mandatory for chunk concat-copy: libvpx-vp9's
259+
// alt-ref frames can reference frames in either direction inside a
260+
// GOP, so a chunk-boundary frame is not guaranteed to be the first
261+
// displayable reference when alt-ref is on. `-cpu-used 2` pins the
262+
// speed/quality tradeoff against libvpx-vp9 default drift across
263+
// versions, so the planHash round-trips deterministically across
264+
// worker images.
277265
const lockGopVp9 = options.lockGopForChunkConcat === true;
278266
if (lockGopVp9) {
279267
if (
@@ -299,10 +287,8 @@ export function buildEncoderArgs(
299287
}
300288
if (pixelFormat === "yuva420p") {
301289
// Alpha + alt-ref is unsupported by libvpx-vp9. The closed-GOP
302-
// branch above already disables alt-ref; only push the flag for
303-
// the non-locked alpha case to keep the args list clean (a second
304-
// `-auto-alt-ref 0` is harmless but noisier in `ffmpeg -loglevel`
305-
// diagnostics).
290+
// branch above already emits `-auto-alt-ref 0`, so skip the
291+
// duplicate push.
306292
if (!lockGopVp9) {
307293
args.push("-auto-alt-ref", "0");
308294
}

0 commit comments

Comments
 (0)