Repository navigation
Conversation
0d50ebf to
7320ccd
Compare
Narrated-video pipeline: HTML slides with staged entrance animations, TTS voiceover measured with ffprobe, burned-in subtitles, deterministic frame-stepping capture, ffmpeg assembly, and ASR verification. - 11 Node scripts, no build step; works with mcode connectors or mmx-cli - 13 themes, 17 layout recipes, image framing primitives - Subtitles confined to their own windows (no overlap); 16:9 and 9:16 canvases - Research phase documented: source grading, cross-verification rules, notes template - Discloses dependencies, accounts, network destinations and data handling (bilingual README) - Validation: node scripts/validate.mjs -> OK (exit 0)
7320ccd to
4104572
Compare
Add Plugin: html2video-for-mcode Narrated-video pipeline: HTML slides with staged entrance animations, TTS voiceover measured with ffprobe, burned-in subtitles, deterministic frame-stepping capture, ffmpeg assembly, and ASR verification. - 11 Node scripts, no build step; works with mcode connectors or mmx-cli - 13 themes, 17 layout recipes, image framing primitives - Subtitles confined to their own windows (no overlap); 16:9 and 9:16 canvases - Research phase documented: source grading, cross-verification rules, notes template, plus how to fetch official-site / press-release text (SPA rendering, PDF-first numbers) - Discloses dependencies, accounts, network destinations and data handling (bilingual README) - Validation: node scripts/validate.mjs -> OK (exit 0)
… them to the repo root) Add Plugin: html2video-for-mcode Narrated-video pipeline: HTML slides with staged entrance animations, TTS voiceover measured with ffprobe, burned-in subtitles, deterministic frame-stepping capture, ffmpeg assembly, and ASR verification. - 11 Node scripts, no build step; works with mcode connectors or mmx-cli - 13 themes, 17 layout recipes, image framing primitives - Subtitles confined to their own windows (no overlap); 16:9 and 9:16 canvases - Research phase documented: source grading, cross-verification rules, notes template, plus how to fetch official-site / press-release text (SPA rendering, PDF-first numbers) - Discloses dependencies, accounts, network destinations and data handling (bilingual README) - Validation: node scripts/validate.mjs -> OK (exit 0)
hetaoBackend
left a comment
There was a problem hiding this comment.
Request changes for exact current head 737ee96.
Blocking security and evidence issues:
- Input-derived IDs and paths are not contained.
skills/html2video-for-mcode/scripts/capture.mjs:75-77,228-230usess.htmlands.idto construct paths and recursively deletes the frame directory;build-video.mjs:55-58,191-197usest.idfor output/frame/ASR paths;plan-timings.mjs:47-50andbuild-video.mjs:102-105also consume script-provided paths. An Agent-editablescript.jsonvalue such as../../victimcan escape the intended build directory and trigger out-of-scope reads/writes/deletion. Add strict ID validation, resolve-and-containment checks, symlink checks, and malicious-ID tests. init-project.mjs:7-16,468-477accepts an existing directory and then overwrites project files without a non-empty check or explicit--force.fetch-official-images.mjs:31-32,102-115accepts arbitrary--out-dirand overwrites files;prep-image.mjs:64-87uses ffmpeg-yfor arbitrary output. This contradicts the README claim that writes stay inside the supplied project directory and creates destructive overwrite behavior. Default to refusing existing/non-empty targets and require explicit force, with output containment enforced.asr.mjs:29-32,65-74allows--base-url/MINIMAX_BASE_URLto replace the endpoint without validation while sending the MiniMax API key as a Bearer token. A misconfiguration or prompt-controlled environment can exfiltrate the credential to arbitrary HTTPS/HTTP endpoints. Default to an HTTPS allowlist for official hosts; make custom endpoints an explicit, separately disclosed dangerous opt-in.fetch-official-images.mjs:25-29,40-46,98-115accepts arbitraryhttp:,https:, andfile:URLs and downloads through a browser/request client without protocol, private-address, redirect, or response-size restrictions. This exposes SSRF and local-file-read/copy behavior. Restrict to validated HTTPS public targets, block loopback/private/link-local/metadata addresses and redirects, bound responses, and make local files explicit opt-in with containment.- The PR adds roughly 2,000 lines of executable scripts but no executable test suite;
evals/evals.jsonis prompt/expected-output data and is not run bynpm run check, while the repository validator does not execute these scripts or validate.claude-plugin/plugin.json. Add automated negative and smoke tests for containment, overwrite refusal, endpoint allowlist, SSRF/file rejection, and a minimal render/checker/build dry-run. The current[code]smithcheck is skipped and cannot substitute for this evidence.
Do not approve or merge until these security boundaries and executable test evidence are present on a new head.
|
Thanks for the review — all five blockers were reproduced against the exact head you flagged and are fixed on the new head (v1.1.0). Point-by-point: 1. Input-derived IDs and paths are not contained — fixed. 2. Destructive overwrite behaviour — fixed. 3. Endpoint replacement could exfiltrate the credential — fixed. 4. SSRF / local-file read in 5. No executable test suite — added. Also in this head (non-blocking, from user feedback while the review was open): pure-CSS/SVG chart recipes with entrance-and-growth animations, a one-switch The validator reports |
追加:放映页(可以先自己放一遍再渲染)+ 修一个"文档说能用、闸门说不能用"的类新提交 1. 新增 单文件、零依赖、 为什么不是"直接打开 2. 3. 修 4. 测试 +21 例(共 93,7 个文件):新增 5. 文档漂移修正:插件树 README 此前落后仓库树一轮(缺整个
|
追加:
|
| 情形 | 页面表现 |
|---|---|
有 clauses + 有 timings.json |
列出该张口播文案(P 可开 / 关) |
| 有 clauses、还没对时 | 只列文案,标题标「(未对时)」—— 口播还没做也能先看 HTML |
没有 clauses,或 --no-script |
面板与口播按钮完全不出现,画面占满整宽 |
顺带修掉一个误导标签:底部提示原写成「X 关配音画对比」(本意是「关动效 / 画面对照」),读起来像是在管音频。现在统一写作「X 动效 / 关动效 对照」,测试里加了断言:页面不得出现「配音」字样。
3. 响应式(此前只考虑了桌面)。 原来右侧固定 320px 面板 + 固定行高的顶/底栏,窄窗口和手机上挤成一团。现在:顶栏/底栏可换行、话题名过长省略号截断;窄窗口与手机上口播面板收成底部抽屉并默认收起(画面优先);手机给触摸条按钮 + 左右滑动翻页;总览网格按宽度自动列数;高度用 100dvh(免得被手机地址栏切掉)。
实测(Playwright,五档视口 + 触屏模拟):1600×900 / 1024×600 / 800×600 / 390×844 / 360×640 全部零横向溢出、零控制台报错,触摸条无标签截断;390×844 上左滑确实翻页;窄屏下点「口播」画面从 385→755px 高。
另外把「还没对时」这一档做实用:没有 timings.json 时,副本按 HTML 里实际用到的 stage 等间隔排(0.3/1.3/2.3s),页面顶部黄条如实标注「不是成片时序」—— 占位值会把动画全挤在 2 秒内,那才是真看不懂。
测试 99 例(+6:不做计时器 / 不得出现「配音」字样 / 响应式与触摸 / 口播三态 / 等间隔兜底)。
…e wrap warning (1.9.9)
|
1.9.9 — cloud-sandbox field batch (sorted by the reporter into skill issues vs environment quirks): 🔴 Entry guard could silently no-op. 🟡 Subtitle wrap warning mis-fired on landscape. The 🟡/🟢 Documented (render.md + symptoms.md): capture runtime scaling with entrance-choreography windows (measured 5×15s ≈ 2250 frames ≈ 6–8 min; quick passes: Tests +2 (256 in fourteen files): Release-gate sequencing: head is now |
…har rule drift (1.9.10)
|
1.9.10 — round-21 review fixes (a two-axis review of 1.9.9 found one hard inconsistency and one wrong baseline; both reproduced before fixing): Rule drift (the hard one). 1.9.9 rewrote the subtitle wrap rule in The published geometry was wrong (correction to the 1.9.9 entry). Its "≈18 CJK per line at 1080, ≈32 at 1920" were the superseded vertical baseline's restatement. The pill's font and padding both scale with Entry guard, per the review: the same-basename fallback now announces on stderr when it fires — the trade is "a loud, self-annotated extra run" vs "a silent no-op" (the silent kind cost a field debugging session); its justification no longer claims cases Tests +1 (257 in fourteen files). Verification: three trees 257 tests / 253 pass / 0 fail / 4 named skips (symlink-capability cases run on Linux CI); mirror CI both platforms: run 35849400871. Release-gate sequencing: head is now |
hetaoBackend
left a comment
There was a problem hiding this comment.
Request changes for exact current head abd0f7db9868eb271f1705187fbeee9ba1ecccaf.
The previous base divergence, whitespace, path-containment and most endpoint/credential findings are now substantially closed. Two blockers remain:
- SSRF DNS validation fails open and is not bound to the actual connection.
plugins/Wzdhehe/html2video-for-mcode/skills/html2video-for-mcode/scripts/url-policy.mjs:147-155catches lookup failure and returns success, after whichfetch-official-images.mjs:84-85,155-173continues/fetches/navigates the original hostname. Even when pre-lookup returns a public IP, the actual Node/Chromium connection resolves the hostname again, allowing DNS rebinding or resolver disagreement to reach loopback/private/metadata destinations. Fail closed on DNS errors and bind policy validation to the actual connected address; add lookup-error and rebinding tests. - Exact-head GitHub execution evidence is still absent. The scoped Linux/Windows workflow is present, but this head only has
[code]smith = SKIPPED; there are no completed smoke-linux, smoke-windows, main CI, or CodeQL jobs. Local macOS results include Playwright-dependent skips and cannot replace exact-head Linux/Windows/CodeQL evidence. Run the workflows on this exact head before approval.
The branch now has current main as its merge-base, both manifests are consistent at 1.9.10, git diff --check is clean, and local non-browser tests are positive. [code]smith is skipped and is not test evidence.
…time veto (1.9.11)
…ing touch-ups (1.9.11)
|
1.9.11 — blocker 1 (SSRF fail-open / unbound connection) fixed; branch is current with Head is now Fail closed — Bound to the actual connection — the veto now rides the connection's own resolution instead of a pre-check:
Tests: +16 → 273 in fifteen files. The lookup-error and rebinding cases assert on request counters of local servers staying zero — on the real Blocker 2 (exact-head execution evidence) — mechanically unchanged: on this new head the scoped smoke, CI and CodeQL runs again sit in
|
…it yearGate flag, evals wording (1.9.12)
|
1.9.12 — field report: TTS reads numbers by its own rules; display form and spoken form are now separated ( Head is now
|
…to tools), digit-bounded year regex, small cleanups (1.9.12)
|
1.9.12 third commit ( Tests +1 → 275 in fifteen files; both behavioural corrections red-proofed (spoken-form reverted → alignment test red; digit-boundary reverted → the 12026 case red). Three trees identical at 275/271/0/4; the standalone repo's run at this commit is green on both platforms (36400677108) — suite health; the queued smoke/CI/CodeQL runs on this head still await an admin Approve and run. |
…ary toast, xfade show-leak fix (1.9.13)
|
1.9.13 (
|
…ate from real geometry, disclaimer cap removed, --ids warnings (1.9.14)
|
1.9.14 (
|
…SKILL_FRONTMATTER_INVALID) (1.9.15) Marketplace submission PLUGIN-202610030176 failed validation with SKILL_FRONTMATTER_INVALID on skills/html2video-for-mcode/SKILL.md. Root cause (reproduced with PyYAML before fixing): the unquoted frontmatter description carried one ASCII ": " — "(regulated subjects): finance/..." — and colon-space inside an unquoted plain scalar is a YAML parse error (ScannerError: mapping values are not allowed here). Strict parsers reject the file; the community repo validator reads the line with a regex, which is why 28 hosted validation rounds never surfaced it. Fix: the colon becomes an em-dash and the four ** bold markers are stripped (the marketplace renders this text as plain text). Description 977 -> 970 chars, every trigger phrase kept; frontmatter now parses clean. Package content changed, so the version increments 1.9.14 -> 1.9.15 in all three manifests per the submission guide. No code or behavior changed; the suite stays 285 tests / 0 fail (re-run on this tree before pushing).
Head 5144313 — 1.9.15: marketplace-submission fix (SKILL frontmatter), no behavior changeExplaining an out-of-band head bump outside the review loop: the plugin was also submitted to the mcode Plugin Marketplace (the standalone mirror repo carries the same Root cause, reproduced before fixing: the frontmatter Fix (description text only, 977 → 970 chars): the colon becomes an em-dash; the four Scope: no code changed. Version increments 1.9.14 → 1.9.15 in As before, the queued smoke/CI/CodeQL runs on this new head sit in |
hetaoBackend
left a comment
There was a problem hiding this comment.
Request changes for exact current head 51443138757cd240af3ea325a3097d20147472c6.
The new fail-closed DNS lookup and Chromium proxy routing improve the previous SSRF design, but this head still has reproducible blockers:
- The veto proxy still allows HTTP requests to literal private addresses. The ordinary HTTP absolute-URL branch enters
policyGet()without first applying the URL/host policy. For an IP literal, Node does not invoke the custom DNSlookup, so the private-address veto is skipped. A controlled local origin received the request and returned 200 through the productionstartVetoProxypath. Apply the sameassertFetchableUrl/blocked-host policy to every HTTP proxy request before connecting, and add an end-to-end canary proving loopback/private/link-local/metadata literals receive zero requests. - The exact-head test suite is not green on macOS. The plugin suite reproducibly reports 1 failure (267 pass, 17 capability skips), and repository
npm testreports the same failure.preview-pagerejects a legitimate thumbnail/audio path becausesafeRelcompares an uncanonicalized non-existent root with arealpath-canonicalized ancestor (/varversus/private/var). Canonicalize the root and nearest existing ancestor consistently, then retain a regression test for this platform alias. - There is still no exact-head GitHub execution evidence. This head exposes only
[code]smith = SKIPPED; the scoped Linux/Windows smoke, main CI, and CodeQL jobs did not run. The local run also skipped 17 capability-dependent cases, so it cannot substitute for the claimed Chromium/ffmpeg/cross-platform contract.
Please close the HTTP literal-address bypass, restore a fully passing local suite, and run the exact-head Linux/Windows/CodeQL checks before requesting approval. [code]smith was not used as evidence.
…as roots (review round 7) (1.9.16) Maintainer review round 7 (CHANGES_REQUESTED on head 5144313). Two of the three blockers are closed here, each reproduced on our tree first. 1. Veto proxy allowed IP-literal targets. The absolute-form HTTP branch fed straight into policyGet, which carried the veto only on the request's lookup option — and for an IP literal Node never calls lookup (it connects to the address directly), so the veto was a no-op. Reproduced against a controlled local origin through the production startVetoProxy: http://127.0.0.1:<port>/secret returned 200 and the origin received the request. Fix in two layers: policyGet applies the URL/host policy before connecting (the single literal entry point for every caller), and the proxy's HTTP branch pre-checks with an attributable error, mirroring the CONNECT branch. checkedLookup stays on the request for the DNS/rebinding case. The canary asserts zero requests AND that the 502 body names the policy reason (blocked-host) — the other blocked literals previously produced 502 because those addresses are unreachable, not refused, so a status-only assertion would have been green for the wrong reason. A positive control keeps allowed hostnames working. 2. macOS path alias made legitimate paths look like escapes. safeRel/assertContained canonicalized the probe side with realpath but compared it against the uncanonicalized path.resolve(root) whenever the root did not exist yet. On macOS /var/... and /private/var/... are two spellings of one directory, so valid project paths were rejected (preview-page refusing thumbnail/audio paths). Both sides now use the same canonicalPath rule, and assertContained gained the same "root not created yet, so an ancestor probe is legitimate" tolerance safeRel already had — still withheld when the root exists, which is the shape that lets a project-internal link point at the project's parent. Fail-closed is preserved: an existing root whose realpath throws is still rejected. macOS is not reachable from here, so the regression reproduces the mechanism with a junction alias and runs everywhere; a negative case pins that a genuine escape through a link inside the project is still refused. Tests +6 -> 291 in fifteen files. Red-proofs: removing both vetoes turns the loopback canary red; removing only policyGet's assertion keeps it green (the layering control); removing the canonicalPath root side turns the alias regression red. Every restore is byte-compared. Suite re-run before pushing on all three trees: 291 tests, 0 failures. 3. Exact-head execution evidence is unchanged and still requires a base-repo admin to approve the queued runs (fork-author 403 evidence is upthread); the mirror repo's dual-platform CI run is the substitute evidence.
Round 7 addressed — head
|
…browser-loopback invariant (1.9.17) Self-review round on top of the round-7 head: a two-axis /code-review pass over the delta, every reported finding re-derived on our own tree first. One residual hardening landed; two reported findings did not survive reproduction. Landed: 1. Both refusal layers are now separately load-bearing. policyGet applies the host policy before connecting (where:'policyGet'); the proxy's absolute-form HTTP branch pre-checks with where:'代理 HTTP'. Removing either one alone had left the suite green, so neither was pinned; the new tests assert each layer's own label, and red-proofs confirm each removal turns exactly its own assertion red. The redundancy is deliberate and now documented in code: policyGet is the single entry point for every caller (including the download paths), the proxy pre-check keeps refusals attributable without depending on policyGet internals. 2. CONNECT refusals are attributable. That branch used to destroy the client socket silently while the HTTP branch reported blocked-host, so the comment claiming the two were "the same shape" was wrong. CONNECT now answers 502 Bad Gateway with the reason in the body - never a tunnel, never an upstream connect. Two existing tests asserted the old silent behaviour and were updated by contract; reverting to a bare destroy() turns both red. 3. The browser-side invariant is now pinned rather than assumed. Chromium has an implicit proxy bypass for loopback, so "all browser egress goes through the veto proxy" rested on Playwright defaults. A capture-shaped launch plus a slide whose image points at a counting origin asserts 0 requests at the origin; if a future Playwright/Chromium changes the default, CI says so. 4. Housekeeping: the root-side canonicalisation was byte-identical in safeRel and assertContained; both now call one exported canonicalRoot(root, where), which is also where the rule and the fail-closed behaviour live. Reproduced and refuted (recorded in the CHANGELOG so it is not re-litigated): - "Chromium bypasses the proxy for loopback, so capture.mjs has an egress hole": with an explicit proxy.server the loopback subresource goes through the veto proxy and comes back 502 with 0 origin hits, with or without bypass:'<-loopback>'. The test above pins the observable outcome anyway. - "the new ancestor tolerance has no test; relaxing it leaves the suite green": relaxing that single gate makes safe-paths fail - the escape-shape test depends on it. - Also checked: exotic IPv4 spellings (127.1, 0x7f.0.0.1, 2130706433, 0177.0.0.1, ::ffff:127.0.0.1) are normalised by the URL parser to 127.0.0.1 and refused, while a public address still passes. Tests +4 -> 295 in fifteen files, 0 failures on all three trees before pushing. No review feedback from the maintainer is addressed by this head; blocker 3 (exact-head execution evidence) still needs a base-repo admin to approve the queued runs.
Self-review on the round-7 head — head
|
…open quoting, three gate bypasses (1.9.18) Independent security audit of the "parse and generate" dimension — the one the hosted review rounds never covered (the auditor was barred from reading the earlier review records). It reported 2 HIGH / 4 MEDIUM / 6 LOW. We reproduced every finding on our own tree before acting; three of its claims did not survive that, and everything it confirmed is fixed in this head. HIGH - H1 attribute injection in the play page: setAttr re-serialised a value read out of a single-quoted attribute into double quotes without escaping, so an author's <html class='x" onmouseover=window.FIRED=1 data-x='> came back from addNoFx with a live event handler (X, the animation toggle, is the trigger). Values are now HTML-escaped on output. The regression test parses attribute names instead of grepping for "on*=" text — after the fix that text legitimately lives inside the attribute value — and carries a detector self-check so the pair cannot be green for free. - H2 SRT cue injection: clause text was spliced into out/subs.srt verbatim, so a clause with embedded newlines plus a timecode line produced more timecodes than cues — forged cues in a file meant for platform upload. SRT generation moved to a single source (tools.buildSrt) that strips bidi and control characters, folds all whitespace runs to one space, and turns a residual inline "-->" into an arrow (lenient parsers such as ffmpeg's srt demuxer scan lines for timecodes). The asserted invariant is line-anchored: timecode lines === cue count. MEDIUM - M1 --open could run a second command: cmd /c start relied on Node quoting the target, and Node only quotes when the argument contains spaces or quotes — a Windows path may contain & without a space. Reproduced with a controlled origin (the second command really executed). The target is now quoted explicitly with windowsVerbatimArguments. The test runs both shapes and requires the unquoted one to still be exploitable. - M2 fx-* classes were trusted by prefix: class="fx-notreal" satisfied the data-stage rule while nothing animates, and locally defined fx classes with opacity-less keyframes were never inspected. Both now consider the slide's own <style>; an undeclared class is named. - M3 HTML comments could stand in for a definition: <!-- --c-fake: 1 --> satisfied the undefined-variable gate. Comments are stripped before both the definition and usage scan. - M4 external-resource gate holes: only double-quoted src|href="https://..." was seen, so <IMG SRC=https://...>, protocol-relative //host/x.png, @import url(...) and background:url(...) all passed. The scan is now case-insensitive, covers unquoted values and srcset, and covers CSS regions — while deliberately not flagging links written as slide prose. LOW - L1 the image gate claimed to prevent second-order escapes but compared lexically; it now also compares canonical paths when the file exists. L2 Windows cmd expands %VAR% even inside quotes and cannot be escaped on a command line — the ASR path now refuses such paths with an actionable message instead of silently mangling them (the previous comment understated it). L3 data-style/data-class no longer shadow the real attributes, a > inside a quoted value no longer truncates the tag, and a decoy <html> inside a comment is no longer patched. L4 control and bidi characters are stripped from transcript text before comparison, printing and checklist.md. L5 isBlockedHost now catches the expanded IPv4-mapped form (0:0:0:0:0:ffff:7f00:1). L6 ffmpeg's concat list cannot represent a quote inside a quoted path, so such paths fail loudly via tools.assertConcatPathSafe instead of writing a silently broken list. Refuted after reproduction (recorded so they are not re-litigated): rmSync(recursive) does not follow junctions into the project parent (the containment guards are load-bearing); the missing exact-head runs still cannot be self-served (base-repo admin approval returns 403); and our escape guards do run (the junction matrices came back blocked). Tests +19 -> 314 in sixteen files, 0 failures on all three trees before pushing. Seven red-proofs: reverting each guard turns exactly its own case red, restores byte-compared. One red-proof did not redden at first, which exposed dead code rather than a weak test — the sanitizer had a dedicated CRLF fold that the trailing whitespace collapse already subsumed; it was removed and the proof now pins the load-bearing step.
Head
|
…ntics (1.9.19) One regression, found by reviewing our own previous fix — and the test that structurally could not have caught it. --open stopped opening the browser on Windows. The 1.9.18 fix for M1 added windowsVerbatimArguments so the target would be quoted explicitly instead of relying on Node's quote-only-when-it-contains-spaces rule. But verbatim mode also stops Node from quoting the empty-string argument, so start's empty *title* vanished: start "" "<path>" became start "<path>". START treats the first quoted token as the window title, so with no command left to run nothing opens — while the script still printed that it had opened the page. A security fix that broke the feature it was securing. The rule behind it was measured, not assumed. Using mkdir as a marker: cmd /c start "T" cmd /c <cmd> runs <cmd> (T became the title), while cmd /c start T cmd /c <cmd> does not (T became a command that does not exist). One quoted token and no command after it therefore opens nothing. The empty title is now a literal '""', and the argv construction moved into an exported openCmdArgv(target, platform) so it can be asserted directly rather than inferred from a spawn. Why our own M1 test missed it: that test substituted echo for start, and echo has no title semantics, so "the quoted path is consumed as a title" was outside anything it could observe — the proxy proved quoting, not START's parser. The replacement asserts on openCmdArgv's argv, red-proves that the pre-fix shape fails the same assertion, and then runs start itself: the quoted form must create the marker, the unquoted form must not. Housekeeping: the new behavioural test runs start /b. Without it every suite run flashes a console window on the machine running it (measured: 3639 ms and one new conhost without /b; 134 ms and none with it). Tests +3 -> 317 in sixteen files. Suite re-run before pushing on both shipped trees: 317 tests, 313 pass, 0 fail, 4 named capability skips — all four the file-symlink canaries, skipped with the reason that this Windows machine has Developer Mode off, and they run on ubuntu. The directory-junction equivalents of those guards (safeRel escape, L1 image gate) execute and pass here via the mklink /J fallback, and the render-smoke suite ran for real on this machine rather than skipping.
Heads
|
What changes
Add Plugin:
html2video-for-mcodeatplugins/Wzdhehe/html2video-for-mcode.A Skill that turns a topic, outline, or script into a narrated MP4: HTML slides with staged
entrance animations, a TTS voiceover, burned-in subtitles, and an ASR pass that verifies the
voiceover says what the script says.
User value
After installing, a MiniMax Code user can ask in plain language:
and get:
What makes it more than a slide exporter:
derived from the TTS audio via ffprobe, so the picture can never lag behind the voiceover.
is just a headline.
screen-recording.
images, external resources, or entrance animations without an animation class: the silent
failure modes that otherwise ship a broken-looking video while every script reports success.
Plugin submission checklist
plugins/<github-owner>/<plugin-name>.plugin.jsonname matches the Plugin directory.README.mdincludes a real example prompt and expected result (bilingual:README.md+README.zh-CN.md).LICENSEandplugin.jsondeclare an open-source license (MIT).(Node 18+, ffmpeg/ffprobe, Playwright Chromium; MiniMax API key or Token Plan for voice and
ASR; Windows/macOS/Linux; PowerShell caveat documented).
(
api.minimaxi.com/api.minimax.iofor ASR only when invoked; voice via mcode connectorsor
mmx-cli; image fetching only from URLs the user passes; no telemetry).are included. The ASR script reads its key from an environment variable or CLI flag at
runtime and never writes it.
TODOhas been replaced.publish/validate-plugin.mjs(an authoring-side tool, deliberately not part of the shipped plugin tree): it stages the Plugin tree into the host checkout, verifies every file in the tree is fingerprint-identical (sha256) to the source tree, then runs the upstream validator →OK plugin Wzdhehe/html2video-for-mcode, exit 0. (Validating the host'snpm run checkalone is not sufficient: it scans the staged copy under_official-plugins/plugins/**, so a stale staged copy yields a green result that proves nothing.)Evidence
The validator also prints a
Validated <N> hosted Pluginssummary line; N counts every plugin in the checkout and grows as the host merges unrelated plugins (it grew repeatedly while this PR was open). That line is checkout state, not a property of this PR, so it is not quoted above.The suite reports 0 fail in every environment measured: the development tree, both published trees, and tool-less sandboxes in both link-capability shapes. Skips are capability-dependent and always named: 0 skips with tools on a symlink-capable system; on stock Windows without Developer Mode the file-symlink canaries skip by name; in tool-less environments every tool-needing test skips with its reason. When ffmpeg / ffprobe / Chromium are absent (the monorepo's own root-level
node --testruns in exactly that environment), every test that needs one of them skips with its stated reason — verified in tool-less sandboxes in both link capability shapes: 0 fail, every remaining case skipping with its stated reason (nothing pretends to pass). Skip counts are environment-dependent and drift as each release adds tests — the invariant is stated here, per-version measurements live in the CHANGELOG — and the scoped workflow installs the tools and runs the whole suite for real. The host repository's owntest/hosted-plugins.test.mjscontains a symlink fixture that fails on a Windows checkout without Developer Mode (EPERM: operation not permitted, symlink …); that is a pre-existing host-side issue, it reproduces on a clean checkout without this Plugin, it passes on the CI'subuntu-latest, and this PR does not touch it. The part ofnpm run checkthat inspects Plugins passes with exit code 0 when the staged copy is current.Manual end-to-end test (Windows, Node 24, ffmpeg-static):
plan-timings.mjs→ every slide duration andstage entrance time derived from measured audio.
check-slides.mjscorrectly rejects slides with undefined CSS variables (--coral-a/--coral-b),missing images, external font links, and
data-stagewithout an animation class; clean slides pass.capture.mjs --mode motionproduced 143 frames for an 8.6s slide; frame-diff (PSNR) confirms thestaged entrance actually renders at its scheduled time (
infbefore the entrance, ~14 dB across it).build-video.mjs --asrproduced a 14.20s MP4 matching the expected duration exactly, full decodeclean,
out/subs.srtgenerated, and per-sentence ASR parts produced.#FFB84Don white)is rejected with a non-zero exit code, a compliant one (
#C2410C) passes.traditional characters (Cantonese voice) and mismatched numbers fail with exit code 1.
Full disclosure of dependencies, network access, and data handling is in the Plugin README.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.