Skip to content

feat(LTX25-RETAKE): regenerate a time window of an existing clip, on upstream's frame-folder arm (#924, #987) - #992

Merged
localai-bot merged 7 commits into
mainfrom
row/LTX25-RETAKE
Aug 16, 2026
Merged

feat(LTX25-RETAKE): regenerate a time window of an existing clip, on upstream's frame-folder arm (#924, #987)#992
localai-bot merged 7 commits into
mainfrom
row/LTX25-RETAKE

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

RetakePipeline keeps the source clip outside [start_time, end_time) and regenerates what is inside it from the prompt (Lightricks/LTX-2 packages/ltx-pipelines/src/ltx_pipelines/retake.py:53,151-329 @ fd4ded7f2d88d3da713abcdd4ad41ecc4a9314ca, verified with git rev-parse HEAD on a clean local checkout before any anchor here was taken). It is reachable from vllm_video_generate and from ltx2-gen through --pipeline-kind retake, --ref-video and three window flags.

Issue #924. Spec .agents/specs/ltx25-retake.md, committed before any implementation. Also closes #987, filed and fixed in this flow.

The source is a frame directory, and that is upstream's own second arm

Upstream opens a container with PyAV (media_io/decode.py:226) and no demuxer is vendored here — src/vllm/entrypoints/openai/video_api.cpp:115-121 says so where it defines the reference-video field — so a .mp4 is refused by name. Upstream also carries a folder arm for exactly the no-container case, and three consequences follow from its own lines rather than from a local simplification:

  • the frame rate must be supplied, because a folder has none (decode.py:213-215);
  • a folder carries no audio stream at all (utils/helpers.py:261-262 returns None before opening anything);
  • both of retake's audio predicates are conjunctions with initial_audio_latent is not None (retake.py:279,282), so regenerate_audio has no observable effect on this arm and the soundtrack is generated fresh.

The last one is surprising. It is upstream's, it is mirrored rather than repaired, and test_ltx2_retake pins it so the next reader finds it asserted instead of re-deriving it.

What the port had to get right

TemporalRegionMask (ltx-core/conditioning/types/noise_mask_cond.py:9-45) lands as a replacement of the all-ones denoise mask create_initial_state writes (ltx-core/tools.py:158-161), which is what state.denoise_mask.copy_ does at :44. Nothing else in the phase loop changes, and that is the design rather than a coincidence: every consumer of that mask already broadcasts it, so seeding video_initial with the encoded clip and zeroing the mask outside the window is the whole mechanism. video.clean = video.latent is what makes the blend restore the source rather than zeros (tools.py:156).

Four things in it fail silently, and each has a case:

  • causal_fix defaults the other way at the call site. get_pixel_coords declares False (patchifiers.py:140); TemporalRegionMask calls it with getattr(latent_tools, "causal_fix", True) (noise_mask_cond.py:33). Reading the function's default moves every video boundary by time - 1 = seven pixel frames and still renders.
  • The two sides use different coordinates. Video bounds are latent frames pushed through get_pixel_coords and divided by fps (:31-35); audio bounds are already seconds (:27-29, via patchifiers.py:216-249) and must not be scaled again.
  • The test is overlap, not containment (:39), so a token straddling either edge is inside. Containment drops the boundary tokens and leaves a seam at each end.
  • The write replaces, it does not conjoin (:44).

Ltx2ConformLatentLength truncates or zero-pads (utils/helpers.py:149-162) and is deliberately a second function rather than a flag on the audio-to-video one, which truncates only (a2vid_two_stage.py:202) and where a short take is an error because create_initial_state asserts the shape (ltx-core/tools.py:146-148). Both polarities are upstream's, on two callers; one shared helper would have to pick one and be wrong for the other.

The retake recipe is its own row in ResolveLtx2PipelineRecipe rather than distilled_two_stage with a phase removed: that recipe renders stage 1 at spatial_downscale = 2, and a full-resolution source latent does not fit that grid, so a retake on it is refused by name. One phase, DISTILLED_SIGMAS (retake.py:287, distilled defaults True at :85 and the CLI hard-codes it at :359), plain Euler (DiffusionStage.__call__ defaults to EulerDiffusionStep(), utils/blocks.py:526-527, and retake overrides neither) and no negative prompt (SimpleDenoiser at :290-294, [prompt] alone at :259).

Arms

arm state
bf16 / FP8 / NVFP4 served — retake adds no weight-bearing module, so whatever Ltx2VideoEngine::Load already resolves serves it unchanged
GGUF not applicable, and the reason is upstream's rather than a preference: its inference quantization kinds are exhaustive and assert_never-closed at ltx-pipelines/utils/quantization_factory.py:23-26 (assert_never at :50) — fp8-cast, fp8-scaled-mm, nvfp4-cast, nvfp4-prequant. There is no GGUF arm to port
container source (.mp4, …) refused by name — no demuxer
EXR-frame folder and any HDR colour space refused by name — no OpenImageIO, no scene-linear path; out of scope per .agents/specs/ltx25-retire-dead-arms.md:167
retake on a (kind, version) pair the table does not carry refused by name
retake alongside audio_path refused by name — retake's audio comes from the source file only (retake.py:250-256)
a clip off the 8k+1 grid or off the 32-pixel grid refused, naming the value that would have worked (retake.py:347-353)

Two stale refusal sentences repaired in the same flow (#987)

ltx2_video.cpp claimed "nothing reads ref_video_dir at all". That was false about the tree when it was written: MiniMax-H3 consumes the directory in full (ReadReferenceClipChw, src/vllm/multimodal/minimax_h3_video.cpp:135, called at :650), and #975 inherited the wider claim from this message. It is doubly false now. It also claimed "there is no AUDIO_VAE_ENCODER key filter", which c2019b0e3 landed.

Both are rewritten in the WHAT IS *NOT* THE REASON shape so the ruled-out cause stays visible, and the guarding case asserts the local fact that the LTX side now reads the directory — a case asserting only upstream symbol names is exactly how the first sentence survived.

The reference-conditioning arm itself stays refused and #975 stays open. The !wants_retake guard narrows the refusal rather than weakening it: retake encodes the clip at its own resolution and seeds the initial latent, while the reference item is downscaled by the adapter's factor, temporally subsampled and appended as extra tokens to a stage-1-only adapter (iclora_utils.py:112-117,87-89,144-148). Serving the first says nothing about the second, and mutation M8 (dropping the guard) is red.

Upstream ships no tests

find /home/mudler/_git/LTX-2 -name 'test_*.py' returns 0 lines at this pin, and so does find . -iname '*test*' -not -path './.git/*'. "Port the upstream tests in the same change" therefore has nothing to port, and the obligation became pinning upstream's behaviours against file:line anchors, plus one assertion tied to a local fact so the suite can see a refusal going stale. This is stated rather than quietly skipped, because a reader who sees no ported test file should be able to tell "upstream had none" from "the porter did not look".

Reachability

Production entry point. vllm_video_generate (include/vllm.h:969) → src/capi/vllm_c.cppLtx2VideoEngine::Generate, where the knobs are parsed, the clip read through Ltx2ReadFrameDirectory, encoded, conformed, and the mask assigned into video.mask in the phase loop. The clip rides vllm_video_params::ref_video (include/vllm.h:912), an ABI field that already existed and that this engine previously only tested for emptiness — no struct grows a field, so sibling rows editing that header concurrently are unaffected. ltx2-gen reaches the same path on its default configuration.

Test entry. The five test_ltx2_video cases start at LoadVideoEngine and Generate, not at Ltx2TemporalRegionMaskVideo. M1 deletes the production call site: that suite goes RED (exit 1) while test_ltx2_retake stays green (exit 0), which is the split .agents/reachability.md predicts and the reason the unit suite is not the proof.

/v1/videos forwards no engine extras today (#928), so the CLI and the C ABI are what this row claims and the HTTP surface is not.

Mutations — three facts each

Restores verified by sha256sum of both product files, not assumed. Exit codes captured directly, never through a pipe. Filters are comma-free and the case count is printed beside every exit code, so a filter that matched nothing cannot read as a pass.

# mutation git diff --stat BUILT exit (video / retake) verdict
M1 delete the production call site of the mask ltx2_video.cpp | 2 -- YES, compile_err=0 1 / 0 DETECTED
M2 never seed video_initial with the encoded clip ltx2_video.cpp | 2 +- YES, compile_err=0 0 / 0, then 1 / 0 SURVIVED, then DETECTED
M3 take get_pixel_coords' own causal_fix=false ltx2_video.cpp | 2 +- YES, compile_err=0 0 / 0, then 1 / 0 SURVIVED, then DETECTED
M4 leave the frozen stream at the schedule's scalar sigma ltx2_video.cpp | 2 +- YES, compile_err=0 1 / 0 DETECTED
M5 conform truncates only, never pads ltx2_retake.cpp | 1 + YES, compile_err=0 0 / 1 DETECTED
M6 containment instead of overlap ltx2_retake.cpp | 2 +- YES, compile_err=0 1 / 1 DETECTED
M7 ignore initial_audio_latent is not None ltx2_retake.cpp | 4 ++-- NO, compile_err=1, then YES compile_err=0 — , then 0 / 1 NOT ESTABLISHED, then DETECTED
M8 drop the !wants_retake guard ltx2_video.cpp | 2 +- YES, compile_err=0 1 / 0 DETECTED
M9 the 8k+1 refusal rounds the snapped value UP ltx2_retake.cpp | 2 +- YES, compile_err=0 1 / 1 DETECTED
M10 conform pads once instead of per channel ltx2_retake.cpp | 4 ++++ YES, compile_err=0 0 / 1 DETECTED

M2 and M3 survived on the first run. Both were claims made in a comment and observed by nothing. M2 is the sharper: the suite recorded retake_latent_absmax, which observes the encode, and inferred the latent reached the phase — a build that reads the clip, encodes it, records the digest and starts from zeros passed everything and rendered a clip of the right length with the right mask. The repair renders two different sources at the same seed and window and requires the pixels to differ. M3's unit case was already red under the flip, but it calls the function directly and says nothing about what the production call site passes; the end-to-end window [0.05, 0.10) selects the same latent frame under both polarities, so a second window [0.30, 0.40) was added where they disagree. M7 failed to build (has_audio_latent unused under -Werror) and is recorded as establishing nothing, then re-run with the parameter referenced.

Merge hazard

ltx2_video.cpp:363-364 carries the READER ANCHORS list, derived and string-compared by test_ltx2_video. This branch shifted it by one (779 …780 …) and re-derived it at the final tree. A clean git merge will not warn if another branch shifts it again — re-derive after any merge.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

mudler added 7 commits August 16, 2026 06:50
…s the audio question (#924)

`RetakePipeline` regenerates a chosen time window of an existing clip. This spec
commits before any implementation, as `## Spec before code` requires.

The clip enters as a directory of `frame_%06d.ppm` rather than a container,
because no demuxer is vendored here (`video_api.cpp:115-121`). That is not a
local workaround: upstream carries a second ingestion arm for exactly the
no-container case, and every consequence this row inherits from it is upstream's
own -- the frame rate must be supplied (`media_io/decode.py:213-215`), the folder
has no audio stream (`utils/helpers.py:261-262`), and both of retake's audio
predicates are conjunctions with `initial_audio_latent is not None`
(`retake.py:279,282`), so `regenerate_audio` has no observable effect on this
arm. Mirrored rather than repaired, and pinned by a test so the next reader finds
it asserted.

Two record corrections ride in the spec because deriving them a third time is the
cost this protocol exists to avoid. `ref_video_dir` is NOT read by nothing --
MiniMax-H3 consumes it fully at `minimax_h3_video.cpp:647,650`; what is true is
that the LTX-2.5 engine never reads its contents. And #924's cited recipe-refusal
anchor has drifted from `ltx2_pipeline.cpp:1131-1136` to `:1175-1179`.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…upstream's frame-folder arm (#924, #987)

`RetakePipeline` keeps the source clip outside `[start_time, end_time)` and
regenerates what is inside it from the prompt (`retake.py:53,151-329` @
`fd4ded7f`). It is reachable from `vllm_video_generate` and from `ltx2-gen`
through `--pipeline-kind retake` plus `--ref-video` and the three window knobs.

THE SOURCE IS A FRAME DIRECTORY, AND THAT IS UPSTREAM'S SECOND INGESTION ARM
rather than a local substitute. Upstream opens a container with PyAV
(`media_io/decode.py:226`) and no demuxer is vendored here, so a `.mp4` is
refused by name. Upstream also carries a folder arm for exactly that case, and
three consequences follow from its own lines and are mirrored rather than
invented: the frame rate must be supplied because a folder has none
(`decode.py:213-215`), a folder carries no audio stream
(`utils/helpers.py:261-262`), and both of retake's audio predicates are therefore
false whatever `regenerate_audio` says (`retake.py:279,282`). The last one is
surprising, it is upstream's, and `test_ltx2_retake` pins it so the next reader
finds it asserted instead of re-deriving it.

`TemporalRegionMask` (`noise_mask_cond.py:9-45`) lands as a REPLACEMENT of the
all-ones denoise mask `create_initial_state` writes, which is what
`state.denoise_mask.copy_` does at `:44`, and nothing else in the phase loop
changes. Every consumer of that mask already broadcasts it, so seeding
`video_initial` with the encoded clip and zeroing the mask outside the window is
the whole mechanism. Four things in it fail silently and each has a case:
`causal_fix` defaults TRUE at the call site (`:33`) and FALSE in
`get_pixel_coords` (`patchifiers.py:140`), so reading the function's default
moves every boundary by seven pixel frames; the video bounds are latent frames
and the audio bounds are already seconds (`:27-35`); the test is OVERLAP, not
containment (`:39`); and the write replaces rather than conjoins.

`Ltx2ConformLatentLength` truncates OR ZERO-PADS (`utils/helpers.py:149-162`)
and is deliberately a second function rather than a flag on the audio-to-video
one, which truncates only (`a2vid_two_stage.py:202`) and where a short take is an
error because `create_initial_state` asserts the shape (`ltx-core/tools.py:146-148`).
One shared helper would have to pick a polarity and be wrong for one caller.

The `retake` recipe is its own row rather than `distilled_two_stage` with a phase
removed: that recipe renders stage 1 at `spatial_downscale = 2` and a
full-resolution source latent does not fit that grid, so a retake on it is
refused by name.

TWO STALE REFUSAL SENTENCES REPAIRED IN THE SAME FLOW (#987, filed and closed
here). `ltx2_video.cpp` claimed "nothing reads `ref_video_dir` at all" -- false
about the tree when written, because MiniMax-H3 consumes the directory in full at
`minimax_h3_video.cpp:650`, and doubly false now -- and claimed "there is no
AUDIO_VAE_ENCODER key filter", which `c2019b0e3` landed. Both are rewritten in
the `WHAT IS *NOT* THE REASON` shape, and the guarding case asserts the LOCAL
fact that the LTX side now reads the directory, because a case asserting only
upstream symbol names is how the first sentence survived. The reference
conditioning arm itself stays refused and #975 stays open: it consumes the same
directory through a downscale resize, a temporal subsample and an appended
stage-1 item, none of which retake performs.

Owed and named: a container demuxer, without which retake's audio half cannot
exist at all; and the real-checkpoint render, since the GPU was out of bounds for
this row.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
The row branch was cut at 0e1bee4 and main has moved. Merged rather than
rebased so the spec-before-code order stays visible in the history.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ing observed (#924)

The row's mutation pass found two guarantees asserted only in prose.

M2 deleted the seeding of the phase's initial video latent with the encoded
source clip -- `video_initial = retake_video_volume;` -- and every case stayed
green. `retake_latent_absmax` observes the ENCODE and says nothing about whether
the latent ever reached the phase, so a build that read the clip, encoded it,
recorded the digest and then started from zeros passed the whole suite and
rendered a clip of the right length with the right mask. The repair renders two
DIFFERENT source clips at the same seed and the same window and requires the
pixels to differ; on the mutated build both start from zeros and are identical.

M3 flipped the production call site to `causal_fix=false`, taking
`get_pixel_coords`' own default (`patchifiers.py:140`) instead of the call site's
(`noise_mask_cond.py:33`), and stayed green too. At 24 fps the window
[0.05, 0.10) selects one latent frame under BOTH polarities, so the case could
not see the difference it was written to pin. The repair adds a window,
[0.30, 0.40), where the fix selects one latent frame and its absence selects
both, so the count separates them.

M7 additionally failed to BUILD -- dropping `has_audio_latent` from the audio
predicates left the parameter unused under -Werror -- and a mutation that fails
to build establishes nothing, so it is re-run with the parameter still
referenced. It is DETECTED.

All ten mutations are now detected. Each is recorded with three facts: the
`git diff --stat` after applying, whether it BUILT with the compile-error count,
and the exit code, captured directly rather than through a pipe.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…nged, and the mutation table (#924)

Section 3.3 planned to lift MiniMax-H3's `ReadReferenceClipChw` into a shared
surface, on the ground that writing a second frame walker by hand is what
`AGENTS.md` `## Shared seams` forbids. Reading both sides made that the wrong
seam, and the reason is recorded rather than the section quietly rewritten: H3's
walker returns raw pixels at whatever size the files are, while upstream's video
ingestion runs every frame through `video_preprocess` ->
`resize_and_center_crop` to the target size (`utils/helpers.py:228`, and `:202`
says the folder arm's centre-crop is chosen to match it). This tree already has
that chain on the LTX side as `Ltx2LoadImageAndPreprocess`. Lifting H3's walker
would have shared the six-line file loop and NOT the preprocessing, which is the
half with an upstream anchor and the half that fails silently.

Section 8 adds the mutation table, three facts per row, and the reachability
answer in the two parts the guide asks for. Two mutations survived first time and
both are written up rather than summarized away.

`## Now` records that `ROAD-V1-LTX25` does NOT change lifecycle state here, so
`docs/STATUS.md` has nothing to record and this branch does not touch it.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…hich surfaces reach it (#924)

The retake table listed the per-generation extra keys and left a reader to guess
what to type. It now gives the `ltx2-gen` flag beside each one, says the extras
ride the existing `extra_keys` / `extra_values` array so the C ABI needs no new
field, and states plainly that `/v1/videos` forwards no engine extras today
(#928) -- so the CLI and the C ABI are the reachable surfaces and the HTTP one is
not claimed.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
… resolves (#924)

The recipe-table case enumerates supported and refused `(kind, version)` pairs by
hand, so the two new `retake` rows needed adding on both sides: `retake`/2 and
`retake`/2.5 resolve, `retake`/2.3 is refused by name.

Resolving is the weaker half. Every value on a `retake` recipe is read off
`retake.py` rather than adapted from a neighbour, so each is asserted: ONE phase
at `spatial_downscale = 1` with no input transform (`:313-324`, `:317-318`),
`DISTILLED_SIGMAS` (`:287`, because `distilled` defaults True at `:85` and the
CLI hard-codes it at `:359`), an empty negative prompt and no guidance override
(`SimpleDenoiser` at `:290-294`, `[prompt]` alone at `:259`).

The stepper assertion is made against the SIBLING recipe rather than against a
literal. `DiffusionStage.__call__` defaults `stepper` to `EulerDiffusionStep()`
(`utils/blocks.py:526-527`) and retake overrides neither `stepper` nor `loop`, so
the ancestral sampler `distilled.py` selects for generation 2.5 reaches retake
through nothing. Checking `kEuler` alone would pass on a build where both
recipes were ancestral or both plain; checking it beside
`distilled_two_stage`/2.5 being `kEulerAncestral` cannot.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-bot
localai-bot merged commit 3ce1cf7 into main Aug 16, 2026
26 of 28 checks passed
localai-bot pushed a commit that referenced this pull request Aug 16, 2026
…ine kinds

`LTX25-RETAKE` (`3ce1cf7c7`, #992) landed while this row was in review, and the
two rows overlap on ten files. Four conflicted as source, and all four conflicts
are the same shape: both rows add a pipeline kind, so both extend the recipe
table, the per-generation extras list and the refusal set. Every one is resolved
as a UNION with nothing dropped from either side.

- `ltx2_pipeline.h` — the `ResolveLtx2PipelineRecipe` table comment. The `dfr`
  row and the two `retake` rows now sit together, and retake's paragraphs on why
  it is not `distilled_two_stage` with a phase removed are kept in full.
- `ltx2_pipeline.cpp` — `DfrRecipe` and `RetakeRecipe`. Git's conflict swallowed
  the shared `return recipe; }` tail, so both functions had to be closed by hand;
  each keeps its own body byte-for-byte. The dispatch arms below auto-merged and
  both kinds resolve.
- `ltx2_video.h` — the extras key constants. `temporal_upsample_rounds` and the
  five retake keys are all declared, each with its own comment block unchanged.
- `ltx2_video.cpp` — the known-extras predicate, the "this family defines" list
  and the `Generate` preamble. The preamble conflict is the only semantic one:
  DFR gave `CheckGeneratedKeyframes` a `pipeline_kind` parameter, so the merged
  call is DFR's two-argument form, and both refusal blocks run.

`READER ANCHORS` was re-derived rather than assumed. Both rows insert above it,
so neither side's list was right: every anchor moved by one, `780 …` became
`781 …`, and the list here is the one `test_ltx2_video`'s own walk produced.
Both rows recorded this hazard in advance and both were correct to.

`docs/FEATURES.md` was reconciled BY KEY against `origin/main` rather than
accepted from the automatic merge: 194 keys, 192 byte-identical to main, one
added (the DFR base row) and one changed. The changed row's only difference is
the six-character `GENkf/` removal in its fourth cell; its third cell, which
carries `Temporal x2 ups gated, UNDRIVEN` and which
`.agents/specs/ltx25-resolution-envelope.md:436` asserts is untouched, is
byte-identical to main at 218 characters. `docs/USAGE.md` is exactly main plus
this row's own hunks — the diff against `origin/main` and the diff across this
row's base are equal line for line. `.agents/issue-index.md` union-merged to 253
rows: two added, zero removed, zero duplicate issue ids.

`check-env-doc` and `test_check_env_doc` are red, and they are #995, not this
merge. The only `VT_MOE_EXPERT` line this branch adds anywhere is the #995 index
row itself, and the branch touches none of the checker's four inputs.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot pushed a commit that referenced this pull request Aug 16, 2026
Pinned SHA, per #841. main moved four commits while this row was
measuring and re-landing: #993, #982, #988 and #992. One of them is
directly relevant -- #988 split #810's A2-Q into A2-Q1 and A2-Q2, and
A2-Q1 is the unit this row exists to unblock -- and none of them touch
the fp8 quant registration, the CUDA source list, or either checker this
branch edits.

`.agents/issue-index.md` is the only keyed record both sides wrote.
Verified afterwards that origin/main's copy is a strict PREFIX of the
merged file and that only this branch's own two rows follow it, which is
what append-only means and what a clean-but-wrong interleave would break.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
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.

LTX-2.5: two refusal messages state reasons that are no longer true (ref_video_dir is read; the audio encoder key filter exists)

2 participants