Commit Graph
7 Commits
Author SHA1 Message Date
scraed 6948b2c766 Retire sampler hyperparameters, add MinStepFrac, and rework the video mask union
Retire Beta/Friction/EarlyStop/InnerThreshold/InnerPatience/MinStepFrac
from all sampler nodes: the widgets are gone, the values are fixed
constants, and old prompts still validate via hidden inputs. A value
sanitizer falls back to defaults on invalid widget values, and the
frontend migrates old workflows by key-based widgets_values mapping
(LAYOUTS table) instead of positional arrays, so kept parameters
(PromptMode, Inpainting_mode) survive on old workflows.

MinStepFrac defaults to 1.0: the inner step size is pinned and the
inner-step count ramps linearly with the remaining noise fraction.

Video mask union: resample nearest-exact first, then a slice-level
sliding max-pool (experimental order) so the mask follows the painted
region's motion through the video.

Update Example_29 with the new output and re-export the workflow JSON.
2026-08-12 17:24:03 +08:00
scraed 59aa08dff0 Store masks inside exported videos and convert the example set to ImageEncode/ImageDecode
Mask-in-video (the masks now travel with the video file):
- New videometa module: write/read a JSON payload under the lanpaint-mask
  mp4 metadata tag via PyAV (stream copy, movflags=use_metadata_tags), so
  exporting produces a NEW file and the source is never modified.
- Two server routes: GET /lanpaint/video_mask_meta (read the payload back)
  and POST /lanpaint/export_mask_video (remux into input/<base>_masked.mp4).
- The mask editor exports the painted keyframes (base64 PNGs) + audio
  intervals into the video; loading a video with the tag auto-restores the
  masks (editor and node preview). A video without the tag clears the
  workflow's masks - the video is the source of truth. The export also
  offers a native save dialog (showSaveFilePicker) when available.
- The video widget upload now uses video_upload so MP4s are selectable in
  the file chooser (was image_upload).
- LanPaint_ImageEncode accepts 5D latents from video VAEs encoding a single
  image (e.g. the Hunyuan video VAE) - fixes the Hunyuan example.

Example set conversion (one example per supported model, new files, old
pairs removed after being superseded):
- New *ImageEncode/*EncodeDecode example workflows for SDXL, Flux.1,
  Flux.2 Dev, Flux2 Klein, SD3.5, Hunyuan, Ideogram4, Krea2, Qwen Edit,
  Qwen Image, Z-Image, Wan 2.2 and MiniMax H3 (AV), all using
  LanPaint_ImageEncode/ImageDecode instead of VAEEncode +
  SetLatentNoiseMask + VAEDecode + MaskBlend; every example references its
  own example folder's masked input (hash-verified); preview jpgs generated
  from the test runs; the H3 example ships its output mp4 as a preview.
- HiDream example removed on request; Example_29 added with the H3 masked
  input video.

Fixes along the way: normalize node input.link/output.links fields against
the links arrays in all converted workflows (the frontend renders
null-link nodes as disconnected); stale Inpainting_mode widget removed from
the Flux.2 sampler; Lambda defaults 5.0 (first-order scheme); torchaudio
resample test assertion relaxed (edge ringing); the videometa tests updated
to the final export signature.
2026-08-09 20:41:45 +08:00
scraed f590861bd5 Add image encode/decode nodes and fix preview frame alignment
LanPaint_ImageEncode replaces VAEEncode + SetLatentNoiseMask (plus the
manual mask ImageScale): the mask is snapped to the latent's actual
spatial size (nearest-exact) so VAE padding can never mismatch again.
LanPaint_ImageDecode replaces VAEDecode + LanPaint_MaskBlend (plus the
dimension-alignment round trip): the decoded image is resized to the
original's exact dimensions, then merged with the original inside the
mask with a MaskBlend-style boundary. The image workflow collapses to
LoadImage -> ImageEncode -> sampler -> ImageDecode -> SaveImage.

merge_video_with_mask now accepts a single 2D mask (broadcast over the
frame batch) and keys the merge count off the image/frame tensors
instead of the mask, so a 1-frame mask no longer truncates a batch.

Three frontend fixes for frame-accurate previews in the video mask
editor: (1) measureVideoFps now spans about 80 percent of the duration
instead of 1 second - the short span had +-1 frame of counting
uncertainty, so the editor and node preview could round to different
integers (e.g. 29 vs 30) and their frame grids drifted by a frame;
(2) the preview overlay maps playback time to the frame index with
floor (the browser presents floor(t*fps), round() mislabeled by one
whenever the video paused mid-frame); (3) the editor's frameAt seek
gets a +1e-6 nudge so index/fps float dust cannot land on the previous
frame.

Also fix a save-race crash: if the editor dialog is closed while the
keyframe uploads are in flight, _flash queried this.el after it was
nulled and the handler reported Save failed even though the save
succeeded. _flash now tolerates a closed editor.
2026-08-09 00:27:11 +08:00
scraed 7514e28048 Use scipy EDT and share mask-morph math between frontend and backend
The video mask editor's backend interpolation was dominated by the
pure-Python Felzenszwalb-Huttenlocher EDT: 20 keyframes at 864x480 took
~168s. _edt_2d now uses scipy.ndimage.distance_transform_edt (exact same
distances, C speed, ~100x faster; the Python fallback stays for
environments without scipy) - the same scenario now takes ~1.6s.

The frontend preview and the backend no longer keep two separate morph
implementations that can drift: the pure math (EDT, SDF, shifts, sigmoid,
per-frame morph) moved to web/lanpaint_mask_math.js, used by the editor
extension and pinned against the backend by tests/parity_mask_math.mjs +
test_js_python_mask_math_parity, which feeds the same 8-bit keyframes to
both sides and asserts the soft masks match within 1e-4 and the 0.5 level
sets are pixel-identical. The preview also switched from the quantized
sigmoid LUT to the exact sigmoid the backend uses.

Fixed a caller-contract bug found while verifying: computeSDFEntry
expects a pure alpha array, but the extension passed raw RGBA bytes
(getImageData data), so soft strokes were read as fully solid and the
index alignment scrambled the SDF (visible as odd strips). The caller now
extracts the alpha channel, and the function throws when handed a
non-w*h array so the mistake fails loudly.
2026-08-08 19:09:11 +08:00
scraed 7708715590 Add video mask editor with audio mask intervals
LanPaint_VideoMaskEditor now outputs a per-frame video mask and an audio
mask [F] (hard 0/1 at video frame rate) painted from time intervals in
the new hidden audio_mask widget. The frontend shows an audio waveform
strip under the video preview with red masked intervals and a playhead,
and the editor dialog supports drag-to-paint / click-to-clear intervals.

LanPaint_MiniMaxAudioEncode becomes a pure encoder (mask_start/mask_end
removed); the workflow attaches the editor's audio mask to the audio
latent with SetLatentNoiseMask and Concat AV Latent nests both per-stream
masks.

Video masks get the temporal union in reshape_mask: a latent token covers
~4 video frames and is all-or-nothing, so each token takes the window-4
max of its frames (nearest-exact) instead of trilinear averaging, which
could erase sparse temporal strokes. The stock per-stream mask prep is
routed through our prepare_mask (video_inpainting for 5D targets) and
handles the 1D [F] audio mask -> audio latent tokens case. Also handles
the VideoFromFile.get_frame_rate rename in newer ComfyUI.
2026-08-08 01:27:12 +08:00
scraed 0ce81f2a49 Shrinked info and added a button instead 2026-04-11 11:10:01 +08:00
scraed b4fee48b87 restore tests 2025-02-28 00:05:03 +08:00