Skip to content

VapourBox 1.7.0: AI add-ons, audio restoration, new filters, deps 1.16.0 - #154

Merged
StuartCameronCode merged 77 commits into
mainfrom
ship-every-platform
Nov 10, 2026
Merged

StuartCameronCode merged 77 commits into
mainfrom
ship-every-platform

Conversation

@StuartCameronCode

Copy link
Copy Markdown
Owner

VapourBox 1.7.0: the "ship on every platform" work. It adds AI passes as opt-in downloads, audio restoration, new classical filters and source analysis, on deps 1.16.0.

New for users

  • Face Restoration: RestoreFormer++ (default), GFPGAN v1.4 or CodeFormer. Faces are found with RetinaFace and masked with BiSeNet. Everything outside a face stays bit-exact.
  • Colourisation: DDColor-T or DeOldify Video. Luma is never touched, and colour is smoothed over time.
  • Audio Restoration: channel fix, declip, declick, rumble and hum removal, denoise (afftdn or the DeepFilterNet add-on), loudness, offset and drift correction. The audio is rendered before the encode, and transcription hears exactly the output's soundtrack.
  • AI upscaling add-ons in Crop & Resize:
    • Real-ESRGAN General, 2xLiveActionV1 SPAN and Real-ESRGAN x4plus/x2plus.
    • An AI strength control for every AI method.
  • Classical filters (deps 1.16.0):
    • SangNom anti-aliasing.
    • NLMeans (CPU) noise reduction.
    • Vine dehalo and mpeg2stinx deblock.
    • IVTC's fix for interlaced fades.
  • Detect borders in Crop & Resize: black borders, blanking, letterbox bars and VHS head-switching. It is advisory, and nothing changes until you apply a suggestion.
  • BT.601 → BT.709 when SD is upscaled to HD (Auto by default). This converts the matrix only; primaries keep the source's.
  • Noise and halo suggestions: a background measurement badges Noise Reduction or Dehalo only when the picture clearly needs it, and stays silent otherwise.

Add-ons

  • Every AI model is now an add-on, downloaded only when a feature needs it. Each is declared by one manifest in app/assets/addons/, read by both the app and the worker.
  • New add-ons:
    • ai-runtime (ONNX Runtime)
    • face restoration (3)
    • colourisation (2)
    • DeepFilterNet
    • live-action upscalers (2)
    • frame-rate-rife
    • upscale-anime
  • Models moved out of the deps bundle: RIFE's model and the Real-ESRGAN anime / Real-CUGAN models. The plugins stay in the bundle, and their GPU probes now run tiny stand-in models, so they work before anything is downloaded.
  • Hosting: converted and re-hosted models are on addon-models-v1, each beside its upstream licence text. Nothing points at an -rc tag.

Dependencies

  • deps 1.16.0 (not Latest), published as the tested rc2 bundles with only the version changed (Scripts/promote-deps-rc.py):
    • vszip 24.0.0
    • SangNom r45
    • no AI models in the bundle

Testing

  • Branch head: CI is 4/4 and nightly 7/7 on 024325a, against the final deps 1.16.0 and addon-models-v1. The version bump since then changes no code; the push gate passes locally (1431 tests).
  • GPU tests: tests that need a Vulkan device are now tagged gpu and left out of CI, since the runners have none. They pass on an M1 Pro with VAPOURBOX_EXPECT_VULKAN=1.
  • Measurement reports: every investigation has one in docs/reports/ (face restoration, colourisation, audio restoration, AI upscale add-ons, classical filters, crop detection + 709, noise/halo scores).

Before merging

  • Run VAPOURBOX_EXPECT_VULKAN=1 flutter test --tags gpu (plus --tags heavy for the encodes) on a Windows and a Linux machine with a GPU. Nothing in CI proves RIFE and VSNCNN run in those bundles, or that the new model add-ons install there.
  • Confirm BT.601 → BT.709 should stay on Auto by default.

🤖 Generated with Claude Code

StuartCameronCode and others added 30 commits November 8, 2026 17:03
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run-debug-macos.sh and run-debug-linux.sh copied only pipe_source.py and
spotless.py beside the debug worker. pipe_source_dir() picks the first
templates directory holding pipe_source.py, so every other vendored module
(deflicker, mclean, temporaldegrain2, …, and now ai_runtime) was missing in a
debug run and only worked from worker/templates/. Copy *.py, as the
packaging scripts already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every AI model is to ship as an optional add-on, never in the deps bundle.
This is the shared groundwork.

Manifest add-ons: one JSON per add-on in app/assets/addons/ (format in
docs/ADDONS.md). Kind "files" is a model pack (plain files, or archive
members each checked by its own hash); kind "python" is wheels unpacked into
addons/<id>/site-packages/. ManifestAddonInstaller downloads and verifies
every file, stages in addons/<id>.new, writes a .vapourbox-addon.json marker
(id, version, platform) and renames into place; a manifest version bump
makes the old install count as missing. Platforms come from the manifest's
table, with minOs checked against the running macOS. AddonService finds every
manifest through the asset manifest; AddonInfo gains additionalCredits for
add-ons made of several separately licensed components.
VAPOURBOX_ADDON_MIRROR=<dir> takes files from a local directory (still
size- and hash-checked) so add-ons can be tested before upload.

The worker compiles the same manifests in (build.rs -> addons.rs): install
state, addon_dir, require() with a message naming the add-on and
Settings -> Add-ons, and --list-addons. ProcessingPipeline::required_addons()
is where a pass declares what it needs; ScriptGenerator refuses the job unless
it is installed and emits the {{#AI_RUNTIME}} block (both templates) when the
AI runtime is needed. WorkerHarness.ensureAddon() installs through the app's
own installer.

ai-runtime: ONNX Runtime + NumPy 2.5.4 for the deps Python 3.12, pinned by
URL and SHA-256 from PyPI: onnxruntime 1.31.1 (macOS arm64, Linux x64/arm64),
onnxruntime-directml 1.24.4 (Windows), 1.23.2 on Intel Macs (macOS 13+, the
last Intel wheel). Nothing else is needed at run time (checked). Passes use
templates/ai_runtime.py: setup() puts site-packages on sys.path itself (the
Windows embeddable Python ignores PYTHONPATH), session() picks TensorRT /
CUDA / DirectML / Core ML / CPU with a CPU fallback (VAPOURBOX_AI_FORCE_CPU),
and frame <-> NCHW helpers with map_frames(). --probe-ai-runtime runs a
committed 172-byte self-test model through that path and reports the
provider; positive results are cached per boot. Credited in NOTICES, the
About dialog and the README (ONNX Runtime MIT, NumPy BSD-3-Clause, DirectML
under Microsoft's licence on Windows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/ADDONS.md: the manifest fields, what an install looks like, the
VAPOURBOX_ADDON_MIRROR test mirror, the addon-models-v1 hosting convention
and the addon-staging/ conversion venv, the worker and template API
(addons.rs, required_addons, the AI_RUNTIME block, ai_runtime.py), the
--probe-ai-runtime probe, and the checklist for adding an add-on. CLAUDE.md's
Add-ons section, key files and structure, BUILDING.md's structure, and an
ENGINEERING_NOTES entry with the wheel facts the ai-runtime manifest rests on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root .gitignore's 'addons/' (the downloaded add-ons directory) also
matched app/assets/addons/, so the manifest was never committed and the
worker's build.rs found no manifests outside the worktree that wrote it.
Anchor the rule to the repository root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scripts/models/convert_realesrgan.py defines SRVGGNetCompact and RRDBNet
(no basicsr needed), loads xinntao's checkpoints pinned by URL and SHA-256,
and exports ONNX in the shape VSNCNN already runs (input/output, NCHW RGB
float, dynamic batch/height/width, opset 13, IR 7). x2plus's
pixel_unshuffle becomes the equivalent one-hot stride-2 convolution, which
ncnn can import (checked exactly equal). Parity against PyTorch is printed;
two runs are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…worker

Three new Crop & Resize methods, run on VSNCNN like the bundled anime
models, with their models in two manifest add-ons:

- upscale-general: Real-ESRGAN General (realesr-general-x4v3 and its
  denoise variant, converted, to be hosted on addon-models-v1) and
  jcj83429's 2xLiveActionV1_SPAN (Apache-2.0, upstream's ONNX pinned at
  commit 5d8cdd2e).
- upscale-esrgan-plus: Real-ESRGAN x4plus / x2plus (converted).

Factor rules: General is 4x only, so 2x resamples its result down
(Spline36, in float RGB, before the conversion back); SPAN is 2x only, so
4x runs it twice; x2plus/x4plus by factor. x4plus runs in 384-pixel tiles:
whole-frame at 720x576 lost the Vulkan device on an M1 Pro and 512 tiles
thrashed. ProcessingPipeline::required_addons() declares the add-on, so
a job without it is refused naming it and Settings -> Add-ons; only
macOS arm64, Windows x64 and Linux x64 (where VSNCNN ships) are offered.

aiStrength (0-1, default 1) blends any AI upscale with a Spline36 upscale
of the same frame in one std.Merge at the clip's format; at 1.0 nothing is
emitted and the script is unchanged. Tests 185-187.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vszip 22.1.0 -> 24.0.0 (first release with CPU NLMeans; WNNM since 22.0.0).
  macOS x64 builds the sdist with Zig 0.17.0 (-Doptimize=fast; the headerpad
  patch now matches the 8-space use_llvm inside if (build_vs)). Linux wheel
  pinned by hash.
- SangNom 45.0 (PyPI wheels, pinned by hash) for Anti-aliasing's SangNom
  method. SSE2 only on x64: one file serves both CPU tiers.
- deps-expected-plugins.json lists SangNom on every platform and tier;
  vapoursynth_integration_test requires sangnom and vszip.NLMeans.
- Credits: SangNom (MIT source, GPL-2.0 binaries, as upstream states) and
  vs-jetpack (MIT, for the ported functions) in NOTICES, About and README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new audio_restoration module renders the output's soundtrack for one
source stream before the video encode: channel fix, sync offset (either
sign), drift correction from the encoded picture's FrameMap length,
declip/declick/rumble/hum notches/afftdn (its 25 ms delay trimmed),
DeepFilterNet (add-on), two-pass loudnorm and a latency-compensated
limiter, all chosen in one place with one -af per ffmpeg run. The encoder
maps the rendered track first and keeps the other audio streams;
subtitles transcribe the rendered track. --audio-plan prints the plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pass sits with Subtitles in an 'Audio & Subtitles' stage (it runs
first: the transcript is of the restored sound), schema-driven like the
others. Manifest add-on files can now be marked executable: the installer
sets the bit and, on macOS, signs an unsigned program. DeepFilterNet is
credited in NOTICES, About and the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esize

Three new Upscale Method options (Real-ESRGAN General, Live Action SPAN,
Real-ESRGAN x4plus; NEW since 1.7.0), a General Denoise switch and an AI
Strength slider for every AI method. Choosing an add-on method prompts
for its add-on through AddonPrompt.ensure (moving between two methods of
one add-on asks once), and the panel shows the add-on's terms with a
Download button while it is missing: the AnimeJaNai notice is now the
general UpscaleAddonNotice. upscaleMethodAddons mirrors the worker's
UpscaleMethod::addon. The SPAN description says it cannot handle VHS
damage, as its author does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…models

NOTICES, About tiles and README Acknowledgments for the Real-ESRGAN
general model and x4plus/x2plus models (BSD-3-Clause, Copyright (c) 2021
Xintao Wang, from upstream's LICENSE) and 2xLiveActionV1_SPAN (Apache-2.0,
Copyright 2025 jcj83429, its NOTICE reproduced), each saying it is an
add-on. README also describes the live-action upscalers and AI Strength.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stream; tests

The first heavy run found atempo moving every click ~20 ms early, afftdn
taking 0.7 dB off -39 dB hiss at its default floor (13.6 dB with the
measured one; its noise tracking undid it), and per-stream metadata
mapping dropping the other streams' tags. Drift is now an exact resample
(aresample up, integer asetrate, aresample back), afftdn gets the
source's measured noise floor, and every audio stream's tags are mapped.

Adds the push-gate --audio-plan test, the heavy encode tests (dead
channel, +/- offset, trim, drift, hum, afftdn, loudness, late audio
start, several streams, DeepFilterNet), unit tests, and an 'audio'
progress phase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e add-on models

A tile larger than the frame in either direction segfaults VSNCNN. The
template tiled whenever the area passed its threshold, so a wide, short
picture (800x200 with x4plus's 384 tiles, or any frame above 720x576 in
area but under 512 lines) crashed. Tiles are now min(tile, width/height).

Tests: script generation for AI strength and the upscale-general models
in the push gate (GPU rule; installs the add-on with the app's installer),
and in the heavy suite every add-on model at 2x and 4x by pixels against
Spline36 (size, count, preview = encode, flat colour within measured
bounds, bar centre within 0.75 px), AI strength 0.5 landing half-way, and
x4plus on an 800x200 source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…GAN and CodeFormer to ONNX

Conversion scripts for the face restoration add-ons: each pins the upstream
checkpoint by URL and SHA-256 and the network source by commit, exports
static-shape opset 17 / IR 8 graphs (loadable by ONNX Runtime 1.23.2), and
records parity with PyTorch. GFPGAN exports with its fixed noise buffers
(randomize_noise=False) so it is a pure function of its input; CodeFormer's
fidelity w is a graph input (exact at w=0, where the fused branch adds zero).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
face_restore.py (NumPy on ONNX Runtime from the ai-runtime add-on, no
OpenCV): RetinaFace detection with priors and NMS, five-point similarity
alignment to the FFHQ 512 template, RestoreFormer++ / GFPGAN / CodeFormer,
a feathered BiSeNet mask of the face itself, and an inverse warp back at the
clip's own resolution. Temporally stabilised as a pure function of the
neighbours (landmarks smoothed with hysteresis and gap filling, restored
faces blended in aligned space with motion-adaptive weights), and merged in
the source format so every pixel outside the faces is bit-exact.

Runs after Crop & Resize, before Sharpen, in both templates; the add-ons
(face-restoration, face-gfpgan, face-codeformer, all requiring ai-runtime)
are declared through required_addons(), and the preview reads twice the
smoothing radius either side. Generic helpers (to_rgbs/from_rgbs, shifted,
FrameMemo) go to ai_runtime.py. Fixture: a VHS-degraded clip of NASA
public-domain footage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…offset

CLAUDE.md (Audio Restoration section, the subtitle and -af rules), README,
docs/ADDONS.md (executable files), ENGINEERING_NOTES and the report
docs/reports/2026-11-audio-restoration: afftdn's 25 ms delay and noise
floor, atempo's ~20 ms shift, alimiter's latency and auto-level, the
late-start gap, hum notch depth, loudness accuracy, DeepFilterNet's delay.

Adds the heavy test that Whisper's cues move with a 2 s sync offset, and
WorkerHarness.ensureWhisper for running it locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schema (model, strength, CodeFormer fidelity, steady-faces toggle; detection
settings advanced-only; the VapourSynth call and models shown in advanced
mode), Dart model, pipeline and converter wiring, the pass row as the first
of the Finishing stage, and the add-on prompt when the pass is switched on or
its model changes, with GFPGAN's and CodeFormer's non-commercial terms shown
whenever they are chosen. Credits for RestoreFormer++, RetinaFace, BiSeNet,
facexlib, GFPGAN and CodeFormer in NOTICES, the About dialog and the README,
each from the upstream LICENSE. No built-in preset turns the pass on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e memo, heavy test

Concurrent Core ML predictions from VapourSynth's threads ran the GPU out of
memory; ai_runtime.Runner serialises a pass's model runs (one lock for the
detector, parser and restorer) and retries a failed GPU run on the CPU. The
per-frame memo now holds only the region the faces reach, not whole RGB
frames. The script logs which provider the restorer runs on.

integration_face_restore_test.dart (heavy): RestoreFormer++ changes the face
and leaves every sample outside it bit-exact (8- and 10-bit), strength 0 is
the input, GFPGAN and CodeFormer run with and without smoothing, and the
preview is the encode's frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…TC fade fix

- Anti-aliasing: SangNom method (2x supersample, SangNom per axis, back
  down; aa in 8-bit units, scaled by the plugin). havsfunc's
  santiag(type=sangnom) stays pinned off: it calls SangNom(order=0), which
  r45 treats as double-rate and refuses without _FieldBased.
- Noise Reduction: NLMeans (CPU) advanced method on vszip 24's NLMeans,
  sharing the KNLMeans settings (same algorithm and output). The KNLMeans
  no-device message now names it as the way out.
- Ports from vs-jetpack 3.0.0 (MIT) into both templates, through _expr()
  and _nnedi3(): vine_dehalo as an advanced Dehalo method, mpeg2stinx as an
  advanced Deblock method (whole frame, never per field; preview radius 1),
  and FixInterlacedFades as an IVTC option (average/match) right after VFM.
  mpeg2stinx and the fade fix match vs-jetpack to 0.04 / 0.00 levels.
- Tests: Rust test_200-205; Flutter script-gen cases, the high-bit-depth
  parity passes, and a new heavy integration_classical_filters_test (fade
  fix closes the field gap 4.12 -> 0.06; NLMeans == KNLMeansCL to 0.0003).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HuggingFace serves ggml-small.bin at 487,601,967 bytes and
ggml-large-v3-turbo.bin at 1,624,555,275 (x-linked-size). The app only
uses the size for progress, but WorkerHarness's downloader enforces it,
so ensureWhisper failed on the small model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…HS2HD probe

docs/reports/2026-11-ai-upscale-addons: conversion parity (PyTorch vs ONNX
<= 0.017 levels; VSNCNN fp32 vs ONNX Runtime <= 0.012), PSNR on a restored
broadcast frame (and why General's 2x keeps Spline36), crops, flat-colour
drift and bar position per model, AI strength, speed and x4plus tiling on
an M1 Pro, the tile-larger-than-frame segfault, and why 2xVHS2HD
(RealPLKSR) can't run on ncnn. Scripts reproduce every number.
ENGINEERING_NOTES entry, reports index row, CLAUDE.md rules, and a note in
docs/ADDONS.md that VSNCNN-run add-ons need no ai-runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-ons

Keeps both sides in README, ENGINEERING_NOTES and the reports index; the
upscale tests are renumbered 186-188 after audio's test_185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps both passes everywhere they meet; face restoration's Rust tests are
renumbered 186/187 (185 is audio's), the copyWith model count is 23, and the
README pipeline table puts Face Restoration at 19 with the rows below
renumbered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StuartCameronCode and others added 27 commits November 8, 2026 22:26
make_colorize_ddcolor.py exports piddnad/DDColor's ddcolor_paper_tiny
checkpoint (Apache-2.0); make_colorize_deoldify.py rebuilds DeOldify's
DynamicUnetWide generator in plain PyTorch (fastai v1 no longer installs)
and loads ColorizeVideo_gen.pth (MIT) with strict=True, verified against
FaceFusion's independent export of the same weights to 1.8e-6.

Both take one grey channel in [0, 1] with dynamic height/width and are
reproducible byte for byte. Spectral/weight norms are baked in, and
ReplicationPad2d, Tensor.repeat and DDColor's einsum are rewritten as exact
equivalents Core ML can take, so each graph is one Core ML partition (DDColor
ran 4x faster). colorize_provider_bench.py times them per provider and size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colorize.py (on the ai-runtime add-on): DDColor-T or DeOldify Video sees the
luma stretched to a square render size; DDColor's CIELAB a/b are recombined
with the frame's own lightness through std.Expr stages (OpenCV's constants,
exact sRGB curve), DeOldify's RGB is scaled up as upstream does; either
becomes YUV with the source's matrix, and only U/V are taken, so the Y plane
is bit-exact at every depth. Temporal smoothing averages each frame's
prediction with its neighbours' (shifted + FrameMemo), weighted to zero
where the grey picture moved.

Runs between Chroma Fixes and Color Correction in both templates; the
add-ons (colorize-ddcolor, colorize-deoldify, each requiring ai-runtime)
are declared through required_addons(); the preview reads temporal_radius
frames either side and runs DDColor on the CPU, since Core ML compiles it
for over a minute per process. ai_runtime.session/Runner take dims= (free
dimension overrides) so one dynamic-shape file gives Core ML static shapes.
Fixture: 5 s of 'Diary of a Sergeant' (1945, US War Department, public
domain).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schema (DDColor / DeOldify Video as methods, each gating its own render size;
colour strength 0-1.5; steady colour 0-4 frames), Dart model, pipeline and
converter wiring, the pass row between Chroma Fixes and Color Correction in
the Detail & Color stage, and the add-on prompt when the pass is switched on
or its model changes. Credits for DDColor (Apache-2.0) and DeOldify (MIT,
Jason Antic) in NOTICES, the About dialog and the README, from the upstream
LICENSE files and model cards. No built-in preset turns the pass on. Unit
tests, script-generation coverage and a heavy end-to-end test (luma
bit-exact at 8 and 10 bits and on a colour source, strength 0 = input, both
models, preview = encode).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_93 requires every vendored module that evaluates expressions to use
the same helper shape as the templates (akarin where present, an explicit
core.std.Expr fallback).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion

Colourisation keeps only the luma, so the colour Chroma Denoise cleaned
never reaches the result. Advisory only, on the Chroma Denoise row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/reports/2026-11-colourisation: licences read upstream, conversion
parity and the DeOldify verification, the Core ML rewrites (one partition,
2.6x faster) and compile times, the CIELAB chain against OpenCV and the
exact formulas, colour flicker with and without smoothing and motion
weighting (84% less at radius 2 for both models), speed on Core ML and the
CPU at SD and 1080p, and the position, already-colour and preset decisions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	README.md
#	docs/ENGINEERING_NOTES.md
The deps 1.16.0 builds failed three times during a GitHub incident
(timeouts for unauthenticated users): release downloads came back as 92-byte
504 pages or cut short, curl -L saved them as the archive, and the build died
later on "not in gzip format". Every curl now runs with --fail and six spaced
retries; Windows downloads (and its PyPI metadata lookups) go through a
retry loop. What gets built is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vszip 24.0.0 and SangNom r45 on every platform. All eight zips and their
sidecars are on the release candidate; release.sh refuses to cut an app
release while this names an -rc tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As 1.15.0-rc1 did: the app reinstalls only when the version string changes,
so an RC built as plain 1.16.0 would leave anyone who installed it on the
RC's contents after the real 1.16.0 (or an rc2) shipped. CI also derives the
asset version from the tag, which could not find the 1.16.0-named zips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- face/colorize parameter tests read the Rust source with CRLF on a
  Windows checkout; normalise line endings before matching.
- AudioFixtures.plan decoded the worker's UTF-8 stdout with the Windows
  code page (the app itself decodes UTF-8), mangling the arrow in
  'Settings → Add-ons'.
- The executable add-on test copied /bin/echo, which is arm64e: macOS 15
  kills an arm64e binary once Apple's signature is gone, ad-hoc signed or
  not. Compile a plain C program instead, as DeepFilterNet's binary is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s 4x

- ai_runtime_test matched the self-test literal with '\n' against a
  CRLF checkout on Windows; normalise first.
- realEsrganPlus_4x (five frames and a preview of the heaviest network)
  took 18 min on Linux lavapipe and ran past its 20 min budget on
  Windows' lavapipe; give that one case 50 min.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gate frame

- WorkerHarness killed only the worker on a timeout. On Windows that left
  its vspipe running Real-CUGAN on every core, so the next GPU tests
  (AnimeJaNai) timed out as well. Use the app's ProcessTree.killTree.
- The push gate's upscale frame is cropped to 120x96: a 240x192
  Real-CUGAN frame took 2.5 to over 5 min on Windows lavapipe.
- Face Restoration and Colourisation encodes allow 1200 s before the
  stall watchdog: on the Intel-Mac VMs Core ML's virtual GPU fails and
  CodeFormer's first frame took ~10 min on the CPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests that run RIFE or AI upscaling through ncnn need a Vulkan device, so
they are tagged `gpu` and both workflows exclude the tag (push gate:
--exclude-tags heavy,gpu; nightly: --tags heavy --exclude-tags gpu).
The lavapipe provisioning is removed: on a CPU Vulkan driver the timings
measured the runner, not the GPU path, and Windows kept running past
every budget. CI keeps the probe-answer and refusal tests, which need no
device. Run `VAPOURBOX_EXPECT_VULKAN=1 flutter test --tags gpu` on a
machine with a GPU (CLAUDE.md, docs/BUILDING.md, ENGINEERING_NOTES).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the Intel-Mac v2 runner Core ML falls back to the CPU and the Face
Restoration preview hit the harness's 5 min default; give both AI passes'
previews the encode's 20 min and stall budget. On linux-x64-v2 Whisper's
small model reached 82% of a 30 s clip in the default 5 min; the subtitle
offset test's encodes get 15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file's name (or an archive member's) may now be a relative path of
plain names joined by '/'. Every segment is checked like a plain name, so
nothing can land outside the add-on's directory, and the installer
creates the parent directories in the download, staging and extraction
steps. It exists for RIFE, whose plugin reads the model version and its
padding from substrings of the model directory's path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The last AI models in the deps bundle move out, so "every AI model is an
add-on" has no exceptions. The plugins (librife, VSNCNN, MoltenVK) stay.

- frame-rate-rife: RIFE 4.26's ncnn model (11 MB), fetched from the
  plugin's repository at the commit the deps scripts pinned, installed as
  rife-v4.26_ensembleFalse/flownet.* because the plugin reads the model
  version and padding from that path.
- upscale-anime: vs-mlrt's Real-ESRGAN animevideo 2x/4x and Real-CUGAN
  2x ONNX files (20 MB), byte for byte, hosted by this project on the
  deps-v1.16.0-rc1 release (vs-mlrt publishes them only in an ~810 MB
  archive).

Worker: both are declared in required_addons() and refused through
addons::require; scripts read the models from the add-ons directory.
AiModel::Bundled and gpu_plugin::UPSCALE_MODELS are gone. The probes no
longer need any model: VSNCNN runs a 264-byte committed self-test ONNX
handed over as bytes (path_is_serialization), RIFE a weightless two-layer
stand-in written beside the probe script, and both check the answer
(a new WrongResult reason). A stand-in that fails to load is LoadFailed.

App: the panels show the add-on notice and prompt on choosing RIFE or an
anime upscaler, and on switching Frame Rate / Crop & Resize on with one
chosen. Deps scripts stop fetching models; deps-expected-plugins.json no
longer lists them, and attribution_test fails if a model reappears.
Credits say the models are separate downloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Samples 24 pairs of consecutive frames (12 above 1080p) across the source,
decoded in its own format and depth, and measures luma in plain Rust:

- noise: motion-compensated frame difference (16x16 blocks, +-10 px,
  match chosen on smoothed pictures), 10th percentile of blocks, 25th
  percentile of pairs; per field when interlaced. Sees tape-like and
  grain noise a spatial high-pass misses, and cancels moving texture.
- halo: per-edge two-sided overshoot beside isolated step edges followed
  over 7 lines of one field, mean over edges, % of the step; a one-sided
  outline scores 0.

The worker decides the thresholds (noise sigma >= 3.5, halo >= 6.0) and
reports noiseHigh / haloHigh. Calibrated in
docs/reports/2026-11-noise-halo-scores.

crop_detect's sampling decode becomes decode_samples, shared by both
detectors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a source is analysed, MainViewModel measures it in the background
(--detect-noise-halo, one source at a time, whole source not the trim) and
keeps the report on QueueItem.noiseHalo. relevanceFor badges Noise
Reduction / Dehalo as recommended only on the worker's noiseHigh /
haloHigh verdicts; unmeasured, low or failed stays neutral, and nothing
else in the pass list changes.

Tests: pass_relevance_test cases (unmeasured, below, failed, above, other
passes unchanged, restraint with a measurement); heavy
integration_noise_halo_test on the VHS capture (clean, sigma 6 noise at 8
and 10 bit) and the DVB-T broadcast (clean, sharpened).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/reports/2026-11-noise-halo-scores: 154 fixtures with known noise and
sharpening (Kodak pans, NASA 1080p, synthetic animation, a 1968 film scan,
the repo's VHS and DVB-T clips; dark, letterbox, interlaced, 10-bit and
sub-pixel-texture traps), every candidate measured, the worker checked
against the NumPy reference, timings. VCG's signalstats TOUT / halo ratio
and a spatial Immerkaer noise score were measured and rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Dehalo

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	docs/ENGINEERING_NOTES.md
rc2 is rc1 without RIFE's model and the Real-ESRGAN anime / Real-CUGAN
models, which are now the frame-rate-rife and upscale-anime add-ons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deps-version.json points at deps-v1.16.0, the tested rc2 bundles with only
the version changed (Scripts/promote-deps-rc.py, now committed). Every
self-hosted add-on model downloads from addon-models-v1, where each sits
beside its upstream licence text, so no manifest names an -rc tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update-version.sh now edits Info.plist with sed only: PlistBuddy rewrote
the whole file and dropped its comments, including the one explaining why
Impeller is forced off (issue #91).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant