---
name: export-jianying
description: |
  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).

  Use this skill as soon as the user mentions any of these intents:
  - Export to Jianying, Jianying draft, package for Jianying, import into Jianying
  - Export materials to the Jianying format, generate a Jianying project
  - Bundle video / image / audio into a Jianying draft
  - Export a Jianying draft from a RenderPlan

  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.
triggers:
  - Export to Jianying, Jianying draft, package for Jianying, import into Jianying
  - Export materials to the Jianying format, generate a Jianying project
  - Bundle video / image / audio into a Jianying draft
  - Export a Jianying draft from a RenderPlan
---

# Jianying Draft Generation Skill

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.

## What it does

- Input: a scene list (each scene carries an asset URL, duration, and optional audio + subtitles).
- Output: a Jianying draft ZIP (containing `draft_content.json` + `draft_meta_info.json` + a `Resources/` material folder).
- How to import: extract the ZIP into the Jianying drafts directory and (re)start Jianying.

## Authentication & environment

There is no dedicated env file; the **process environment** variables of the caller apply. Examples use `python`; on macOS without that alias use `python3`.

- **Enterprise OpenClaw**: auth is injected, **no** `PRIV_TOKEN` / `--priv-token` needed.
- **Other environments**: configure a token — obtain a `PRIV_TOKEN` from your ab-api service.

| Env var | Description | Default |
|---------|-------------|---------|
| `PRIV_TOKEN` | Tianyan token; overridable with `--priv-token`. | (none) |
| `MM_API_BASE_URL` | API root URL. | `https://api-agent.remixmate.ai/api` |
| `AGENT_NAME` | Optional, sent as the `x-invoke-agent` request header. | (none) |

## Steps

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.
2. **Pick the input form**:
   - **Form A (recommended)**: `--from-job-id` — load the RenderPlan from the database without passing a file path (safe for multi-user concurrent flows).
   - **Form B (fallback, single-user / local debugging)**: `--from-render-plan` — read a local `render-plan.json`.
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.
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`).
5. **Async job**: draft generation is async (assets must be downloaded and packaged); the script polls until completion.

### Load the RenderPlan from the database (recommended, multi-user safe)

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:

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --from-job-id <jobId> \
  --title "My Video"
```

- `<jobId>` is the task id printed by `prepare_video_assets` (the line `📦 render job jobId: N`).
- Requires the `PRIV_TOKEN` and `MM_BACKEND_API_URL` env vars.

### Read from a RenderPlan file (fallback, single-user / local debugging)

When not using the database mode, read a local RenderPlan file:

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --from-render-plan <path>.render-plan.json \
  --title "My Video"
```

> ⚠️ Don't point `--from-render-plan` at a shared `output/render-plan.json`. That
> file is NOT produced by the default token-present pipeline (the plan goes to the
> DB under a jobId), so anything sitting there is likely a stale leftover from an
> unrelated run. Prefer the `--from-job-id` DB mode, or render with an explicit
> `--save-render-plan --render-plan-output <path>` first.

The script automatically extracts the following from the RenderPlan:
- Each scene's visual asset URL (image / video)
- TTS audio URL and duration
- Subtitle text
- Canvas size (width × height)

### Basic usage — single video scene

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "My Video" \
  --scenes '[{"videoUrl":"https://example.com/video.mp4","duration":5}]'
```

### Multiple scenes + audio + subtitles

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "Product Promo" \
  --scenes '[
    {"videoUrl":"https://example.com/scene1.mp4","duration":5,"audioUrl":"https://example.com/bgm.mp3","audioDuration":10,"subtitleText":"Opening line"},
    {"imageUrl":"https://example.com/product.jpg","duration":3,"subtitleText":"Product spotlight"},
    {"videoUrl":"https://example.com/scene2.mp4","duration":8}
  ]'
```

### Multi-layer text styling (textLayers)

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "Ink & Bamboo" \
  --scenes '[
    {
      "imageUrl":"https://example.com/painting1.jpg",
      "duration":4,
      "textLayers":[
        {"content":"Bamboo & Panda","role":"headline","fontSize":15,"color":"#FFFFFF","bold":true,"position":{"x":0,"y":-0.35}},
        {"content":"Among swaying bamboo, the national treasure rests","role":"subheadline","fontSize":8,"color":"#F0C040","position":{"x":0,"y":0.38}}
      ]
    }
  ]' \
  --output "styled_draft.zip"
```

> When `textLayers` is present the script always downloads the ZIP and post-processes it (`--no-download` is ignored).

### Read scenes from a file

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "Batch run" \
  --scenes scenes.json
```

### Custom canvas size + custom output path

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "Vertical video" \
  --width 1080 --height 1920 \
  --scenes '[{"videoUrl":"https://example.com/v.mp4","duration":10}]' \
  --output "my_draft.zip"
```

### Specify the Jianying draft root

Default is `--system mac`. Switch to the Windows preset:

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "Win draft" --system windows \
  --scenes '[{"videoUrl":"https://example.com/v.mp4","duration":5}]'
```

Explicitly specify a path (overrides `--system`):

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "Custom path" \
  --draft-root-path "/Users/yourname/Movies/JianyingPro/User Data/Projects/com.lveditor.draft" \
  --scenes '[{"videoUrl":"https://example.com/v.mp4","duration":5}]'
```

### Only print the download URL (no file write)

```bash
python3 <SkillDir>/scripts/gen_jianying_draft.py \
  --title "URL only" --no-download \
  --scenes '[{"videoUrl":"https://example.com/v.mp4","duration":5}]'
```

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.

See every option: `python3 <SkillDir>/scripts/gen_jianying_draft.py --help`.

## Common CLI flags

| Flag | Description | Default |
|------|-------------|---------|
| `--from-render-plan` | Convert from a RenderPlan JSON automatically (replaces `--scenes`). | — |
| `--from-job-id` | Load the RenderPlan from the database (pass a jobId, replaces `--from-render-plan`). | — |
| `--scenes` | Scenes JSON (inline string or file path). | required (unless using `--from-render-plan` or `--from-job-id`) |
| `--title` | Draft title. | required (optional when using `--from-render-plan`) |
| `--width` | Canvas width in pixels. | `1080` |
| `--height` | Canvas height in pixels. | `1920` |
| `--draft-name` | Jianying draft name. | same as title |
| `--system` | Draft-root preset: `mac` / `windows`. | `mac` |
| `--draft-root-path` | Explicit Jianying draft root path (overrides `--system`). | resolved from `--system` |
| `--no-download` | Skip the ZIP download; print only the URL (ignored when textLayers / subtitleText is present). | off |
| `-o` / `--output` | Download path. | `jianying_draft.zip` |
| `--poll-interval` | Polling interval (seconds). | `5` |
| `--max-wait` | Maximum wait time (seconds). | `300` |
| `--private-token` | Override the token. | see table above |

### Draft-root presets

| `--system` | Resolved path |
|------------|---------------|
| `mac` | `~/Movies/JianyingPro/User Data/Projects/com.lveditor.draft` (`~` expands on the runner machine). |
| `windows` | `%LOCALAPPDATA%/JianyingPro/User Data/Projects/com.lveditor.draft` (literal, not expanded — the target Windows machine resolves it). |

## Scene JSON shape

Fields supported per scene:

| Field | Type | Description |
|-------|------|-------------|
| `videoUrl` | string | Video material URL (mutually exclusive with `imageUrl`). |
| `imageUrl` | string | Image material URL (mutually exclusive with `videoUrl`). |
| `duration` | float | Scene duration in seconds — required. |
| `width` | int | Material width (optional). |
| `height` | int | Material height (optional). |
| `audioUrl` | string | Audio URL (optional). |
| `audioDuration` | float | Audio duration in seconds (optional, defaults to `duration`). |
| `subtitleText` | string | Subtitle text (optional, overridden by `textLayers` when present). |
| `textLayers` | array | Multi-layer text styling (optional, see below). |

### textLayers array element

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.

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `content` | string | yes | — | Text content. |
| `role` | string | no | `"subheadline"` | Role preset: `headline` / `subheadline` / `badge` / `cta` / `caption`. |
| `fontSize` | number | no | role-based | Jianying font size (headline=15, subheadline=8). |
| `color` | string | no | role-based | Hex color `"#RRGGBB"`. |
| `bold` | bool | no | `false` | Whether to bold. |
| `position` | object | no | role-based | `{x, y}` normalized coordinates; 0=center, negative y=upper area. |
| `backgroundColor` | string | no | `null` | Text background color; `null` = transparent. |
| `shadow` | bool | no | `false` | Whether to add a shadow. |

**Role presets**:
- `headline`: size 15, white, bold, y=-0.35 (upper), with shadow.
- `subheadline`: size 8, gold #F0C040, y=0.38 (lower).
- `badge`: size 6, white, bold, top-left corner, gold background.
- `cta`: size 10, gold, bold, blue background.
- `caption`: size 6, light gray, y=0.44.

## How to import into Jianying

1. By default the draft ZIP is saved to `--output` (default `jianying_draft.zip`).
2. Extract the ZIP into the "draft root" the script prints — i.e. the `draftRootPath` passed to the API:
   - mac default: `~/Movies/JianyingPro/User Data/Projects/com.lveditor.draft/`
   - windows default: `%LOCALAPPDATA%/JianyingPro/User Data/Projects/com.lveditor.draft/`
   - custom: the path passed via `--draft-root-path`
3. Start (or restart) Jianying; the draft appears in the project list.

> 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.

## Error handling

- **401** / **token missing** (non-OpenClaw): check or set the token via the Feishu link above.
- **Business `code != 0`**: read the `msg` printed on stderr.
- **Generation timeout**: increase `--max-wait` or retry later.
- **Network**: check connectivity and `MM_API_BASE_URL`.
