@remixmate/cli 0.9.19 → 0.9.21

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.
@@ -106,6 +106,18 @@ This skill does not hit any external API; no token required. The script only doe
106
106
  Show **every scene in full** — do not collapse them with phrases like "scenes 2–6 same as above".
107
107
  The user is reviewing the narration word by word; a summary they cannot proofread defeats the gate.
108
108
 
109
+ > ⚠️ **Read every value back from the returned DSL, never from what you meant to pass.**
110
+ > The summary exists so the user can catch a wrong tool call; a summary written from intent hides
111
+ > exactly the bug it should surface.
112
+ > - Durations ← `scenes[].duration`, **not** your `--duration` argument (`fit-caption` / `fit-narration`
113
+ > templates recompute it and ignore the target you passed).
114
+ > - On-screen text ← `textLayers[]`, `customPayload.caption.lines`, `customPayload.carousel.items` —
115
+ > quoted verbatim, with the counts you actually see.
116
+ > - If something you intended to set is missing or empty in the returned DSL, that is a failed call:
117
+ > say so and re-run `gen_script.py` with the right flags. Listing caption lines that are not in
118
+ > `customPayload.caption.lines` means the user confirms a script that does not exist and pays to
119
+ > render something else.
120
+
109
121
  ### Summary content
110
122
 
111
123
  The agent should show the following in clear Markdown:
@@ -156,9 +168,12 @@ For these templates the picture comes from `customPayload.carousel.items`, which
156
168
  When the user selects a `carousel-caption` template (or any template whose `assetRequirements` is image/video-only and whose `payloadStyle` is `carousel-caption`):
157
169
 
158
170
  1. **Extract every media URL the user provided** (image or video links in the prompt) and pass each one as a separate `--carousel-items <url>` flag — preserve the user's order, and pass the URLs **verbatim** (do not rewrite host/path/query).
159
- 2. If the user wants on-screen text, pass each caption line as `--caption-lines '<text>'`. For purely visual templates like `adaptive-image-video` (no text, `needsNarration: false`), captions are optional.
171
+ 2. Pass each on-screen caption line as `--caption-lines '<text>'`. Whether this is optional depends on the template's `capabilities.durationStrategy`, **not** on `needsNarration` (every `carousel-caption` template has `needsNarration: false`):
172
+ - `durationStrategy: fit-caption` (e.g. `spotlight-card`) → **caption lines are mandatory.** The template has no narration and the typewriter copy is both the content and the clock: it is what the video says *and* what decides how long it runs. **If the user did not supply the copy, write it yourself** from the material you researched (repo README, page screenshots, the topic) and pass it. `gen_script.py` hard-fails on an empty caption for these templates.
173
+ - `durationStrategy: fit-images` (e.g. `adaptive-image-video` / `image-to-video`) → purely visual, captions genuinely optional; duration comes from the image count.
160
174
  3. **Never call gen_script for a `carousel-caption` template without `--carousel-items`.** If the user picked such a template but provided no media, ask them for the image/video URLs first — do not generate an empty carousel.
161
175
  4. Do **not** route these user-provided images through `gen-image`; they are existing assets and go straight into the carousel.
176
+ 5. Nothing downstream fills these in for you. `gen_script.py` routes on `capabilities.payloadStyle` alone — omitting the flags does **not** fall back to a generic path that generates images or writes copy; it assembles an empty carousel / empty caption. There is no auto-generation of caption text anywhere in the pipeline.
162
177
 
163
178
  ### Command example
164
179
 
@@ -266,7 +281,9 @@ python3 <SkillDir>/scripts/gen_script.py \
266
281
  |------|-------------|---------|
267
282
  | `--topic` | Video topic (required unless `--validate`). | — |
268
283
  | `--platform` | Target platform: `douyin` / `xiaohongshu` / `bilibili` / `wechat` / `youtube` / `generic`. | `generic` |
269
- | `--duration` | Target duration (seconds). | `30` |
284
+ | `--headline` | On-screen main title (4–12 chars / ~3 words). Written to `meta.headline` + `textLayers[role=headline]`. **Pass it whenever the user gave a title** — otherwise headline falls back to the long-form topic and overflows the top text layer. | falls back to `--topic` |
285
+ | `--subheadline` | On-screen subtitle / project name / slogan. Written to `meta.subheadline` + `textLayers[role=subheadline]`. Not the same thing as CC subtitles (`global.subtitle`). | `""` |
286
+ | `--duration` | Target duration (seconds). Templates whose `durationStrategy` is `fit-caption` / `fit-narration` recompute the real duration and ignore this value. | `30` |
270
287
  | `--style` | Style tag. | — |
271
288
  | `--ratio` | Aspect ratio. | `16:9` |
272
289
  | `--scenes` | Scene count. | auto-planned |
@@ -279,7 +296,7 @@ python3 <SkillDir>/scripts/gen_script.py \
279
296
  | `--stub-image-url` | Test mode: every image AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_IMAGE_URL`). | — |
280
297
  | `--stub-video-url` | Test mode: every video AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_VIDEO_URL`). | — |
281
298
  | `--carousel-items` | Repeatable. Media URL placed directly into `customPayload.carousel.items` for `carousel-caption` templates (e.g. `adaptive-image-video`, `spotlight-card`). Bypasses gen-image. **Required** for `carousel-caption` templates when the user supplies images. | — |
282
- | `--caption-lines` | Repeatable. On-screen typewriter caption line for `carousel-caption` templates → `customPayload.caption.lines`. Supports `**emphasis**`. Optional for purely visual templates. | — |
299
+ | `--caption-lines` | Repeatable. On-screen typewriter caption line for `carousel-caption` templates → `customPayload.caption.lines` (max 10 lines). Supports `**emphasis**`. **Required** for `durationStrategy: fit-caption` templates (`spotlight-card`) — write the lines yourself if the user did not supply them. Optional only for `fit-images` templates. | — |
283
300
 
284
301
  ## DSL generation principles
285
302
 
@@ -295,7 +312,7 @@ Apply the following principles when producing the DSL:
295
312
  instead of one continuous paragraph.
296
313
  5. **Moderate scene count**: 30-second videos work well with 4–6 scenes, 60-second videos with 6–10.
297
314
  6. **Leave room for templates**: pick generic layouts; do not assume a specific template implementation.
298
- 7. **Image model allowlist**: every `type: image` + `source: gen-image` `AssetRef`'s `payload.model` **must** be one of the values in the table below. **Never** use display names, short forms, or made-up ids (e.g. `seedream`, `gemini-flash`, etc.).
315
+ 7. **Image model allowlist**: every `type: image` + `source: gen-image` `AssetRef`'s `payload.model` **must** be one of the values in the table below. **Never** use display names, short forms, or made-up ids (e.g. `seedream`, `seedream-5`, etc.).
299
316
 
300
317
  ### Allowlist `model` values aligned with gen-image
301
318
 
@@ -307,12 +324,11 @@ prefix and version — not a short alias:
307
324
  |-----------------|--------------|----------|-------|
308
325
  | `doubao/doubao-seedream-5-0-260128` | Seedream 5.0 Lite | Volcano | Default. Highest output resolution, up to 14 reference images. |
309
326
  | `doubao/doubao-seedream-5-0-pro-260628` | Seedream 5.0 Pro | Volcano | High fidelity: precise element placement, faithful on-image text. Up to 10 reference images, caps out around 2K. Costs noticeably more per image. |
310
- | `gemini-3-pro` | Gemini 3 Pro | Google | Up to 4 reference images. |
311
327
 
312
328
  **Agent behavior (avoid accidentally rewriting `model`)**:
313
329
 
314
330
  - `gen_script.py` already writes a valid `payload.model` (`doubao/doubao-seedream-5-0-260128` unless `DEFAULT_IMAGE_MODEL` overrides it). When the user only asks to refine narration, change `payload.prompt`, add or remove scenes, etc. and does **not** ask to change the image model, the agent **must keep** each image asset's original `payload.model` — do not replace it under the guise of "polishing the script".
315
- - **Only when the user explicitly asks to change the image model** (e.g. switches to the Pro variant or to Gemini), update the corresponding image `AssetRef`'s `payload.model` to the matching row id from the table. Writing a display name or an alias into JSON is wrong.
331
+ - **Only when the user explicitly asks to change the image model** (e.g. switches to the Pro variant), update the corresponding image `AssetRef`'s `payload.model` to the matching row id from the table. Writing a display name or an alias into JSON is wrong.
316
332
  - When creating a new image `AssetRef`, pick one of the values above for `payload.model`; default to `doubao/doubao-seedream-5-0-260128` to match the script, or to whichever value the user specified.
317
333
  - If a run fails with an unknown-model error, the catalog has moved on from this table — check `/model/capabilities` rather than guessing a version string.
318
334
 
@@ -321,6 +337,9 @@ prefix and version — not a short alias:
321
337
  - **Schema validation failed**: check the DSL JSON shape and required fields against the schema.
322
338
  - **Scene duration mismatch**: adjust the narration length or the scene duration.
323
339
  - **Invalid platform**: look at the supported-platform list.
340
+ - **`carousel-caption template ... needs visual or text content`**: you passed neither `--carousel-items` nor `--caption-lines`. Pass the user's media URLs (and caption lines where the template requires them).
341
+ - **`template ... is typewriter-driven ... but --caption-lines is empty`**: a `fit-caption` template (e.g. `spotlight-card`) got no caption. Write the copy yourself if the user did not supply it, then pass one `--caption-lines` per line.
342
+ - **`duration Ns is below/above template ... supportedDurations`**: the assembled DSL falls outside the range the template declares it was designed for. Below the minimum usually means the content is too thin (add caption lines / narration / scenes); above the maximum means trimming content or lowering `--duration`. This is enforced at generation time on purpose — a degenerate video still costs full render credits.
324
343
 
325
344
  ## scripts/ contents
326
345
 
@@ -475,6 +475,51 @@ def _resolve_contract(template_config: dict | None) -> dict:
475
475
  }
476
476
 
477
477
 
478
+ def _enforce_supported_duration(
479
+ dsl: dict, template_config: dict | None, template_id: str | None
480
+ ) -> None:
481
+ """校验成片总时长落在模板自己声明的 ``supportedDurations`` 区间内。
482
+
483
+ 这条约束此前只在 template-library 的 CI(``check-dsl-examples.mjs``)对仓库里的
484
+ 示例 DSL 生效,运行时链路(gen_script → prepare_video_assets → render_video)没有
485
+ 任何一环校验它。于是 spotlight-card(``min: 10``)可以静默产出 5s 成片——模板自己
486
+ 声明"我不是为 5s 设计的",却没人拦。这里在 DSL 出厂前补上同一道栅栏。
487
+
488
+ 越界即报错退出,而不是静默出片:时长不足通常意味着内容(打字机文案 / 旁白 / 场景)
489
+ 根本没填够,继续往下走只会烧掉渲染积分换一条废片。
490
+ """
491
+ sd = (template_config or {}).get("supportedDurations")
492
+ if not isinstance(sd, dict):
493
+ return
494
+
495
+ total = sum(s.get("duration", 0) for s in dsl.get("scenes", []))
496
+ lo = sd.get("min")
497
+ hi = sd.get("max")
498
+
499
+ if isinstance(lo, (int, float)) and total < lo:
500
+ print(
501
+ f"❌ duration {total}s is below template '{template_id}' declared minimum {lo}s "
502
+ f"(supportedDurations: [{lo}, {hi}]).\n"
503
+ " The template declares it is not designed for clips this short; rendering "
504
+ "anyway burns credits on a degenerate video.\n"
505
+ " Fix: add content until the estimated duration reaches the minimum — more "
506
+ "--caption-lines for typewriter-driven templates, longer narration / more scenes "
507
+ "for narration-driven ones.",
508
+ file=sys.stderr,
509
+ )
510
+ sys.exit(1)
511
+
512
+ if isinstance(hi, (int, float)) and total > hi:
513
+ print(
514
+ f"❌ duration {total}s exceeds template '{template_id}' declared maximum {hi}s "
515
+ f"(supportedDurations: [{lo}, {hi}]).\n"
516
+ " Fix: shorten the content (fewer --caption-lines / scenes) or lower --duration, "
517
+ "or pick a template that supports longer videos.",
518
+ file=sys.stderr,
519
+ )
520
+ sys.exit(1)
521
+
522
+
478
523
  def _plan_contract_scenes(
479
524
  contract: dict, topic: str, duration: int, scene_count: int | None, language: str
480
525
  ) -> list:
@@ -586,6 +631,8 @@ def _build_carousel_caption_dsl(
586
631
  resolution: str,
587
632
  output_language: str,
588
633
  resolved_voice_id: str,
634
+ font_id: str | None,
635
+ font_name: str | None,
589
636
  narration_enabled: bool,
590
637
  payload_defaults: dict,
591
638
  duration_strategy: str | None,
@@ -732,6 +779,7 @@ def _build_carousel_caption_dsl(
732
779
  # 并省略 global.narration,避免下游误判存在旁白。
733
780
  "subtitle": {"enabled": narration_enabled, "style": "bottom"},
734
781
  **({"narration": {"voiceId": resolved_voice_id, "speed": 1.0}} if narration_enabled else {}),
782
+ **({"font": {"fontId": font_id, **({"fontName": font_name} if font_name else {})}} if font_id else {}),
735
783
  "bgm": {"enabled": True, "volume": 0.12},
736
784
  },
737
785
  "assets": assets,
@@ -779,6 +827,8 @@ def build_dsl(
779
827
  ratio: str,
780
828
  resolution: str,
781
829
  voice_id: str,
830
+ font_id: str | None,
831
+ font_name: str | None,
782
832
  scene_count: int | None,
783
833
  allow_digital_human: bool,
784
834
  allow_ai_video: bool,
@@ -861,7 +911,32 @@ def build_dsl(
861
911
  file=sys.stderr,
862
912
  )
863
913
  sys.exit(1)
864
- return _build_carousel_caption_dsl(
914
+ # 兜底校验 ②:durationStrategy=fit-caption 的模板(spotlight-card 类)由打字机
915
+ # 文案驱动节奏 —— caption 就是内容本体,不是可选装饰。caption_lines 为空时上面的
916
+ # "两者皆空" 检查放行,产出的却是「顶部标题 + 轮播、底部一个字都没有」的退化片:
917
+ # 估时掉到轮播地板值(2 张图 ≈ 5s),而调用方往往还在确认摘要里描述了一段
918
+ # 根本没进 DSL 的文案,用户在确认环节也看不出来。所以这里必须硬失败。
919
+ # 注意:只卡 fit-caption。carousel-caption 里 durationStrategy=fit-images 的
920
+ # 纯视觉模板(image-to-video 等)本来就允许无文案,不受影响。
921
+ if contract["duration_strategy"] == "fit-caption" and not (caption_lines or []):
922
+ print(
923
+ "❌ template "
924
+ f"'{template_id}' is typewriter-driven (capabilities.durationStrategy="
925
+ "fit-caption), but --caption-lines is empty.\n"
926
+ " For this template the bottom typewriter copy IS the content: it carries "
927
+ "the message and it decides the video length. With no lines the render "
928
+ "collapses to the carousel floor (~5s for 2 images) and shows no text at all.\n"
929
+ " Fix: pass one --caption-lines '<text>' per line. If the user did not "
930
+ "supply the copy, WRITE IT YOURSELF from the material you researched "
931
+ "(repo README, page screenshots, the user's topic) and pass it — do not leave "
932
+ "it empty, and do not describe lines you never passed.\n"
933
+ " gen_script.py --topic <topic> --template-id "
934
+ f"{template_id} --carousel-items <url> "
935
+ "--caption-lines '<line 1>' --caption-lines '<line 2>' ...",
936
+ file=sys.stderr,
937
+ )
938
+ sys.exit(1)
939
+ carousel_dsl = _build_carousel_caption_dsl(
865
940
  template_id=template_id,
866
941
  topic=topic,
867
942
  headline=effective_headline,
@@ -875,10 +950,14 @@ def build_dsl(
875
950
  resolution=resolution,
876
951
  output_language=output_language,
877
952
  resolved_voice_id=resolved_voice_id,
953
+ font_id=font_id,
954
+ font_name=font_name,
878
955
  narration_enabled=needs_narration,
879
956
  payload_defaults=contract["payload_defaults"],
880
957
  duration_strategy=contract["duration_strategy"],
881
958
  )
959
+ _enforce_supported_duration(carousel_dsl, template_config, template_id)
960
+ return carousel_dsl
882
961
 
883
962
  # ── 其余模板:统一场景规划(arc 叙事弧 / single / fixed)+ 统一装配循环 ──────
884
963
  scene_plans = _plan_contract_scenes(contract, topic, duration, scene_count, output_language)
@@ -940,14 +1019,16 @@ def build_dsl(
940
1019
  })
941
1020
  else:
942
1021
  # 视频素材:复用 image prompt 生成器作为兜底,再追加 "视频/动态" 关键词
943
- # 模型 / 时长 / 比例都遵循 gen-video 校验规则(Seedance 4-12s、Veo 4/6/8s
1022
+ # 模型 / 时长 / 比例都遵循 gen-video 校验规则(Seedance 2.0:4-15s
944
1023
  image_result = generate_image_prompt(plan["purpose"], topic, style, narration_text=narration_text)
945
1024
  video_prompt = image_result["prompt"]
946
- # 视频时长上限按 gen-video Seedance 模型的 12s 截断,下限 4s
947
- video_duration = max(4, min(int(plan["duration"]), 12))
1025
+ # 视频时长按 gen-video 4-15s 区间截断
1026
+ video_duration = max(4, min(int(plan["duration"]), 15))
948
1027
  vid_payload = {
949
1028
  "prompt": video_prompt,
950
- "model": os.environ.get("DEFAULT_VIDEO_MODEL", "doubao-seedance-1-5-pro-251215"),
1029
+ # 写别名而不是带日期的模型 ID:真源是 ab-api 的能力目录,
1030
+ # 版本号换代时这里不该跟着改(seedance → 当前的 Seedance 2.0)
1031
+ "model": os.environ.get("DEFAULT_VIDEO_MODEL", "seedance"),
951
1032
  "ratio": ratio,
952
1033
  "duration": video_duration,
953
1034
  }
@@ -1092,6 +1173,7 @@ def build_dsl(
1092
1173
  # global.narration,避免下游误判存在旁白。
1093
1174
  "subtitle": {"enabled": needs_narration, "style": "bottom"},
1094
1175
  **({"narration": {"voiceId": resolved_voice_id, "speed": 1.0}} if needs_narration else {}),
1176
+ **({"font": {"fontId": font_id, **({"fontName": font_name} if font_name else {})}} if font_id else {}),
1095
1177
  "bgm": {"enabled": True, "volume": 0.12},
1096
1178
  },
1097
1179
  "assets": assets,
@@ -1102,6 +1184,8 @@ def build_dsl(
1102
1184
  if template_id:
1103
1185
  dsl["meta"]["templateId"] = template_id
1104
1186
 
1187
+ _enforce_supported_duration(dsl, template_config, template_id)
1188
+
1105
1189
  return dsl
1106
1190
 
1107
1191
 
@@ -1128,6 +1212,18 @@ Examples:
1128
1212
  parser.add_argument("--resolution", default="1080p", help="Resolution (default: 1080p)")
1129
1213
  parser.add_argument("--scenes", type=int, default=None, help="Scene count (default: auto-planned)")
1130
1214
  parser.add_argument("--voice-id", default=None, help="Narration voice id. When omitted, the resolver picks template.defaultVoiceId, then the language-keyed fallback (zh→Chinese (Mandarin)_Male_Announcer, en→TBD English voice). gen_voice --list-voices --local prints the language-tagged catalog.")
1215
+ parser.add_argument(
1216
+ "--font-id",
1217
+ default=None,
1218
+ help=(
1219
+ "Font family key from the font library (font.uniq_id, e.g. zzgf-xi-mai). "
1220
+ "Omit to follow the template's own font pairing. "
1221
+ "Pass the opaque key, NOT a CSS family name: the family name is a rendering "
1222
+ "detail resolved by ab-render; a mistyped family name silently falls back to "
1223
+ "the default font with no error."
1224
+ ),
1225
+ )
1226
+ parser.add_argument("--font-name", default=None, help="Human-readable font name. Logged and stored for display only; never used for rendering.")
1131
1227
  parser.add_argument("--allow-digital-human", action="store_true", help="Allow digital-human assets")
1132
1228
  parser.add_argument("--allow-ai-video", action="store_true", help="Allow AI-generated video assets")
1133
1229
  parser.add_argument("--template-id", default=None, help="Template id. The template owns outputLanguage and may declare a defaultVoiceId.")
@@ -1276,6 +1372,8 @@ Examples:
1276
1372
  ratio=args.ratio,
1277
1373
  resolution=args.resolution,
1278
1374
  voice_id=args.voice_id,
1375
+ font_id=args.font_id,
1376
+ font_name=args.font_name,
1279
1377
  scene_count=args.scenes,
1280
1378
  allow_digital_human=args.allow_digital_human,
1281
1379
  allow_ai_video=args.allow_ai_video,
@@ -76,7 +76,7 @@
76
76
  "items": {
77
77
  "type": "string"
78
78
  },
79
- "description": "Bottom typewriter text lines for templates that support a caption/typewriter area (e.g. spotlight-card). Each element is one line of text. Supports **emphasis** syntax (rendered with accent color). **Must** pass when the user explicitly provides bullet-point text / bottom copy for the video."
79
+ "description": "Bottom typewriter text lines for templates that support a caption/typewriter area (e.g. spotlight-card). Each element is one line of text, max 10 lines. Supports **emphasis** syntax (rendered with accent color). **Must** pass when the user explicitly provides bullet-point text / bottom copy. **Also must pass — written by you — when the user did NOT provide any copy but the template is typewriter-driven** (capabilities.durationStrategy=fit-caption, e.g. spotlight-card): such templates have no narration, so these lines are both the video's content and the thing that decides its duration. Draft them from the material you researched (repo README, page screenshots, the topic). Leaving this empty for a fit-caption template is rejected: nothing auto-generates caption text, and an empty caption renders a titled carousel with no words at all."
80
80
  },
81
81
  "stub_image_url": {
82
82
  "type": "string",
@@ -97,11 +97,12 @@
97
97
  },
98
98
  "ui": {
99
99
  "primary": [
100
- "topic",
101
- "platform",
102
- "duration"
100
+ "topic"
103
101
  ],
104
- "advanced": [
102
+ "advanced": [],
103
+ "hidden": [
104
+ "platform",
105
+ "duration",
105
106
  "style",
106
107
  "ratio",
107
108
  "scenes",
@@ -110,9 +111,7 @@
110
111
  "headline",
111
112
  "subheadline",
112
113
  "carousel_items",
113
- "caption_lines"
114
- ],
115
- "hidden": [
114
+ "caption_lines",
116
115
  "stub_image_url",
117
116
  "stub_video_url",
118
117
  "skip_asset_generation"
@@ -1,23 +1,25 @@
1
1
  ---
2
2
  name: gen-video
3
3
  description: |
4
- AI video generation skill: produce a short clip from a text prompt. Backed by ab-api's `/model/genVideo` (Seedance and Veo families).
4
+ AI video generation skill: produce a short clip from a text prompt. Backed by ab-api's `/model/genVideo` (Seedance 2.0 family).
5
5
 
6
6
  Use this skill immediately whenever the user asks for any of:
7
7
  - Text-to-video, AI-generated clip, "make a short video of ..."
8
- - Generate video with Doubao / Seedance / Veo / Google
8
+ - Generate video with Doubao / Seedance
9
9
  - Image-to-video, first-frame / last-frame, reference-image-to-video
10
+ - Omni-modal reference: borrow the look of a reference image, the camera work of a reference video, or the voice / music of a reference audio clip
10
11
 
11
12
  Even without an explicit "use AI", any request that turns a description into a moving clip should route here.
12
13
  triggers:
13
14
  - Text-to-video, AI-generated clip, "make a short video of ..."
14
- - Generate video with Doubao / Seedance / Veo / Google
15
+ - Generate video with Doubao / Seedance
15
16
  - Image-to-video, first-frame / last-frame, reference-image-to-video
17
+ - Reference video / reference audio, "same camera move as this clip", "use this track"
16
18
  ---
17
19
 
18
20
  # AI Video Generation Skill
19
21
 
20
- Wraps ab-api's `POST /model/genVideo` (the same endpoint the web "Lingchuang AI Video" tool uses), authenticated with the **Tianyan privateToken**, routed through LiteLLM to **Seedance** or **Veo**. Generation is async — the handler submits the task and polls `/model/getVideoStatus` until completion.
22
+ Wraps ab-api's `POST /model/genVideo` (the same endpoint the web "Lingchuang AI Video" tool uses), authenticated with the **Tianyan privateToken**, routed through LiteLLM to **Seedance 2.0**. Generation is async — the handler submits the task and polls `/model/getVideoStatus` until completion.
21
23
 
22
24
  > This skill was migrated from a Python script to an remixmate CLI HTTP handler (`entry.type: http`). The agent invocation is unchanged (same tool name `gen_video`, same params as in `skill.json`); local repro goes through `remixmate gen-video ...`.
23
25
 
@@ -27,29 +29,58 @@ The authoritative roster — ids, aliases and per-model limits — lives in the
27
29
  (`/model/capabilities`), which the CLI fetches at runtime. The table below mirrors it; when the
28
30
  two disagree, the catalog wins.
29
31
 
30
- | LiteLLM `model` | Display name | Provider | Duration | Notes |
31
- |-----------------|--------------|----------|----------|-------|
32
- | `doubao-seedance-1-5-pro-251215` | Seedance 1.5 Pro | Volcano | 4–12s | Audio support, first/last frame, fixed camera, adaptive ratio |
33
- | `veo-3.1-generate-001` | Veo 3.1 | Google | 4 / 6 / 8s | Native audio, first/last frame, reference images, negative prompt, up to 4K |
34
- | `veo-3.1-fast-generate-001` | Veo 3.1 Fast | Google | 4 / 6 / 8s | Faster Veo 3.1 variant for quick iteration |
32
+ All three are Seedance 2.0; they differ only in price tier and top resolution.
33
+
34
+ | LiteLLM `model` | Display name | Provider | Resolution | Notes |
35
+ |-----------------|--------------|----------|------------|-------|
36
+ | `doubao-seedance-2-0-mini-260615` | Seedance-2.0-mini | Volcano | `480p` `720p` | Default. Cheapest tier (~half the standard rate) |
37
+ | `doubao-seedance-2-0-fast-260128` | Seedance-2.0-fast | Volcano | `480p` `720p` | Mid tier |
38
+ | `doubao-seedance-2-0-260128` | Seedance-2.0 | Volcano | `480p` `720p` `1080p` | Highest quality; the only tier with 1080p |
35
39
 
36
40
  **Model shortcuts** (`--model` / `-m` accepts these directly):
37
- - `seedance` / `seedance-1.5` / `seedance-1.5-pro` → Seedance 1.5 Pro
38
- - `veo` / `veo-3.1` / `veo-3.1-generate` → Veo 3.1
39
- - `veo-fast` / `veo-3.1-fast` / `veo-3.1-fast-generate` → Veo 3.1 Fast
40
-
41
- ### Per-model parameter ranges
42
-
43
- | | Seedance 1.5 Pro | Veo 3.1 / Veo 3.1 Fast |
44
- |--|--|--|
45
- | **Aspect ratio** | `adaptive` `16:9` `4:3` `1:1` `3:4` `9:16` `21:9` | `16:9` `9:16` |
46
- | **Resolution** | `480p` `720p` `1080p` | `720p` `1080p` `4k` |
47
- | **Duration** | 4–12 seconds (continuous integers) | 4 / 6 / 8 seconds |
48
- | **First / last frame** | yes | yes |
49
- | **Reference images** | not supported (use first/last frame) | yes, up to 3 |
50
- | **Generated audio** | yes | yes (native audio) |
51
- | **Fixed camera** | yes | — |
52
- | **Negative prompt** | | yes |
41
+ - `seedance-mini` / `seedance-2.0-mini` → Seedance-2.0-mini
42
+ - `seedance-fast` / `seedance-2.0-fast` → Seedance-2.0-fast
43
+ - `seedance` / `seedance-2.0` Seedance-2.0
44
+
45
+ Veo 3.1 / Veo 3.1 Fast are **not available** — there is no working Veo channel on the gateway,
46
+ so they are absent from the catalog and `--model veo` will fail.
47
+
48
+ ### Parameter ranges (identical across the three tiers, except resolution)
49
+
50
+ | | Seedance 2.0 mini / fast / standard |
51
+ |--|--|
52
+ | **Aspect ratio** | `adaptive` `16:9` `4:3` `1:1` `3:4` `9:16` `21:9` |
53
+ | **Resolution** | `480p` `720p` (`1080p` on `seedance` only) |
54
+ | **Duration** | 4–15 seconds (continuous integers) |
55
+ | **First / last frame** | yes |
56
+ | **Reference images** | yes, up to 9 |
57
+ | **Reference videos** | yes, up to 3 (public https URLs) |
58
+ | **Reference audio** | yes, up to 3 (public https URLs) |
59
+ | **Generated audio** | yes |
60
+ | **Fixed camera** | yes |
61
+ | **Web search** | yes, text-only input |
62
+ | **Negative prompt** | ignored (Veo-only parameter) |
63
+
64
+ 4k output exists on the standard tier upstream but is **not offered here** — the credit
65
+ pricing table has no 4k row, so a 4k run would be billed at the 720p rate.
66
+
67
+ ### Two input modes, and they do not mix
68
+
69
+ - **Pinned frames** — `--first-frame` / `--last-frame` fix the exact opening and closing
70
+ images. Reference images may be added alongside.
71
+ - **Omni-modal reference** — `--reference` (images) / `--reference-video` / `--reference-audio`
72
+ hand the model material to borrow from; it decides the framing.
73
+
74
+ Combining a pinned frame with a reference video or audio is rejected (by the CLI, and by the
75
+ backend). Audio alone is not a valid input either: `--reference-audio` needs at least one
76
+ reference image or video with it.
77
+
78
+ In the prompt, refer to attachments as **"image 1" / "video 2" / "audio 1"**, numbered per
79
+ type in the order you passed them. That is the only way the model can tell them apart —
80
+ file names and asset ids mean nothing to it.
81
+
82
+ Reference images and videos containing **real human faces are rejected upstream** by the
83
+ Seedance 2.0 content filter; the run fails before it charges.
53
84
 
54
85
  ## Auth & environment
55
86
 
@@ -61,7 +92,7 @@ No skill-local env file — the executing process inherits the system environmen
61
92
  | Env var | Description | Default |
62
93
  |---------|-------------|---------|
63
94
  | `PRIV_TOKEN` | Tianyan token; `--priv-token` overrides | none |
64
- | `MM_VIDEO_MODEL` | Default model id | `doubao-seedance-1-5-pro-251215` |
95
+ | `MM_VIDEO_MODEL` | Default model id or shortcut | catalog default (`seedance-mini`) |
65
96
  | `MM_API_BASE_URL` | API root; `--api-base-url` overrides | `https://api.remixmate.com/api` |
66
97
  | `AGENT_NAME` | Optional `x-invoke-agent` header | none |
67
98
 
@@ -71,7 +102,7 @@ No skill-local env file — the executing process inherits the system environmen
71
102
  2. **Async job**: video generation is async; the handler polls until completion (typically 1–3 minutes) and emits `__progress__` lines.
72
103
  3. **Surface results**: stdout prints the video URL on its own line; show it directly to the user (no download needed — the URL is cloud-persisted).
73
104
 
74
- ### Seedance text-to-video (default model)
105
+ ### Text-to-video (default model: Seedance-2.0-mini)
75
106
 
76
107
  ```bash
77
108
  remixmate gen-video \
@@ -80,7 +111,7 @@ remixmate gen-video \
80
111
  --ratio "16:9"
81
112
  ```
82
113
 
83
- ### Seedance first/last frame + audio
114
+ ### First/last frame + audio
84
115
 
85
116
  Frame images accept local file paths, HTTPS URLs, or data URIs (local files are base64-encoded into a data URI).
86
117
 
@@ -93,37 +124,54 @@ remixmate gen-video \
93
124
  --generate-audio
94
125
  ```
95
126
 
96
- ### Veo 3.1 high-resolution
127
+ ### 1080p final cut
128
+
129
+ Only `seedance` (the standard tier) offers 1080p; it also costs roughly twice mini per second.
97
130
 
98
131
  ```bash
99
132
  remixmate gen-video \
100
- --model veo \
133
+ --model seedance \
101
134
  --prompt "<video description>" \
102
135
  --duration 8 \
103
136
  --ratio "16:9" \
104
- --resolution 4k
137
+ --resolution 1080p
105
138
  ```
106
139
 
107
- ### Veo 3.1 Fast for rapid iteration
140
+ ### With reference images
141
+
142
+ Pass `--reference` multiple times (up to 9).
108
143
 
109
144
  ```bash
110
145
  remixmate gen-video \
111
- --model veo-fast \
112
146
  --prompt "<video description>" \
113
- --duration 6
147
+ --reference ./ref1.png \
148
+ --reference ./ref2.png \
149
+ --duration 8
114
150
  ```
115
151
 
116
- ### Veo with reference images
152
+ ### Omni-modal reference (image + video + audio)
117
153
 
118
- Pass `--reference` multiple times (Veo only, up to 3).
154
+ Videos and audio must be public https URLs — unlike images they are not uploaded.
119
155
 
120
156
  ```bash
121
157
  remixmate gen-video \
122
- --model veo \
123
- --prompt "<video description>" \
124
- --reference ./ref1.png \
125
- --reference ./ref2.png \
126
- --duration 8
158
+ --prompt "Keep the first-person framing of video 1 and use audio 1 as the score; the product from image 1 is lifted toward the lens" \
159
+ --reference ./product.png \
160
+ --reference-video https://example.com/handheld.mp4 \
161
+ --reference-audio https://example.com/score.mp3 \
162
+ --duration 8 \
163
+ --generate-audio
164
+ ```
165
+
166
+ ### Fresh subjects (web search)
167
+
168
+ Only fires on text-only input; attaching any image / video / audio turns it off.
169
+
170
+ ```bash
171
+ remixmate gen-video \
172
+ --prompt "<a subject the model may not know: a new product, a current event>" \
173
+ --web-search \
174
+ --duration 5
127
175
  ```
128
176
 
129
177
  ## Common CLI flags
@@ -131,17 +179,21 @@ remixmate gen-video \
131
179
  | Flag | Description | Default |
132
180
  |------|-------------|---------|
133
181
  | `-p` / `--prompt` | Video description (combinable with first/last frame or references) | — |
134
- | `-m` / `--model` | Model id or shortcut (`seedance` / `veo` / `veo-fast`) | see `MM_VIDEO_MODEL` |
135
- | `-d` / `--duration` | Duration in seconds. Defaults to 5 (Seedance) or 8 (Veo) when omitted | per model |
136
- | `-r` / `--ratio` | Aspect ratio (default `16:9`). Seedance also accepts `adaptive` | `16:9` |
137
- | `--resolution` | Resolution. Veo accepts `4k` | `720p` |
182
+ | `-m` / `--model` | Model id or shortcut (`seedance-mini` / `seedance-fast` / `seedance`) | see `MM_VIDEO_MODEL` |
183
+ | `-d` / `--duration` | Duration in seconds (4–15) | 5 |
184
+ | `-r` / `--ratio` | Aspect ratio; `adaptive` also accepted | `16:9` |
185
+ | `--resolution` | Resolution. `1080p` only on `seedance` | `720p` |
138
186
  | `--first-frame` | First-frame image: local path, https URL, or data URI | none |
139
187
  | `--last-frame` | Last-frame image | none |
140
- | `--reference` | Reference image (repeatable; Veo only, max 3) | none |
188
+ | `--reference` | Reference image (repeatable, max 9) | none |
189
+ | `--reference-video` | Reference video, public https URL (repeatable, max 3) | none |
190
+ | `--reference-audio` | Reference audio, public https URL (repeatable, max 3) | none |
141
191
  | `--generate-audio` | Generate native audio | off |
142
- | `--camera-fixed` | Fixed camera (mainly Seedance) | off |
143
- | `--negative-prompt` | Negative prompt (mainly Veo) | none |
144
- | `--seed` | Random seed | none |
192
+ | `--camera-fixed` | Fixed camera | off |
193
+ | `--web-search` | Search the web first (text-only input) | off |
194
+ | `--return-last-frame` | Also return the clip's last frame, to chain shots | off |
195
+ | `--negative-prompt` | Negative prompt (Veo only; ignored by Seedance) | none |
196
+ | `--seed` | Random seed; `-1` = random | none |
145
197
  | `--person-generation` | Person policy: `allow_all` / `dont_allow` (Veo) | none |
146
198
  | `--api-base-url` | Override API root | see above |
147
199
  | `--priv-token` | Override token | see above |
@@ -4,7 +4,7 @@
4
4
  "tier": "atomic",
5
5
  "category": "asset",
6
6
  "title": "AI Video Generation",
7
- "description": "AI video generation: produce a short video clip from a text prompt. Supports Seedance and Veo models, plus first/last frame and reference images.",
7
+ "description": "AI video generation: produce a short video clip from a text prompt. Three Seedance 2.0 tiers (mini / fast / standard), plus first/last frame and reference images.",
8
8
  "auth": "required",
9
9
  "joinsTake": true,
10
10
  "envVars": [
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "model": {
28
28
  "type": "string",
29
- "description": "Model: 'seedance' (default; 4-12s, adaptive/21:9 ratios, fixed camera), 'veo' (4/6/8s, native audio, up to 4k, reference images), or 'veo-fast' (faster Veo variant for iteration)"
29
+ "description": "Model: 'seedance-mini' (default; cheapest, 480p/720p), 'seedance-fast' (480p/720p), or 'seedance' (Seedance 2.0, adds 1080p). All are 4-15s and share the same features"
30
30
  },
31
31
  "duration": {
32
32
  "type": "number",
@@ -53,7 +53,29 @@
53
53
  "items": {
54
54
  "type": "string"
55
55
  },
56
- "description": "Reference images: local file path, https URL, or data URI. Veo only, max 3 Seedance rejects them, use first_frame / last_frame instead."
56
+ "description": "Reference images: local file path, https URL, or data URI. Up to 9; combinable with first_frame / last_frame."
57
+ },
58
+ "reference_video": {
59
+ "type": "array",
60
+ "items": {
61
+ "type": "string"
62
+ },
63
+ "description": "Reference videos (https URL only, must be publicly reachable). Up to 3. The model borrows their subject, camera work and style. Cannot be combined with first_frame / last_frame."
64
+ },
65
+ "reference_audio": {
66
+ "type": "array",
67
+ "items": {
68
+ "type": "string"
69
+ },
70
+ "description": "Reference audio (https URL only, must be publicly reachable). Up to 3. Borrows timbre, melody or dialogue. Needs at least one reference image or video alongside it; cannot be combined with first_frame / last_frame."
71
+ },
72
+ "web_search": {
73
+ "type": "boolean",
74
+ "description": "Let the model search the web for up-to-date subjects before generating. Text-only input — ignored once any image / video / audio is attached."
75
+ },
76
+ "return_last_frame": {
77
+ "type": "boolean",
78
+ "description": "Also return the generated clip's last frame, to chain it into the next shot"
57
79
  },
58
80
  "generate_audio": {
59
81
  "type": "boolean",
@@ -65,11 +87,11 @@
65
87
  },
66
88
  "negative_prompt": {
67
89
  "type": "string",
68
- "description": "Content to steer away from (Veo)"
90
+ "description": "Content to steer away from. Veo only — Seedance ignores it (kept for when a Veo channel is available again)"
69
91
  },
70
92
  "seed": {
71
93
  "type": "number",
72
- "description": "Random seed. Pass the same seed with the same prompt and model to make a run reproducible."
94
+ "description": "Random seed, -1 for random. Pass the same seed with the same prompt and model to make a run reproducible."
73
95
  },
74
96
  "person_generation": {
75
97
  "type": "string",
@@ -93,20 +115,24 @@
93
115
  "prompt",
94
116
  "model",
95
117
  "duration",
96
- "ratio"
118
+ "ratio",
119
+ "resolution"
97
120
  ],
98
121
  "advanced": [
99
- "resolution",
100
122
  "first_frame",
101
123
  "last_frame",
102
124
  "reference",
125
+ "reference_video",
126
+ "reference_audio",
103
127
  "generate_audio",
104
128
  "camera_fixed",
105
- "negative_prompt"
129
+ "web_search",
130
+ "return_last_frame",
131
+ "seed"
106
132
  ],
107
133
  "hidden": [
108
134
  "json_output",
109
- "seed",
135
+ "negative_prompt",
110
136
  "person_generation"
111
137
  ]
112
138
  }