Repository navigation
Conversation
Adds `plugins/antianqi/jianying`: a Windows-only MiniMax Code Plugin that
grades video with a pixel-for-pixel re-implementation of Jianying's own
grading math, and edits Jianying drafts through the application's local
named pipe.
The Plugin ships two runtime layouts in parallel:
- `plugin.json` + `mcp.json` — the portable Agent Plugins 1.0 shape read by
mcode 0.3.x.
- `.claude-plugin/plugin.json` — the mcode 0.4.0+ manifest, with the six
Skill paths and the stdio MCP server.
MCP layer (8 verbs, `mcp/server.mjs`, zero npm dependencies): `draft_open`,
`draft_read`, `draft_patch`, `draft_commit`, `render_frames`, `grade_apply`,
`audio_mix`, `verify_diff`. Every verb has a hand-written input schema under
`mcp/tools/`; unknown keys and out-of-range values are rejected before the
call leaves the plugin.
Skill layer (6 Skills): `jianying-cut` (grade and export), `jianying-draft-edit`
(edit a draft through the 27 official behaviour operations), `jianying-review`
(multi-dimension review), `jianying-talk-cut` (cut a spoken take), plus two
library Skills — `jianying-verify` and `jianying-grade-chain` — that only
activate when another Skill references them.
Shared knowledge lives in `references/` (chain API semantics, verification
discipline, pitfalls, machine-readable capability dictionary) and the
deterministic guardrails in `scripts/`.
Why not use the official exporter: the official CLI is blocked by `-100007`
(session mode) and `-100015` (unauthorized host process), and its command
tree is five days older than the application build. The Plugin re-implements
the grading math instead (13/13 features reproduced to <= 1 LSB) and says so
in the README rather than working around the block.
Design compliance
- Both manifests declare `name: jianying`, matching the Plugin directory,
and license `Apache-2.0` with an Apache-2.0 `LICENSE` file.
- The v0.4 manifest lists all six Skills as `./skills/<name>/SKILL.md`. Every
path resolves, and every frontmatter `name` equals its directory name —
a mismatch is silently dropped by the runtime with no diagnostic, so
`test/jianying.test.mjs` pins both halves.
- The MCP server uses the `stdio` transport, a single `node` argument and a
contained `./mcp` working directory. The Plugin does not set `PLUGIN_ROOT`
or `PLUGIN_DATA` itself.
- Every runnable script invocation inside a Skill is anchored to
`${PLUGIN_ROOT}`; every referenced `scripts/` and `references/` file exists.
- The Plugin ships no network client, no credential literal, no telemetry and
no third-party service. The only socket it opens is the local Windows
named pipe of a running Jianying instance.
- No contributor machine path is hardcoded: paths resolve from `JYAI_HOME`,
`HOME`, `LOCALAPPDATA`, `ProgramFiles` and `PATH`.
- README discloses the platform, a copyable example prompt, the expected
result, the required executables, the account/paid-service position and
the four-part data behaviour (no credentials / no network / no telemetry /
no third-party services).
- The six Skills stay well inside the runtime limits (64 Skill directories,
8 MCP servers per Plugin).
Validation
- `node scripts/validate.mjs` -> `OK plugin antianqi/jianying`;
`Validated 30 hosted Plugins and all examples.`
- `npm run check` (validate + the repository test suite) is green on
ubuntu-latest, which is the only job the repository runs.
- `node --test test/jianying.test.mjs` -> 10 pass / 0 fail.
Test evidence
- `test/jianying.test.mjs` (new, 10 cases): both manifest layouts, every
Skill path in the v0.4 manifest resolving with a matching frontmatter name,
the portable layout exposing the same six Skills, the stdio transport and
its portable working directory, all eight verb schemas parsing, script
invocations anchored to `${PLUGIN_ROOT}`, referenced files existing, the
absence of a network client / credential literal / telemetry, the absence of
contributor machine paths, and the README disclosures.
- Negative-injection audit of that file: seven defects injected one at a
time, the suite re-run, and the file restored from a backup afterwards.
Each injection failed for the expected reason — frontmatter name mismatch,
description over 1024 characters, a hardcoded drive-letter path, a `fetch`
call, a missing "expected result" section, a missing "no telemetry"
disclosure, and a `skills[]` entry pointing at a missing file — and the
suite returned to 10/10 after every restore.
- Plugin self-tests (Windows, manual, need a live Jianying 11.6):
`selftest-mcp.mjs` 17/17, `selftest-draft-edit.mjs` 16/16,
`selftest-talk-cut.mjs` 10/10, `check-env.mjs` 17 OK / 0 MISS. The
verification cases carry negative injections of their own: a synthetic
noise frame set must be judged "not visible", a synthetic large change must
be judged "visible", a white background pushed to 255 must be reported as
newly clipped, and an `expect` that says a field must not change must fail.
- `.github/workflows/jianying-windows.yml` (new) runs only
`node --test test/jianying.test.mjs` on windows-latest, filtered to this
Plugin's paths. The repository's own `validate` job stays on ubuntu-latest;
this is the scoped Windows counterpart the CI file asks a non-tool-map
Windows Plugin to add, rather than re-introducing a repository-wide
windows job.
Known limits (also in the README)
- Windows only, and the draft verbs need Jianying 11.6 running; grading and
export do not need the GUI.
- Structured grading fields (curves, HSL, colour wheels) cannot be written
back into a Jianying preview yet: the draft-side field names are not
confirmed, so grading is local export only.
- The official exporter stays unreachable; the Plugin does not work around
the block.
- `clear` (8-level pyramid) is not wired into the chain.
- `talk-cut` does not do ASR: the official ASR lives in the agent runtime and
is not reachable from the plugin, so it takes a user-supplied SRT and only
decides which lines are filler, pauses or repeats.
The same test file has to guard two layouts: the hosted community layout (plugins/<owner>/<name>/) and a standalone Plugin repository whose root is the Plugin itself, which is what the local marketplace installs from. Detect the Plugin root instead of assuming the hosted path, so the suite can be copied into a standalone repository unchanged.
Two defects in references/capability-dict.json, the contract an agent reads
to decide what it can do:
- verbs.draft_patch.ops listed only 'video.add', so the contract claimed one
op where the Plugin implements all 27 official behaviour operations.
- acceptance.verify_diff still stated the uncalibrated criterion
('max MAD > threshold'). The official renderer is not deterministic: the
same draft rendered twice differs in 14-17 of 108 frames with a worst-case
MAD of 48-123, two orders of magnitude above the threshold. An uncalibrated
MAD cannot tell a change from noise, so the contract now requires the
noise floor to be measured (noiseDir) and names the semantic criterion
(expect over the written draft's per-segment field diff) as the one that
does not depend on pixels.
The draft_patch guard now also states, in machine-readable form, that none of
the 27 official operations sets a grading or filter parameter: they cover
structure, volume, layout, text and undo/redo only. Grading parameters
therefore cannot be written into a draft yet (the draft-side field names are
not confirmed), and grading goes through grade_apply, which renders from a
media file and never touches the draft.
The pitch said 'we can export a graded film', which reads as a claim about producing the deliverable. What the re-implementation actually buys is verification: it is a faithful model of Jianying's grading maths (13/13 features, <= 1 LSB, byte-identical curve LUTs), so a grading result can be confirmed from the chat instead of by opening Jianying and squinting at a preview. That framing also needs the reason it is worth doing, which was measured here for the first time. The official render path is PNG frames -> h264 High / yuv420p / 8bit mp4, and that step is lossy: comparing the PNG originals against frames pulled back out of that same mp4 gives MAD 2-8, maxAbs 157, and 95% of pixels changed. Checking a grading against a preview that carries its own 8-bit 4:2:0 projection cannot settle the question, and the 1 LSB model settles it directly. README now leads with the verification framing and the error table; EVIDENCE.md records the measurement.
…w work
Three claims in the Plugin were wrong, and this corrects all of them.
1. "The official behaviour operations are 27" — wrong. The 27 came from a
historical whitelist constant in the application's app-server.mjs, and we
tested only that list. Sweeping the full set against the bridge shows
**18 domains / 199 operations, every one of them admitted** (173 accepted
with empty parameters, 26 rejected only for missing parameters, **0**
unsupported). The capability dictionary now lists all 199, and
references/official-ops.md records the sweep, the confirmed parameter
contracts and the traps below.
2. "Grading parameters cannot be written into a draft" — wrong, the door is
open. effect.add / effect.updateAdjustParams / adjustment.add /
adjustment.setBasicColor / filter.add / keyframe.* / video.setBasicColor
are all admitted, and the draft already carries the matching material
collections (materials.hsl, hsl_curves, color_curves, log_color_wheels,
primary_color_wheels). effect.add lands a materials.video_effects entry and
adjustment.add lands a materials.placeholders entry with type "adjust".
The parameter contracts are not mapped yet, so local export is still the
main path, but the boundary is no longer "impossible".
3. "segment.setTimeRange takes startTimeUs/startUs plus durationUs" — a false
positive. The application answers accepted:true with changedPaths:["/"],
and the time range in the draft comes back {start:0, duration:0}: the
operation does not know those key names and writes zero for the fields it
did not recognise. Nine parameter shapes were tested in isolation.
New: subtitles. The bridge's own normalisation function shows that text.add
accepts startTime and duration directly, so a subtitle track is just a text
track with time ranges — no split-then-retime dance. Measured on a copy:
text.add {content:"第一句字幕", startTime:1000000, duration:2500000}
-> text track, 1 segment, target_timerange = {start:1000000, duration:2500000}
The application aligns to frame boundaries, so the tolerance is one frame
(33333us at 30fps). scripts/add-subtitles.mjs turns an SRT or a talk-cut plan
into that track, verifies the time ranges by reading the draft back, and
commits; scripts/selftest-subtitle.mjs covers it, including a negative case
that proves the probe can tell "has subtitles" from "has none".
Also recorded in references/official-ops.md: the application ships a
dedicated text.addSubtitles operation with per-word timing
(material.words[]), but its parameter shape is not mapped yet, and
needInsertTargetTrack=true inserts into whichever track trackIndex points at
— even a video track — so several subtitles share one text track only with
needInsertTargetTrack=false plus that track's index.
Validation
- selftest-mcp 17/17, selftest-draft-edit 16/16, selftest-talk-cut 10/10,
selftest-subtitle 4/4, check-env 17 OK / 0 MISS.
- node --test test/jianying.test.mjs 10/10.
- node scripts/validate.mjs -> OK plugin antianqi/jianying; 30 hosted Plugins
validated.
…o AI effects
Two things land here.
The operation set is now the whole thing. The v0.3 schema carried a
27-entry enum, which came from a historical whitelist constant in the
application rather than from the bridge's actual authorisation. Sweeping the
full set against the bridge shows 18 domains / 199 operations and **not one
of them is rejected** (173 accepted with empty parameters, 26 rejected only
for missing parameters, 0 unsupported). The enum now carries all 199, the
capability dictionary lists them, and references/official-ops.md records the
sweep together with the parameter contracts that were confirmed against the
draft rather than against "the application said accepted".
The audio AI effects work, and they differ from each other in an important
way:
- Denoise is `video.denoiseOn`, not `audio.denoiseOn`. Parameters
{segmentId, isDenoise?, denoiseMode?, denoiseRate?} land in
materials.realtime_denoises as {is_denoise, denoise_mode, denoise_rate}
alongside sami_name "denoise_v2" and sami_version "1.0" — a local SAMI
realtime engine, so the denoise is computed on the machine.
- Vocal beautify is `audio.setVocalBeautify` {segmentId, enable:true},
landing in materials.vocal_beautifys as {enable:true, type:"vocal_beautify",
ambient_sound_level:75, voice_change_mode:null, production_path:""}. An
empty production_path means realtime rather than a cloud product, so this
one is computed locally too.
- Voice change is `audio.setVoiceEffect` {segmentId}, landing in
materials.audio_effects.
- Vocal separation is different: `audio.setVocalSeparation` builds
materials.vocal_separations with {type:"vocal_separation", choice,
removed_sounds, time_range, final_algorithm:"", production_path:""}. The
empty production_path is the point — the separated audio is a task result,
so the reference can be written but the artefact is not ours to produce.
Six parameter shapes were tried and none changed the draft, so the
contract is still unmapped.
scripts/selftest-audio-ai.mjs drives all of this through the Plugin's own
draft_patch verb rather than the raw pipe, and checks the draft afterwards:
8/8, including a negative case that requires the three material collections
to be empty before the change, so the probe cannot pass vacuously.
Also confirmed here: the key names come in both styles (isDenoise and
is_denoise, denoiseMode and denoise_mode are all accepted), while `enable`
is only `enable` — `isEnable` is ignored.
Validation
- selftest-mcp 17/17, selftest-draft-edit 16/16, selftest-talk-cut 10/10,
selftest-subtitle 4/4, selftest-audio-ai 8/8, check-env 17 OK / 0 MISS.
- node --test test/jianying.test.mjs 10/10.
- node scripts/validate.mjs -> OK plugin antianqi/jianying; 30 hosted Plugins
validated.
…ot the same as present in the output The membership check fires at export, not while editing: the official FAQ says advanced features marked with a VIP crown (voice cloning, smart denoise) only prompt for a subscription before the video is exported. That matches what we measured — without a membership, video.denoiseOn, audio.setVocalBeautify and audio.setVoiceEffect all wrote into the draft without complaint. It also means the earlier phrasing needs a correction. The export pipeline is the official renderer for picture (player domain) plus our own ffmpeg mix for sound, and that mix handles volume, fades, speed and loudness only: it does not read realtime_denoises or vocal_beautifys. The official renderer emits a video stream with no audio track at all, the official media domain only has file-level tools, and the official exporter is blocked by -100007 and -100015. So the effects land in the draft, and Jianying will honour them when it exports, but the files we produce do not carry them. Two consequences recorded in references/official-ops.md and the capability dictionary: the denoise and beautify entries now say explicitly that landing in the draft does not mean landing in our output, and the acceptance line separates "written into the draft" from "present in the delivered file". Also worth recording from the local 11.6 install: the paywall surfaces in the agent panel (vip_use_popup) and answers with gateId, action "retry" and expectedRevision, which means a membership block is distinguishable from a parameter mistake — a distinction the verification discipline should carry. Making the effect audible in our own output means implementing it in the mix chain instead (ffmpeg afftdn for denoise, EQ and compression as an approximation for beautify, a Demucs-class model for separation), and saying plainly that those are not Jianying's own DSP.
…inst a global noise floor The user pointed out that Jianying also has a live preview while editing, which challenged how we had attributed the "official renderer noise floor". Measuring it properly, in one session, for the same target timestamp: - capture the frame twice without seeking: 4 of 6 timestamps differ - seek away and back, capture again: 4 of 6 differ - the VEF1 timestamp delta against the requested time: 0 every time So the non-determinism is not a mis-aimed frame, not a time-alignment problem and not a globally unstable renderer — a simpler one-line draft is fully deterministic (0/6) while this draft is not, and the same draft rendered twice differs in 69 of 108 frames. It happens per capture, on particular frames: frames 001-004 come back wholly different (MAD around 123, maxAbs 255) while frames 000 and 005-009 differ by under 1.7. A preview engine like this is also what the GUI runs, so nobody notices it during playback, but a static per-pixel comparison magnifies it. That makes a single global noise floor too coarse, and actively wrong: with one frame sitting at noise 128 the global bar becomes 192, which then buries a real change of 64 on a perfectly stable frame and reports a false FAIL. The criterion is now per frame, bar_i = max(threshold, noise_i * noiseK), so a frame whose own noise already exceeds the threshold counts as undecidable while a real change on any stable frame is still detected. verify_diff now reports unstableFrames, visibleMaxMad, visibleFrame and a per-frame frameDetail, and the self-test covers it: three synthetic frame sets where one frame is unstable and another carries a real change, asserting that the unstable frame is flagged and the stable change still reads as visible. Worth stating plainly: this does not touch the grading fidelity result. The 13/13 at 1 LSB is measured against an offline GL run of the official shader fragments, not against frames pulled from the preview renderer, so it was never exposed to this non-determinism.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Adds
plugins/antianqi/jianying— a Windows-only MiniMax Code Plugin that grades video with apixel-for-pixel re-implementation of Jianying's own grading math, and edits Jianying drafts through
the running application's local named pipe.
draft_open·draft_read·draft_patch(27 official behaviour operations) ·draft_commit·render_frames·grade_apply·audio_mix·verify_diff—mcp/server.mjs, stdio, 0 npm dependencies, one hand-written input schema per verb undermcp/tools/jianying-cut·jianying-draft-edit·jianying-review·jianying-talk-cut+ two library Skills (jianying-verify,jianying-grade-chain) that only activate when referencedreferences/— chain API semantics, verification discipline, pitfalls, machine-readable capability dictionaryscripts/— the plugin's own MCP client, an environment self-check, three self-tests, and the talk-cut planner/applierBoth runtime layouts ship in parallel:
plugin.json+mcp.json(portable Agent Plugins 1.0, read bymcode 0.3.x) and
.claude-plugin/plugin.json(mcode 0.4.0+ manifest, with the six Skill paths and thestdio MCP server).
The grading math is re-implemented rather than delegated: the official exporter is blocked by
-100007(session mode) and
-100015(unauthorized host process), and its command tree is five days older thanthe application build. The Plugin re-implements the 13 grading features to ≤ 1 LSB and states the
block in the README instead of working around it.
User value
A MiniMax Code user can grade a clip and export a 10-bit master without opening Jianying, and can
edit a Jianying draft — add a clip, trim, split, change a segment's volume — while the application stays
the only execution platform and the only renderer.
Example prompt
Expected result
grade_applyexportsgraded10.mp4(direct point-wise path, recommended) andgraded_lut.mp4(65³ LUT path), and returns a per-node effectiveness probe: every node in the spec must produce a
non-zero MAD, and a zero node fails the verb instead of silently dropping that node.
quality / loudness / timeline / draft integrity) and reports the smallest repair range, not a vague
suggestion.
Two more example prompts, for the parts that are not grading:
Plugin submission checklist
plugins/<github-owner>/<plugin-name>—plugins/antianqi/jianying.plugin.jsonname matches the Plugin directory —jianyingin both manifests.README.mdincludes a real example prompt and expected result.LICENSEandplugin.jsondeclare an open-source license — Apache-2.0 in all three places.Windows 10/11;
ffmpeg6.0, Python 3.14, Node ≥ 22, Jianying 11.6 (running for the draft verbs);JYAI_HOMEpointing at the toolchain; no account, no paid service, no API key.credentials, no telemetry, no third-party services; the only socket is the local Windows named
pipe of a running Jianying instance; drafts are read-only and every write happens on a copy with
a per-file sha assertion that the real draft did not change.
included — 31 text files, no symlinks, no binaries, no scaffold placeholders.
TODOhas been replaced.npm run checkpasses.Evidence
node scripts/validate.mjsnode --test test/jianying.test.mjs— 10 pass / 0 fail. The new test pins the things that failsilently: every
skills[]path in the v0.4 manifest resolves and its frontmatternameequals itsdirectory name (a mismatch is dropped by the runtime with no diagnostic), the stdio transport and its
portable working directory, all eight verb schemas,
${PLUGIN_ROOT}anchoring of script invocations,the existence of every referenced file, the absence of a network client / credential literal / telemetry,
the absence of contributor machine paths, and the README disclosures.
Negative-injection audit of that test — seven defects injected one at a time, suite re-run, file
restored from a backup afterwards; each injection failed for the expected reason and the suite returned
to 10/10 after every restore:
name!= directory namefrontmatter name must equal its directory namedescriptionover 1024 charactersdescription is 1285 chars (limit 1024)must not hardcode a drive letter pathfetch()in a scriptmust not make HTTP requestsREADME must state the expected resultREADME must disclose telemetry behaviourskills[]entry points at a missing filePlugin self-tests (Windows, manual — they need a live Jianying 11.6 and a real draft; the paths are
supplied through
JYAI_TEST_DRAFT, and the suite prints an explicit SKIP when it is not set, so it cannever report a false green):
selftest-mcp.mjsselftest-draft-edit.mjsselftest-talk-cut.mjscheck-env.mjsThese carry their own negative injections: a synthetic noise frame set must be judged "not visible", a
synthetic large change must be judged "visible", a white background pushed to 255 must be reported as
newly clipped, and an
expectthat says a field must not change must fail.Windows CI —
.github/workflows/jianying-windows.ymlruns onlynode --test test/jianying.test.mjson
windows-latest, filtered to this Plugin's paths. The repository'svalidatejob stays onubuntu-latest; this is the scoped Windows counterpart that.github/workflows/ci.ymlasks anon-tool-map Windows Plugin to add, rather than re-introducing a repository-wide Windows job (that job
was dropped in round-9 because it failed on files a PR does not own).
Known limits (stated in the README as well)
yet — the draft-side field names are not confirmed, so grading is local export only.
clear(8-level pyramid) is not wired into the chain.talk-cutdoes not do ASR: the official ASR lives in the agent runtime and is not reachable from aplugin, so it takes a user-supplied SRT and only decides which lines are filler, pauses or repeats.
Standalone copy
The same Plugin is also published as a standalone repository whose root is the Plugin root, so it can
be installed from the local marketplace before this PR merges:
That repository carries the same 31 Plugin files, an
EVIDENCE.mdwith the measured numbers, and thesame
test/jianying.test.mjs— the focused suite was made layout-agnostic (it detects the Plugin rootinstead of assuming the hosted
plugins/<owner>/<name>/path), so one file guards either layout.