Commit Graph
301 Commits
Author SHA1 Message Date
James Russo 15aac00704 Merge pull request #684 from TheodorKleynhans/feat/cli-fps-fraction-syntax
feat(cli): accept ffmpeg-style rational fps (NTSC, PAL, slow-mo)
2026-05-11 10:09:57 -04:00
Miguel Ángel d4ba9080f3 Merge pull request #704 from WadydX/fix/init-video-short-flag-conflict
fix(cli): resolve init --video short-flag collision with global -V
2026-05-10 22:43:43 +02:00
WadydX 2778845bd3 fix(cli): handle init -V with explicit migration error 2026-05-10 21:31:17 +01:00
Miguel Ángel 57ea5641fe chore: release v0.5.7 2026-05-10 18:34:43 +00:00
WadydX e61d1fe002 fix(cli): avoid -V collision for init video flag 2026-05-10 19:01:31 +01:00
Miguel Ángel bd7bbae42d chore: release v0.5.6 2026-05-10 05:40:30 +00:00
Theodor Kleynhans 5dcc89c930 feat(cli): accept ffmpeg-style rational fps (NTSC, PAL, slow-mo)
Replaces the rigid `--fps 24|30|60` whitelist with a numeric range and
adds support for ffmpeg-style fractional framerates so NTSC stays exact
end-to-end.

- `--fps 30` keeps working (integer fps)
- `--fps 30000/1001` now means exact NTSC 29.97 (not the lossy decimal)
- `--fps 24000/1001`, `--fps 60000/1001`, `--fps 25/50/120/240` all work
- Decimals like `--fps 29.97` are rejected with a friendly error pointing
  the user at the rational form, since `29.97` and `30000/1001` round
  to different framerates inside ffmpeg

Carries an `Fps = { num: number; den: number }` rational end-to-end:
RenderConfig, EncoderOptions, StreamingEncoderOptions, CaptureOptions,
DockerRenderOptions, Studio API request body, regression-harness
meta.json. The `-r` and `-framerate` ffmpeg args emit the rational form
verbatim (`30000/1001`) so no decimal round-trip happens at the encoder
boundary. Frame-interval math uses `1000 * den / num` ms (33.366… for
NTSC, 33.333… for integer 30).

Helpers live in @hyperframes/core:
- `parseFps(input: string | number): FpsParseResult` — discriminated
  parser used by both the CLI and the Studio API route
- `fpsToFfmpegArg(fps: Fps): string` — emits "30" or "30000/1001"
- `fpsToNumber(fps: Fps): number` — for arithmetic (telemetry, frame
  count, frame-index → time)

Studio API wire format accepts polymorphic `fps: number | string`:
- number → integer fps (`30`)
- string → rational (`"30000/1001"`)
Decimals are rejected; matches the same rule as the CLI.

Existing meta.json fixtures with integer `"fps": 30` continue to load
unchanged — the regression-harness validator now normalizes both number
and string inputs through `parseFps`.
2026-05-09 00:09:15 +02:00
Miguel ÁngelandClaude Opus 4.6 ae343bfdc8 chore: release v0.5.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-08 15:00:29 +00:00
Miguel Ángel 8de7ad7f61 chore: release v0.5.4 2026-05-07 23:22:15 -07:00
Miguel Ángel b009288df7 feat(runtime): single-clock transport — eliminate pause/play audio drift (#671)
Replace the two-clock architecture (GSAP rAF ticker + HTMLMediaElement
pipeline reconciled by a 50ms polling loop) with a single TransportClock.
GSAP is always paused and seeked to clock.now() on each rAF tick.
Drift between visual timeline and audio is structurally impossible.

Architecture:

  TransportClock.now() ──rAF──▶ timeline.seek(t) + el.currentTime
       ▲
  AudioContext.currentTime (~21µs)  ← WebAudio active
       OR
  audio.currentTime (~33ms)         ← HTMLMediaElement fallback
       OR
  performance.now() (~1ms)          ← no audio

Key changes:
- TransportClock class with monotonic + audio-master clock sources
- WebAudioTransport: routes audio through AudioBufferSourceNode for
  sample-accurate scheduling, falls back gracefully to HTMLMediaElement
- rAF tick loop replaces 50ms setInterval poll; GSAP always paused
- Strict sync (40ms threshold, consecutive-sample gated) + forceSync
  on play/pause/seek transitions for sub-frame media accuracy
- Buffer-stall: visuals freeze when audio is buffering instead of
  running ahead
- Frame quantization preserved in seek/renderSeek (parity contract)

Browser-verified: 0.0ms drift after 40 pause/play cycles (was 400ms+).

Also fixes: CDN script HTML error responses in validate (pre-existing).

54 tests across clock, clock-drift, webAudioTransport, and media.

Closes #668
2026-05-08 07:53:46 +02:00
James 2b2281d490 refactor: dedupe resolution presets and clean up 4k stack 2026-05-07 16:58:26 +00:00
James 25edba803a feat(studio): add resolution selector to render export bar 2026-05-07 16:58:26 +00:00
James 1545763ea3 fix(cli,producer): cross-multiply aspect check, CLI HDR guard, honest banner 2026-05-07 16:58:25 +00:00
James e07aeba213 feat(cli): add --resolution flag to hyperframes render for one-line 4k 2026-05-07 16:58:25 +00:00
James Russo c1c7ba999a Merge pull request #661 from heygen-com/05-07-feat_cli_add_--resolution_flag_to_hyperframes_init_for_4k_scaffolding
feat(cli): add --resolution flag to hyperframes init for 4k scaffolding
2026-05-07 07:55:13 -07:00
Vance Ingalls edac92b431 docs: add texture mask text catalog entry (#650)
* feat(registry): add texture mask PNGs for texture-mask-text component

* feat(registry): add texture-mask-text CSS snippet

* feat(registry): add registry-item.json for texture-mask-text

* feat(registry): add texture-mask-text demo composition

* feat(registry): register texture-mask-text component in manifest

* style: format texture-mask-text files with oxfmt

* fix: set mask-image directly on texture classes instead of via CSS custom property

url() inside CSS custom properties doesn't resolve correctly with mask-image
in some browsers. Move mask-image declarations to each texture class directly.

* docs: add texture mask text catalog entry

* test: lint texture mask text usage

* fix: harden texture mask text docs and lint

* fix: stabilize texture mask asset paths

* fix: address texture catalog review feedback

* fix: harden texture mask text instructions

* docs: remove texture catalog intro copy

* docs: use canonical texture preview URL

* docs: use cdn texture mask assets

* fix: escape catalog frontmatter safely

* test: stabilize windows render cli test

* test: pin texture catalog instructions
2026-05-07 00:23:36 -07:00
James 9f0074e44a fix(cli): handle reverse-order CSS in applyResolutionPreset and document scope 2026-05-07 06:10:22 +00:00
James a4eea984d9 feat(cli): add --resolution flag to hyperframes init for 4k scaffolding 2026-05-07 06:09:44 +00:00
James 555c51fcf6 feat(core): add 4k canvas resolution presets 2026-05-07 02:36:16 +00:00
James a739be58b1 fix(cli): preload render.js once in renderLocal test suite to fix Windows CI
The first dynamic `await import("./render.js")` cold-load takes >5 s on
Windows runners — long enough to blow vitest's default 5 s timeout in
whichever test ran it first. Subsequent imports are <10 ms because the
module is now cached, so only test #1 ever times out.

The downstream failure is more subtle: when test #1 times out, vitest
moves on, but its leaked async function eventually hits the synchronous
`producer.createRenderJob(...)` line and pushes a stale config to
`producerState.createdJobs`. That push lands AFTER test #2's `beforeEach`
clears the array, so test #2's `createdJobs[0]` is the leaked test #1
entry instead of its own. That's why test #2 saw `browserGpuMode: 'software'`
when it expected `'auto'`.

Hoist the import into `beforeAll` (matching the pattern the existing
`parseVariablesArg` and `validateVariablesAgainstProject` describe blocks
in this file already use). Cold-load happens once outside any test's
timeout window, every test stays fast, no leaked promise can corrupt
state.

Failing run: https://github.com/heygen-com/hyperframes/actions/runs/25470257972/job/74732502915
Started failing on main with the merge of #642 (auto-detect-browser-gpu),
which added the "forwards browserGpuMode='auto'" test as test #2.
2026-05-07 02:06:03 +00:00
James Russo 31acf7fdec Merge pull request #654 from TheodorKleynhans/feat/cli-png-sequence-format
feat(cli): expose png-sequence format
2026-05-06 18:12:18 -07:00
James Russo 588639fc1a Merge pull request #657 from heygen-com/fix/runtime-swallow-helper
fix(runtime): swallow() helper replaces empty catches in inlined runtime
2026-05-06 18:11:38 -07:00
James Russo a327e13a1e Merge pull request #632 from kiyeonjeon21/fix/cli-bun-command-hints
fix(cli): use bun in command hints
2026-05-06 17:22:02 -07:00
James Russo 5212ed49c9 Merge pull request #320 from Dylanwooo/feat/doctor-json-output
feat(cli): add --json output to doctor
2026-05-06 17:21:33 -07:00
James f33000dd1f fix(cli): rename browserGpu → browserGpuMode in render.test.ts
Drive-by fix: hf#631 (composition flag) merged with two test calls
using `browserGpu: false`, but hf#642 (browserGpuMode auto) merged
shortly after and removed that field from RenderOptions in favour of
the tri-state `browserGpuMode`. Main has been failing typecheck since
hf#642 landed (every PR inherits the failure).

Renaming `browserGpu: false` → `browserGpuMode: "software"` matches
the new shape; both tests still verify what they were written for
(forwards entryFile / omits entryFile to createRenderJob).
2026-05-07 00:00:49 +00:00
Theodor Kleynhans 4e28658173 feat(cli): expose png-sequence format
The producer already supports `format: "png-sequence"` end-to-end (see
RenderConfig in renderOrchestrator.ts), but the CLI's VALID_FORMAT
validator rejects it before the flag reaches the producer. Surface it
the same way `mov` and `webm` are surfaced.

Behaviour:
- `--format png-sequence` accepted alongside mp4/webm/mov.
- Auto-output path uses no extension (FORMAT_EXT["png-sequence"] = "")
  since the producer treats outputPath as a directory of frame_NNNNNN.png.
- `printRenderComplete` sums the contained file sizes when outputPath
  is a directory, instead of reporting the platform-dependent inode
  size.
- DockerRenderOptions.format type extended; existing buildDockerRunArgs
  is unchanged because it forwards the string verbatim.

Tests:
- renderLocal forwards `format: "png-sequence"` to createRenderJob.
- buildDockerRunArgs propagates `--format png-sequence` to the
  container.

Docs:
- Rendering guide: format flag table, format comparison table, new
  "PNG sequence (no encoding)" section, "How it works" extended.
- CLI package docs: format flag table updated.
2026-05-07 01:56:06 +02:00
Miguel Ángel 0e0a0e40d0 feat(cli): add --composition flag to render specific compositions (#631)
* feat(cli): add --composition flag to render specific compositions

Expose the existing entryFile config in the producer through
a new --composition / -c CLI flag. This lets users render
individual composition files without restructuring their project:

  hyperframes render -c compositions/intro.html -o intro.mp4

The flag validates the file exists before starting the render,
threads through both local and Docker render paths, and is
documented in the CLI help, examples, and docs.

* fix(cli): address PR review — path traversal guard, forward tests, tripwire

- Add path-containment check mirroring hyperframeLint.ts: reject
  --composition paths that escape the project directory
- Normalize leading ./ from composition paths for clean render plan output
- Improve error message: suggest .html file path instead of compositions command
- Add description note about <template> sub-composition constraint
- Add render.test.ts: entryFile forwarded to createRenderJob (forward + omit)
- Update dockerRunArgs tripwire test with entryFile coverage
2026-05-07 01:17:39 +02:00
James Russo 614f764bcd Merge pull request #642 from heygen-com/feat/auto-detect-browser-gpu
feat(engine): browserGpuMode "auto" — probe-once WebGL detection with software fallback
2026-05-06 14:50:46 -07:00
Miguel Ángel aa524f43c9 chore: release v0.5.3 2026-05-06 11:45:59 -07:00
James f635deb86a feat(engine): cache probe Promise + log resolved mode + sync docs
Three follow-ups from Vai's staff-eng review:

1. Concurrent-probe race (real bug): the parallel coordinator runs N
   workers via Promise.all, so `--workers 4` on a no-GPU host fired 4
   simultaneous probe Chromes — each paying the same 240 ms launch cost.
   Cache the *Promise* (not the resolved value): first caller assigns
   the in-flight Promise, every other concurrent caller awaits the same
   one. Verified with a new test asserting all concurrent callers get
   the identical Promise reference.

2. Stale rendering.md (lines 23, 29): user-visible contract said
   "browser GPU enabled by default", which was wrong post-auto. Now
   describes the auto / hardware / software trichotomy explicitly.

3. Silent fallback: auto-mode produced no output, so a regression to
   "always falls back to software even with GPU present" would have
   been invisible in production logs. Added a single stderr line per
   process when the probe resolves: `[hyperframes] browserGpuMode auto
   → <mode> (<reason>)`. Cache hits don't re-log.

Verification:
- Engine 536/536 (incl. new concurrent-dedup test asserting Promise
  reference equality across simultaneous callers)
- CLI 256/256
- Format / lint / typecheck clean
2026-05-06 17:33:55 +00:00
James 2221647728 refactor(cli): unify RenderOptions on browserGpuMode tri-state
Initial PR carried a backwards-compat shim where RenderOptions had both
`browserGpu?: boolean` (for docker) and `browserGpuMode?` (for local).
Since renderLocal/renderDocker have no external callers, simplify to a
single field. The boolean → docker-args conversion now happens inline at
the one site that needs it (`browserGpu: options.browserGpuMode === "hardware"`
when handing off to dockerRunArgs).

No behaviour change. 535/535 engine + 256/256 CLI still pass.
2026-05-06 17:33:55 +00:00
James 67bb56c703 feat(engine): browserGpuMode "auto" — probe WebGL once, fall back to software
When the host doesn't have a usable GPU (CI containers, eval rigs without
GPU passthrough, dev VMs), Chrome's hardware-mode WebGL flags
(`--use-gl=egl/metal/d3d11`) silently leave WebGL unavailable —
`getContext("webgl")` returns null, three.js' WebGLRenderer dies, the
canvas stays black. Surfaced today by Abhay's c2v-eval failing on a
docker render of an hf bundle that uses three.js + a custom fragment
shader.

The fix that's been there: `--use-gl=angle --use-angle=swiftshader` (CPU
software WebGL, ~5-50× slower but pixel-identical). The engine already
exposed `browserGpuMode: "software"` for this. The gap was discovery —
users had to know to pass `--no-browser-gpu` on no-GPU hosts.

This change adds `browserGpuMode: "auto"` (now the CLI default for local
renders): on first launch in the process, probe Chrome with hardware
args, check `canvas.getContext("webgl") !== null`, cache the result.
~1-2 s on first render, free on every subsequent render in the same
worker. Hardware GPUs keep their fast path; no-GPU hosts get SwiftShader
without ceremony.

Behaviour matrix:
- No flag, no env, local       → "auto" (NEW default)
- `--browser-gpu`              → "hardware" (force; errors if no GPU)
- `--no-browser-gpu`           → "software" (force SwiftShader)
- `PRODUCER_BROWSER_GPU_MODE`  → "hardware" / "software" / "auto" / unset
- Docker mode                  → forced "software" (unchanged)

Engine-config default stays "software" (conservative for embedders); the
"auto" default lives in the CLI's `resolveBrowserGpuForCli` so producer
embedders aren't surprised by a probe-on-launch.

Also adds `--enable-unsafe-swiftshader` to the software flag set —
Chrome 120+ deprecated implicit SwiftShader fallback and emits a
deprecation warning unless the flag is set explicitly. Despite the
"unsafe" name this is exactly the pre-deprecation behaviour; the rename
is about Chrome's threat model on the open web, not about the rendering
itself.

Verification:
- Engine 535/535 + CLI 256/256 (incl. new probe tests + tri-state CLI test)
- Empirical: probe on this no-GPU devbox returns "software" in 240 ms,
  cached 0 ms on subsequent calls
- Format / lint / typecheck clean across all packages

Refs the Abhay/Slack thread on c2v-eval rendering without a GPU node.
2026-05-06 17:33:55 +00:00
Miguel Ángel 7174c4cdcd chore: release v0.5.2 2026-05-06 10:27:07 -07:00
Miguel Ángel 35e71cb3f0 fix(cli): bypass stale cache on tag-based registry lookup (#648)
The add command's tag fallback (e.g. `hyperframes add html-in-canvas`)
uses the same 24h cached manifest as single-item lookups. When new items
are added to the registry, the stale cache returns an incomplete item
list, causing tag resolution to find zero matches.

Pass skipCache: true in the tag fallback path so it always fetches the
latest manifest from the registry.
2026-05-06 19:26:20 +02:00
Miguel Ángel 64f3a4ed5f chore: release v0.5.1 2026-05-06 10:14:00 -07:00
James 94b8acf3b0 chore: release v0.5.0 2026-05-06 16:12:18 +00:00
Miguel Ángel 6ef52972fd fix: resolve merge conflict in shader-transitions capture.ts
Merges main's refactored capture (CaptureSceneOptions, forceVisible,
stabilizeTransformedBoxShadows, foreignObjectRendering fallback) with
our HTML-in-Canvas drawElementImage capture path. The native capture
tries first and falls back to html2canvas on failure.
2026-05-06 07:49:31 -07:00
Vance Ingalls a7b308b667 feat: cache shader transition preview frames (#634)
* feat: cache shader transition preview frames

* fix: move shader transition loading to player
2026-05-06 01:25:09 -07:00
Miguel Ángel 20c1341cc8 feat(cli): name-or-tag resolution for hyperframes add
hyperframes add now auto-detects whether the argument is a single
block name or a tag. No --tag flag needed:

  hyperframes add html-in-canvas  # installs all 7 html-in-canvas blocks
  hyperframes add captions        # installs all 5 caption blocks
  hyperframes add vfx-shatter     # installs one block

When the name doesn't match a registry item, the CLI falls back to
tag-based resolution and bulk-installs all matching blocks.

Also fixes tag assignments:
- VFX blocks tagged "html-in-canvas" (not "vfx")
- Caption blocks tagged "captions" only (no html-in-canvas)
2026-05-06 00:58:46 -07:00
Miguel Ángel d9d4df4265 feat(cli): add --tag flag to hyperframes add for bulk install
Install all registry blocks matching a tag in one command:

  hyperframes add --tag vfx       # installs all 7 VFX blocks
  hyperframes add --tag captions  # installs all 5 caption blocks

The resolver loads each item's full manifest to check tags, then
installs matching blocks sequentially. Failed items are skipped
with a warning so one broken block doesn't abort the batch.

Also supports JSON output for CI: hyperframes add --tag vfx --json
2026-05-06 00:52:23 -07:00
Miguel Ángel 6d2bfe7aaa feat(engine): enable CanvasDrawElement in renderer Chrome args
Cherry-picked from feat/html-in-canvas-launch (PR #611):

- Enable --enable-features=CanvasDrawElement in Chrome browser args
  so HTML-in-canvas compositions render correctly
- Add native drawElementImage() capture path for shader transitions
  with existing fallback preserved
- Reuse renderer Chrome args in hyperframes validate for consistent
  WebGL/CanvasDrawElement environment
- Add capture.test.ts for the new shader transition capture path
2026-05-06 00:40:24 -07:00
James Russo 64457d9fe7 Merge pull request #641 from heygen-com/fix/bundler-runtime-and-joins
fix(bundler): inline runtime body, drop bare-semi joins, drop empty catch binding
2026-05-05 22:53:59 -07:00
Rames JussoandClaude Opus 4.7 dfca302d37 fix(bundler): runtime mode opt-in, ASI-safe joinJsChunks, prune dead subs
Per @vai-bot's review on hf#641:

Important #1: dead `src=""` substitution sites
=============================================

Now that `bundleToSingleHtml` inlines the runtime IIFE by default, the empty
`src=""` placeholder is never emitted in the no-env-var path — the 5 downstream
substitution sites that grep for `src=""` were dead.

Two of them (studio dev server + studio vite preview) genuinely WANT the
placeholder so they can hot-reload a local /api/runtime.js endpoint without
re-inlining ~150 KB on every composition edit. Three of them (CLI validate,
snapshot, layout) were just doing the same inlining the bundler already does.

Resolution:
- Add a `runtime: "inline" | "placeholder"` option to `BundleOptions`. Default
  is "inline" (matches the self-contained-bundle promise the function name
  makes). The two studio surfaces explicitly pass `{ runtime: "placeholder" }`
  to opt in.
- studioServer.ts + studio/vite.config.ts: pass the option, keep their
  existing string-replace logic unchanged.
- validate.ts + snapshot.ts + layout.ts: delete the now-redundant runtime
  substitution code (regex never matches the new inlined-runtime shape).

Important #2: joinJsChunks ASI hazard
======================================

The new helper appended `;` to chunks not already ending in `;` and joined
on `\n`. If a chunk ended with a `// line comment`, the appended semicolon
was eaten by the comment, leaving the next chunk's first statement attached
to the previous chunk's last expression — exactly the ASI hazard the helper
exists to prevent.

Fix: append `\n;` instead of `;` for chunks not already terminated. The
newline closes the line comment, the standalone `;` becomes the statement
separator. For typical chunks (already ending in `;`), output is unchanged
— still clean `\n`-joined chunks with no bare-semicolon lines.

Also added a trailing `;` to `wrapScopedCompositionScript`'s IIFE close
(`})()` → `})();`) so composition scripts join cleanly without falling
through to the `\n;` fallback.

New test: regression guard at the chunk boundary verifies every inline
script body in the bundle parses cleanly via esbuild even when a source JS
file ends with a line comment.

Verification
============

- `bun run --filter @hyperframes/core test` — 653/653 pass
- `bun run --filter @hyperframes/cli test` — 243/243 pass
- `bun run --filter @hyperframes/{core,cli,studio} typecheck` — clean
- `bunx oxfmt --check` + `bunx oxlint` on all touched files — clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 04:37:04 +00:00
JamesandClaude Opus 4.7 a707b6a882 fix(cli): pin inverse-alpha invariants, harden encoder stdin
- Extract applyMask helper from postprocess and add 5 unit tests pinning
  the contract this PR is selling: fg.alpha + bg.alpha === 255 per pixel,
  RGB triples byte-identical between fg and bg, and bg=null path leaves
  the bg buffer untouched. Without these, a future postprocess change
  (mask threshold, premultiplied alpha, gamma) could silently break the
  inverse-alpha relationship and the existing plumbing tests would all
  still pass.
- Add stdin 'error' listener inside spawnFfmpeg. If either encoder dies
  mid-render, Node emits an unhandled error on the dead writable on the
  next .write() and crashes the CLI before waitForExit's reject path
  can surface the encoder's stderr tail. Doubled encoder count = doubled
  failure surface, so this is worth pinning down.
- Tighten stdio param to a 3-tuple so an accidental 1-element array fails
  at type-check.
- Sharpen backpressure comment: write→true means "highWaterMark not
  exceeded," not "libuv flushed." Reuse-without-corruption is safe only
  because session.process is slow enough that libuv drains in between.

Addresses review on PR #637.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:18:23 -07:00
JamesandClaude Opus 4.7 c2bc2aa1c1 feat(cli): add --background-output to remove-background
Emit an inverse-alpha background plate alongside the cutout in a single
inference pass. Same source RGB, alpha = 255 − mask. Dual-encoder pipeline
runs in parallel; both outputs share the same --quality preset.

This is a hole-cut plate (subject region transparent), not an inpainted
clean plate — composite something opaque under it to fill the hole.
Docs and skill cover when each is the right tool.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:47:30 -07:00
Miguel Ángel 21ec5f800a fix(cli): use size-adaptive timeouts for publish uploads (#635)
## Summary
- Replace the flat 30s upload timeout with a size-adaptive calculation: `max(120s, bytes / 500KB/s)`
- Metadata requests (presigned URL, complete) keep the original 30s timeout
- Companion to the backend change removing the 64 MB upload limit in experiment-framework

## Context
With the backend size limit removed, large projects (78 MB+) need proportionally longer to upload. A 78 MB project now gets ~164s, a 500 MB project ~17 min. The old 30s timeout would abort any upload over ~15 MB on a typical connection.

## Test plan
- [x] All 4 existing vitest tests pass
- [x] Build succeeds, no type errors
- [x] Lint + format pass (oxlint + oxfmt)
- [x] Timeout values verified for 10/78/200/500/1000 MB archives
2026-05-06 00:00:08 +02:00
Kiyeon Jeon a57d63b531 fix(cli): use bun in command hints 2026-05-05 18:33:51 +09:00
James 584627546a chore: release v0.4.45 2026-05-05 04:41:23 +00:00
JamesandClaude Opus 4.7 f4ecf96918 fix(engine,cli,producer): address PR #627 review feedback
- engine/chunkEncoder, engine/streamingEncoder: extend `-bf 0` to GPU h264
  paths (nvenc, qsv, vaapi) and `-b_strategy 0` for qsv so GPU-encoded
  outputs avoid negative-DTS freezes too — not just SW libx264.
- engine/videoFrameExtractor: detect mid-path traversal (e.g.
  `assets/../../foo.mp4`) by normalizing first and re-anchoring at the
  project root. Adds a regression test.
- engine/videoFrameExtractor: dedupe stderr "src not resolvable" warnings
  by `video.src` so a comp with N broken sources logs once, not N times.
- engine/videoFrameExtractor.test: drop dynamic `require("node:fs")`,
  use ES `import { writeFileSync } from "node:fs"`.
- engine/ffprobe: extract `readTagCI` helper for case-insensitive ffprobe
  tag reads (will recur for other libavformat-versioned sidecar tags).
- cli/background-removal/pipeline: collapse Quality / QUALITIES /
  QUALITY_CRF / DEFAULT_QUALITY / isQuality surface using
  `Quality = keyof typeof QUALITY_CRF`.
- producer/renderOrchestrator: replace `v.src.startsWith("/")` with
  `isAbsolute(v.src)` in the HDR probe path so Windows absolute paths
  (`C:\...`) aren't treated as relative — matches the audioMixer guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:29:34 -07:00
JamesandClaude Opus 4.7 688052d368 fix(cli): correct sharp 3-channel mask + BT.709 + quality presets in remove-background
- inference.ts: force `.toColourspace("b-w")` on the resized mask. Sharp upcasts
  the 1-channel raw input to RGB-interleaved during resize, so `fullMask[i]`
  was reading R,G,B,R,G,B... of pixels 0..691199 instead of the alpha for all
  2,073,600 pixels. Visible symptom: horizontal scanline alpha artifact in
  every transparent webm — the avatar appeared semi-transparent throughout.
- pipeline.ts: add BT.709 + limited-range colorspace tags so Chrome's YUV→RGB
  matches the source mp4 (without these, ffmpeg's default RGB→YUV is BT.601
  and skin tones drift visibly when the cutout is overlaid on its source).
- pipeline.ts: add Quality preset type ("fast"/"balanced"/"best" → CRF 30/18/12).
  Default raised from CRF 30 → 18 ("balanced") so the most common pattern
  (text-behind-subject) works out of the box without visible doubling.
- remove-background.ts: wire `--quality` flag with validation, +2 examples.
- Tests: BT.709 tags present, quality preset → CRF mapping, default is balanced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:27:56 -07:00