@remixmate/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/argv.d.ts +15 -0
- package/dist/argv.js +51 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +83 -0
- package/dist/handlers/gen-image.d.ts +13 -0
- package/dist/handlers/gen-image.js +161 -0
- package/dist/handlers/gen-voice.d.ts +16 -0
- package/dist/handlers/gen-voice.js +110 -0
- package/dist/handlers/index.d.ts +13 -0
- package/dist/handlers/index.js +14 -0
- package/dist/handlers/template-bind.d.ts +15 -0
- package/dist/handlers/template-bind.js +149 -0
- package/dist/http.d.ts +52 -0
- package/dist/http.js +110 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/manifest.json +260 -0
- package/dist/progress.d.ts +23 -0
- package/dist/progress.js +15 -0
- package/dist/registry.d.ts +33 -0
- package/dist/registry.js +72 -0
- package/dist/runner.d.ts +17 -0
- package/dist/runner.js +63 -0
- package/package.json +54 -0
- package/skills/export-jianying/SKILL.md +251 -0
- package/skills/export-jianying/scripts/gen_jianying_draft.py +1099 -0
- package/skills/export-jianying/skill.json +24 -0
- package/skills/export-jianying/version.json +7 -0
- package/skills/gen-digital-human/SKILL.md +211 -0
- package/skills/gen-digital-human/scripts/gen_digital_human_video.py +574 -0
- package/skills/gen-digital-human/skill.json +23 -0
- package/skills/gen-digital-human/version.json +7 -0
- package/skills/gen-image/SKILL.md +130 -0
- package/skills/gen-image/skill.json +22 -0
- package/skills/gen-image/version.json +7 -0
- package/skills/gen-script/SKILL.md +283 -0
- package/skills/gen-script/scripts/gen_script.py +1208 -0
- package/skills/gen-script/skill.json +41 -0
- package/skills/gen-script/version.json +7 -0
- package/skills/gen-video/SKILL.md +170 -0
- package/skills/gen-video/scripts/gen_video.py +591 -0
- package/skills/gen-video/skill.json +24 -0
- package/skills/gen-video/version.json +7 -0
- package/skills/gen-voice/SKILL.md +104 -0
- package/skills/gen-voice/skill.json +21 -0
- package/skills/gen-voice/version.json +7 -0
- package/skills/prepare-video-assets/SKILL.md +192 -0
- package/skills/prepare-video-assets/scripts/prepare_video_assets.py +57 -0
- package/skills/prepare-video-assets/skill.json +23 -0
- package/skills/prepare-video-assets/version.json +7 -0
- package/skills/render-video/SKILL.md +187 -0
- package/skills/render-video/scripts/_chrome_vendor.py +305 -0
- package/skills/render-video/scripts/_video_probe.py +174 -0
- package/skills/render-video/scripts/_vod_polling.py +86 -0
- package/skills/render-video/scripts/remote_renderer_client.py +236 -0
- package/skills/render-video/scripts/render_video.py +2288 -0
- package/skills/render-video/scripts/upload_video.py +193 -0
- package/skills/render-video/skill.json +21 -0
- package/skills/render-video/version.json +7 -0
- package/skills/template-bind/README.md +63 -0
- package/skills/template-bind/SKILL.md +234 -0
- package/skills/template-bind/scripts/check_contracts.py +196 -0
- package/skills/template-bind/scripts/match_template.py +215 -0
- package/skills/template-bind/scripts/registry_loader.py +379 -0
- package/skills/template-bind/scripts/render_job_client.py +162 -0
- package/skills/template-bind/scripts/sync_registry.py +44 -0
- package/skills/template-bind/scripts/template_paths.py +72 -0
- package/skills/template-bind/skill.json +20 -0
- package/skills/template-bind/version.json +7 -0
- package/skills/template-bind/video_dsl/README.md +97 -0
- package/skills/template-bind/video_dsl/runtime/__init__.py +11 -0
- package/skills/template-bind/video_dsl/runtime/dsl_validator.py +495 -0
- package/skills/template-bind/video_dsl/runtime/prompt_enhancer.py +274 -0
- package/skills/template-bind/video_dsl/runtime/template_binder.py +87 -0
- package/skills/template-bind/video_dsl/runtime/timeline_compiler.py +312 -0
- package/skills/template-bind/video_dsl/schema/render-plan-v1alpha1.json +193 -0
- package/skills/template-bind/video_dsl/schema/template-binding-v1alpha1.json +122 -0
- package/skills/template-bind/video_dsl/schema/template-definition-v1alpha1.json +247 -0
- package/skills/template-bind/video_dsl/schema/video-dsl-v1alpha1.json +378 -0
- package/skills/video-parser/SKILL.md +238 -0
- package/skills/video-parser/scripts/analyze_video.py +1038 -0
- package/skills/video-parser/scripts/deconstruct_video.py +602 -0
- package/skills/video-parser/scripts/parse_via_render.py +148 -0
- package/skills/video-parser/skill.json +21 -0
- package/skills/video-parser/version.json +7 -0
- package/skills/web-capture/SKILL.md +183 -0
- package/skills/web-capture/scripts/web_capture.py +461 -0
- package/skills/web-capture/skill.json +29 -0
- package/skills/web-capture/version.json +7 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill registry — scan skills/ and load skill.json definitions.
|
|
3
|
+
*
|
|
4
|
+
* skill.json schema:
|
|
5
|
+
* { name, toolName, description, parameters, entry?, scriptPath? }
|
|
6
|
+
*
|
|
7
|
+
* `entry` is the dispatcher hint. Missing entry falls back to the legacy
|
|
8
|
+
* top-level scriptPath, treated as { type:'python', scriptPath } so older
|
|
9
|
+
* skills keep working without a touch.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SKILLS_DIR: string;
|
|
12
|
+
export type SkillEntry = {
|
|
13
|
+
type: 'python';
|
|
14
|
+
scriptPath: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'http';
|
|
17
|
+
handler: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'builtin';
|
|
20
|
+
handler: string;
|
|
21
|
+
};
|
|
22
|
+
export interface SkillDef {
|
|
23
|
+
name: string;
|
|
24
|
+
toolName: string;
|
|
25
|
+
description: string;
|
|
26
|
+
parameters: Record<string, unknown>;
|
|
27
|
+
entry: SkillEntry;
|
|
28
|
+
skillDir: string;
|
|
29
|
+
/** Absolute path to the python entry script (only when entry.type === 'python'). */
|
|
30
|
+
scriptAbsolutePath?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function loadSkills(baseDir?: string): SkillDef[];
|
|
33
|
+
export declare function findSkill(name: string, baseDir?: string): SkillDef | null;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill registry — scan skills/ and load skill.json definitions.
|
|
3
|
+
*
|
|
4
|
+
* skill.json schema:
|
|
5
|
+
* { name, toolName, description, parameters, entry?, scriptPath? }
|
|
6
|
+
*
|
|
7
|
+
* `entry` is the dispatcher hint. Missing entry falls back to the legacy
|
|
8
|
+
* top-level scriptPath, treated as { type:'python', scriptPath } so older
|
|
9
|
+
* skills keep working without a touch.
|
|
10
|
+
*/
|
|
11
|
+
import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
export const SKILLS_DIR = path.resolve(__dirname, '..', 'skills');
|
|
16
|
+
function normalizeEntry(raw) {
|
|
17
|
+
if (raw.entry)
|
|
18
|
+
return raw.entry;
|
|
19
|
+
if (raw.scriptPath)
|
|
20
|
+
return { type: 'python', scriptPath: raw.scriptPath };
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function loadOne(skillDir) {
|
|
24
|
+
const skillJsonPath = path.join(skillDir, 'skill.json');
|
|
25
|
+
if (!existsSync(skillJsonPath))
|
|
26
|
+
return null;
|
|
27
|
+
let raw;
|
|
28
|
+
try {
|
|
29
|
+
raw = JSON.parse(readFileSync(skillJsonPath, 'utf-8'));
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error(`[remixmate] failed to parse ${skillJsonPath}:`, err);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const entry = normalizeEntry(raw);
|
|
36
|
+
if (!entry) {
|
|
37
|
+
console.warn(`[remixmate] skipping ${raw.name}: missing entry/scriptPath`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const def = {
|
|
41
|
+
name: raw.name,
|
|
42
|
+
toolName: raw.toolName,
|
|
43
|
+
description: raw.description,
|
|
44
|
+
parameters: raw.parameters,
|
|
45
|
+
entry,
|
|
46
|
+
skillDir,
|
|
47
|
+
};
|
|
48
|
+
if (entry.type === 'python') {
|
|
49
|
+
def.scriptAbsolutePath = path.join(skillDir, entry.scriptPath);
|
|
50
|
+
}
|
|
51
|
+
return def;
|
|
52
|
+
}
|
|
53
|
+
export function loadSkills(baseDir = SKILLS_DIR) {
|
|
54
|
+
if (!existsSync(baseDir)) {
|
|
55
|
+
console.warn(`[remixmate] skills directory not found: ${baseDir}`);
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const skills = [];
|
|
59
|
+
for (const entry of readdirSync(baseDir)) {
|
|
60
|
+
const skillDir = path.join(baseDir, entry);
|
|
61
|
+
if (!statSync(skillDir).isDirectory())
|
|
62
|
+
continue;
|
|
63
|
+
const def = loadOne(skillDir);
|
|
64
|
+
if (def)
|
|
65
|
+
skills.push(def);
|
|
66
|
+
}
|
|
67
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
68
|
+
return skills;
|
|
69
|
+
}
|
|
70
|
+
export function findSkill(name, baseDir) {
|
|
71
|
+
return loadSkills(baseDir).find((s) => s.name === name) ?? null;
|
|
72
|
+
}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill dispatcher — routes a parsed invocation to either:
|
|
3
|
+
* - spawn `python3 <scriptAbsolutePath> --flag value ...` (entry.type === 'python')
|
|
4
|
+
* - call a TS handler from handlers/ (entry.type === 'http' | 'builtin')
|
|
5
|
+
*
|
|
6
|
+
* Resolves to the child process's exit code so cli.ts can propagate it.
|
|
7
|
+
*/
|
|
8
|
+
import type { ParsedArgs } from './argv.js';
|
|
9
|
+
export interface RunOptions {
|
|
10
|
+
/** Override the skills directory (defaults to ab-skill/skills). */
|
|
11
|
+
baseDir?: string;
|
|
12
|
+
/** Raw argv slice after the skill name (passed verbatim for python; parsed for handlers). */
|
|
13
|
+
rawArgs: string[];
|
|
14
|
+
/** Parsed argv (for handlers). */
|
|
15
|
+
parsedArgs: ParsedArgs;
|
|
16
|
+
}
|
|
17
|
+
export declare function runSkill(skillName: string, opts: RunOptions): Promise<number>;
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill dispatcher — routes a parsed invocation to either:
|
|
3
|
+
* - spawn `python3 <scriptAbsolutePath> --flag value ...` (entry.type === 'python')
|
|
4
|
+
* - call a TS handler from handlers/ (entry.type === 'http' | 'builtin')
|
|
5
|
+
*
|
|
6
|
+
* Resolves to the child process's exit code so cli.ts can propagate it.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { findSkill, SKILLS_DIR } from './registry.js';
|
|
11
|
+
import { HANDLERS } from './handlers/index.js';
|
|
12
|
+
import { SkillError } from './http.js';
|
|
13
|
+
export async function runSkill(skillName, opts) {
|
|
14
|
+
const skill = findSkill(skillName, opts.baseDir ?? SKILLS_DIR);
|
|
15
|
+
if (!skill) {
|
|
16
|
+
process.stderr.write(`❌ skill not found: ${skillName}\n`);
|
|
17
|
+
return 2;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
switch (skill.entry.type) {
|
|
21
|
+
case 'python':
|
|
22
|
+
return await runPython(skill, opts.rawArgs);
|
|
23
|
+
case 'http':
|
|
24
|
+
case 'builtin':
|
|
25
|
+
return await runHandler(skill, opts.parsedArgs);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err instanceof SkillError) {
|
|
30
|
+
process.stderr.write(err.message + '\n');
|
|
31
|
+
return err.exitCode;
|
|
32
|
+
}
|
|
33
|
+
process.stderr.write(`❌ unexpected error: ${err.message}\n`);
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function runPython(skill, rawArgs) {
|
|
38
|
+
if (!skill.scriptAbsolutePath) {
|
|
39
|
+
throw new SkillError(`skill ${skill.name} has entry.type=python but no scriptAbsolutePath`);
|
|
40
|
+
}
|
|
41
|
+
const cwd = path.dirname(skill.scriptAbsolutePath);
|
|
42
|
+
return await new Promise((resolve) => {
|
|
43
|
+
const proc = spawn('python3', [skill.scriptAbsolutePath, ...rawArgs], {
|
|
44
|
+
cwd,
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
env: { ...process.env, PYTHONUNBUFFERED: '1' },
|
|
47
|
+
});
|
|
48
|
+
proc.on('exit', (code) => resolve(code ?? 1));
|
|
49
|
+
proc.on('error', (err) => {
|
|
50
|
+
process.stderr.write(`❌ failed to spawn python3: ${err.message}\n`);
|
|
51
|
+
resolve(127);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function runHandler(skill, args) {
|
|
56
|
+
const handlerKey = skill.entry.handler;
|
|
57
|
+
const handler = HANDLERS[handlerKey];
|
|
58
|
+
if (!handler) {
|
|
59
|
+
throw new SkillError(`handler not registered: ${handlerKey} (skill ${skill.name})`);
|
|
60
|
+
}
|
|
61
|
+
await handler(args, { skillName: skill.name });
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@remixmate/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI media generation skills for Claude Code / Codex — 9 skills covering image, video, voice, digital human, script, template binding, rendering, Jianying export, and video deconstruction.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"remixmate": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"skills",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && node scripts/build-manifest.mjs",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"list": "node dist/cli.js --list",
|
|
21
|
+
"smoke": "node scripts/smoke.mjs",
|
|
22
|
+
"test:validators": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-validators.py",
|
|
23
|
+
"test:template-pipeline": "PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-template-pipeline.py",
|
|
24
|
+
"test:contracts": "PYTHONDONTWRITEBYTECODE=1 python3 skills/template-bind/scripts/check_contracts.py",
|
|
25
|
+
"clean": "node scripts/clean.mjs"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"claude-code",
|
|
29
|
+
"codex",
|
|
30
|
+
"agent-skills",
|
|
31
|
+
"ai-video",
|
|
32
|
+
"tts",
|
|
33
|
+
"text-to-speech",
|
|
34
|
+
"image-generation",
|
|
35
|
+
"remotion",
|
|
36
|
+
"jianying"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/remixmate/cli.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/remixmate/cli#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/remixmate/cli/issues"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
|
+
"typescript": "^5.7.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: export-jianying
|
|
3
|
+
description: |
|
|
4
|
+
Jianying (CapCut) draft-generation skill. Packages asset URLs into a draft ZIP that Jianying can import; supports automatic conversion from a RenderPlan (calls ab-api /file/generateJianYing).
|
|
5
|
+
|
|
6
|
+
Use this skill as soon as the user mentions any of these intents:
|
|
7
|
+
- Export to Jianying, Jianying draft, package for Jianying, import into Jianying
|
|
8
|
+
- Export materials to the Jianying format, generate a Jianying project
|
|
9
|
+
- Bundle video / image / audio into a Jianying draft
|
|
10
|
+
- Export a Jianying draft from a RenderPlan
|
|
11
|
+
|
|
12
|
+
Even when the user does not say "Jianying" explicitly, use this skill whenever they want to package materials into a draft that can be edited in Jianying.
|
|
13
|
+
triggers:
|
|
14
|
+
- Export to Jianying, Jianying draft, package for Jianying, import into Jianying
|
|
15
|
+
- Export materials to the Jianying format, generate a Jianying project
|
|
16
|
+
- Bundle video / image / audio into a Jianying draft
|
|
17
|
+
- Export a Jianying draft from a RenderPlan
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Jianying Draft Generation Skill
|
|
21
|
+
|
|
22
|
+
Calls **ab-api** `POST /file/generateJianYing` from a script to package asset URLs (video / image / audio) + duration descriptions into a Jianying-importable draft ZIP. Uses Tianyan **privateToken** auth.
|
|
23
|
+
|
|
24
|
+
## What it does
|
|
25
|
+
|
|
26
|
+
- Input: a scene list (each scene carries an asset URL, duration, and optional audio + subtitles).
|
|
27
|
+
- Output: a Jianying draft ZIP (containing `draft_content.json` + `draft_meta_info.json` + a `Resources/` material folder).
|
|
28
|
+
- How to import: extract the ZIP into the Jianying drafts directory and (re)start Jianying.
|
|
29
|
+
|
|
30
|
+
## Authentication & environment
|
|
31
|
+
|
|
32
|
+
There is no dedicated env file; the **process environment** variables of the caller apply. Examples use `python`; on macOS without that alias use `python3`.
|
|
33
|
+
|
|
34
|
+
- **Enterprise OpenClaw**: auth is injected, **no** `PRIV_TOKEN` / `--priv-token` needed.
|
|
35
|
+
- **Other environments**: configure a token — obtain a `PRIV_TOKEN` from your ab-api service.
|
|
36
|
+
|
|
37
|
+
| Env var | Description | Default |
|
|
38
|
+
|---------|-------------|---------|
|
|
39
|
+
| `PRIV_TOKEN` | Tianyan token; overridable with `--priv-token`. | (none) |
|
|
40
|
+
| `MM_API_BASE_URL` | API root URL. | `http://localhost:3001/api` |
|
|
41
|
+
| `AGENT_NAME` | Optional, sent as the `x-invoke-agent` request header. | (none) |
|
|
42
|
+
|
|
43
|
+
## Steps
|
|
44
|
+
|
|
45
|
+
1. **Script path**: read the system-injected `Base directory for this skill: <path>` as `<SkillDir>`. Every command below uses `<SkillDir>/scripts/gen_jianying_draft.py`; never hard-code paths.
|
|
46
|
+
2. **Pick the input form**:
|
|
47
|
+
- **Form A (recommended)**: `--from-job-id` — load the RenderPlan from the database without passing a file path (safe for multi-user concurrent flows).
|
|
48
|
+
- **Form B (fallback, single-user / local debugging)**: `--from-render-plan` — read a local `render-plan.json`.
|
|
49
|
+
3. **Draft root**: by default the `--system mac` preset resolves the target Jianying draft directory; switch with `--system windows`, or override with `--draft-root-path`. This path is sent to the API as `draftRootPath` and embedded in the draft files.
|
|
50
|
+
4. **By default download the ZIP** locally (`--output` sets the path); pass `--no-download` if you only need the download URL (this flag is ignored when scenes contain `textLayers` / `subtitleText`).
|
|
51
|
+
5. **Async job**: draft generation is async (assets must be downloaded and packaged); the script polls until completion.
|
|
52
|
+
|
|
53
|
+
### Load the RenderPlan from the database (recommended, multi-user safe)
|
|
54
|
+
|
|
55
|
+
When `prepare_video_assets` was invoked with `--save-job` (default) and persisted the RenderPlan, load it directly via `--from-job-id` — no file path required:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
59
|
+
--from-job-id <jobId> \
|
|
60
|
+
--title "My Video"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `<jobId>` is the task id printed by `prepare_video_assets` (the line `📦 render job jobId: N`).
|
|
64
|
+
- Requires the `PRIV_TOKEN` and `MM_BACKEND_API_URL` env vars.
|
|
65
|
+
|
|
66
|
+
### Read from a RenderPlan file (fallback, single-user / local debugging)
|
|
67
|
+
|
|
68
|
+
When not using the database mode, read a local `render-plan.json`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
72
|
+
--from-render-plan output/render-plan.json \
|
|
73
|
+
--title "My Video"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The script automatically extracts the following from `render-plan.json`:
|
|
77
|
+
- Each scene's visual asset URL (image / video)
|
|
78
|
+
- TTS audio URL and duration
|
|
79
|
+
- Subtitle text
|
|
80
|
+
- Canvas size (width × height)
|
|
81
|
+
|
|
82
|
+
### Basic usage — single video scene
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
86
|
+
--title "My Video" \
|
|
87
|
+
--scenes '[{"videoUrl":"https://example.com/video.mp4","duration":5}]'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Multiple scenes + audio + subtitles
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
94
|
+
--title "Product Promo" \
|
|
95
|
+
--scenes '[
|
|
96
|
+
{"videoUrl":"https://example.com/scene1.mp4","duration":5,"audioUrl":"https://example.com/bgm.mp3","audioDuration":10,"subtitleText":"Opening line"},
|
|
97
|
+
{"imageUrl":"https://example.com/product.jpg","duration":3,"subtitleText":"Product spotlight"},
|
|
98
|
+
{"videoUrl":"https://example.com/scene2.mp4","duration":8}
|
|
99
|
+
]'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Multi-layer text styling (textLayers)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
106
|
+
--title "Ink & Bamboo" \
|
|
107
|
+
--scenes '[
|
|
108
|
+
{
|
|
109
|
+
"imageUrl":"https://example.com/painting1.jpg",
|
|
110
|
+
"duration":4,
|
|
111
|
+
"textLayers":[
|
|
112
|
+
{"content":"Bamboo & Panda","role":"headline","fontSize":15,"color":"#FFFFFF","bold":true,"position":{"x":0,"y":-0.35}},
|
|
113
|
+
{"content":"Among swaying bamboo, the national treasure rests","role":"subheadline","fontSize":8,"color":"#F0C040","position":{"x":0,"y":0.38}}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
]' \
|
|
117
|
+
--output "styled_draft.zip"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
> When `textLayers` is present the script always downloads the ZIP and post-processes it (`--no-download` is ignored).
|
|
121
|
+
|
|
122
|
+
### Read scenes from a file
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
126
|
+
--title "Batch run" \
|
|
127
|
+
--scenes scenes.json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Custom canvas size + custom output path
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
134
|
+
--title "Vertical video" \
|
|
135
|
+
--width 1080 --height 1920 \
|
|
136
|
+
--scenes '[{"videoUrl":"https://example.com/v.mp4","duration":10}]' \
|
|
137
|
+
--output "my_draft.zip"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Specify the Jianying draft root
|
|
141
|
+
|
|
142
|
+
Default is `--system mac`. Switch to the Windows preset:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
146
|
+
--title "Win draft" --system windows \
|
|
147
|
+
--scenes '[{"videoUrl":"https://example.com/v.mp4","duration":5}]'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Explicitly specify a path (overrides `--system`):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
154
|
+
--title "Custom path" \
|
|
155
|
+
--draft-root-path "/Users/yourname/Movies/JianyingPro/User Data/Projects/com.lveditor.draft" \
|
|
156
|
+
--scenes '[{"videoUrl":"https://example.com/v.mp4","duration":5}]'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Only print the download URL (no file write)
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python3 <SkillDir>/scripts/gen_jianying_draft.py \
|
|
163
|
+
--title "URL only" --no-download \
|
|
164
|
+
--scenes '[{"videoUrl":"https://example.com/v.mp4","duration":5}]'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
6. **Show the result**: by default the script prints the download URL and saves the ZIP to `--output`. At the end it prints "extract into the draft root → restart Jianying" to import. With `--no-download`, only the download URL is printed.
|
|
168
|
+
|
|
169
|
+
See every option: `python3 <SkillDir>/scripts/gen_jianying_draft.py --help`.
|
|
170
|
+
|
|
171
|
+
## Common CLI flags
|
|
172
|
+
|
|
173
|
+
| Flag | Description | Default |
|
|
174
|
+
|------|-------------|---------|
|
|
175
|
+
| `--from-render-plan` | Convert from a RenderPlan JSON automatically (replaces `--scenes`). | — |
|
|
176
|
+
| `--from-job-id` | Load the RenderPlan from the database (pass a jobId, replaces `--from-render-plan`). | — |
|
|
177
|
+
| `--scenes` | Scenes JSON (inline string or file path). | required (unless using `--from-render-plan` or `--from-job-id`) |
|
|
178
|
+
| `--title` | Draft title. | required (optional when using `--from-render-plan`) |
|
|
179
|
+
| `--width` | Canvas width in pixels. | `1080` |
|
|
180
|
+
| `--height` | Canvas height in pixels. | `1920` |
|
|
181
|
+
| `--draft-name` | Jianying draft name. | same as title |
|
|
182
|
+
| `--system` | Draft-root preset: `mac` / `windows`. | `mac` |
|
|
183
|
+
| `--draft-root-path` | Explicit Jianying draft root path (overrides `--system`). | resolved from `--system` |
|
|
184
|
+
| `--no-download` | Skip the ZIP download; print only the URL (ignored when textLayers / subtitleText is present). | off |
|
|
185
|
+
| `-o` / `--output` | Download path. | `jianying_draft.zip` |
|
|
186
|
+
| `--poll-interval` | Polling interval (seconds). | `5` |
|
|
187
|
+
| `--max-wait` | Maximum wait time (seconds). | `300` |
|
|
188
|
+
| `--private-token` | Override the token. | see table above |
|
|
189
|
+
|
|
190
|
+
### Draft-root presets
|
|
191
|
+
|
|
192
|
+
| `--system` | Resolved path |
|
|
193
|
+
|------------|---------------|
|
|
194
|
+
| `mac` | `~/Movies/JianyingPro/User Data/Projects/com.lveditor.draft` (`~` expands on the runner machine). |
|
|
195
|
+
| `windows` | `%LOCALAPPDATA%/JianyingPro/User Data/Projects/com.lveditor.draft` (literal, not expanded — the target Windows machine resolves it). |
|
|
196
|
+
|
|
197
|
+
## Scene JSON shape
|
|
198
|
+
|
|
199
|
+
Fields supported per scene:
|
|
200
|
+
|
|
201
|
+
| Field | Type | Description |
|
|
202
|
+
|-------|------|-------------|
|
|
203
|
+
| `videoUrl` | string | Video material URL (mutually exclusive with `imageUrl`). |
|
|
204
|
+
| `imageUrl` | string | Image material URL (mutually exclusive with `videoUrl`). |
|
|
205
|
+
| `duration` | float | Scene duration in seconds — required. |
|
|
206
|
+
| `width` | int | Material width (optional). |
|
|
207
|
+
| `height` | int | Material height (optional). |
|
|
208
|
+
| `audioUrl` | string | Audio URL (optional). |
|
|
209
|
+
| `audioDuration` | float | Audio duration in seconds (optional, defaults to `duration`). |
|
|
210
|
+
| `subtitleText` | string | Subtitle text (optional, overridden by `textLayers` when present). |
|
|
211
|
+
| `textLayers` | array | Multi-layer text styling (optional, see below). |
|
|
212
|
+
|
|
213
|
+
### textLayers array element
|
|
214
|
+
|
|
215
|
+
When `textLayers` is present the script downloads the ZIP and post-processes `draft_content.json` to replace / add text materials and track clips so font size, color, position, etc. are honored.
|
|
216
|
+
|
|
217
|
+
| Field | Type | Required | Default | Description |
|
|
218
|
+
|-------|------|----------|---------|-------------|
|
|
219
|
+
| `content` | string | yes | — | Text content. |
|
|
220
|
+
| `role` | string | no | `"subheadline"` | Role preset: `headline` / `subheadline` / `badge` / `cta` / `caption`. |
|
|
221
|
+
| `fontSize` | number | no | role-based | Jianying font size (headline=15, subheadline=8). |
|
|
222
|
+
| `color` | string | no | role-based | Hex color `"#RRGGBB"`. |
|
|
223
|
+
| `bold` | bool | no | `false` | Whether to bold. |
|
|
224
|
+
| `position` | object | no | role-based | `{x, y}` normalized coordinates; 0=center, negative y=upper area. |
|
|
225
|
+
| `backgroundColor` | string | no | `null` | Text background color; `null` = transparent. |
|
|
226
|
+
| `shadow` | bool | no | `false` | Whether to add a shadow. |
|
|
227
|
+
|
|
228
|
+
**Role presets**:
|
|
229
|
+
- `headline`: size 15, white, bold, y=-0.35 (upper), with shadow.
|
|
230
|
+
- `subheadline`: size 8, gold #F0C040, y=0.38 (lower).
|
|
231
|
+
- `badge`: size 6, white, bold, top-left corner, gold background.
|
|
232
|
+
- `cta`: size 10, gold, bold, blue background.
|
|
233
|
+
- `caption`: size 6, light gray, y=0.44.
|
|
234
|
+
|
|
235
|
+
## How to import into Jianying
|
|
236
|
+
|
|
237
|
+
1. By default the draft ZIP is saved to `--output` (default `jianying_draft.zip`).
|
|
238
|
+
2. Extract the ZIP into the "draft root" the script prints — i.e. the `draftRootPath` passed to the API:
|
|
239
|
+
- mac default: `~/Movies/JianyingPro/User Data/Projects/com.lveditor.draft/`
|
|
240
|
+
- windows default: `%LOCALAPPDATA%/JianyingPro/User Data/Projects/com.lveditor.draft/`
|
|
241
|
+
- custom: the path passed via `--draft-root-path`
|
|
242
|
+
3. Start (or restart) Jianying; the draft appears in the project list.
|
|
243
|
+
|
|
244
|
+
> The backend embeds `draftRootPath` into `draft_content.json` / `draft_meta_info.json`. As long as the extraction directory matches the path that was passed in, Jianying resolves the material paths correctly without extra rewrites.
|
|
245
|
+
|
|
246
|
+
## Error handling
|
|
247
|
+
|
|
248
|
+
- **401** / **token missing** (non-OpenClaw): check or set the token via the Feishu link above.
|
|
249
|
+
- **Business `code != 0`**: read the `msg` printed on stderr.
|
|
250
|
+
- **Generation timeout**: increase `--max-wait` or retry later.
|
|
251
|
+
- **Network**: check connectivity and `MM_API_BASE_URL`.
|