@remixmate/cli 0.13.1 → 0.13.3

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.13.1",
4
- "generatedAt": "2026-10-25T04:48:35.989Z",
3
+ "version": "0.13.3",
4
+ "generatedAt": "2026-10-25T14:33:26.570Z",
5
5
  "skills": [
6
6
  {
7
7
  "id": "doc-parse",
@@ -625,6 +625,7 @@
625
625
  "motion": {
626
626
  "type": "string",
627
627
  "enum": [
628
+ "auto",
628
629
  "static",
629
630
  "kenburns-in",
630
631
  "kenburns-out",
@@ -632,7 +633,7 @@
632
633
  "pan-right",
633
634
  "float"
634
635
  ],
635
- "description": "Camera motion for every scene (scenes[].animationHints.motion), replacing the skeleton's alternating push-in / pull-out. **Must** pass when the user picked one: `运镜效果: 缓慢推近(kenburns-in)` → `kenburns-in`."
636
+ "description": "Camera motion for every scene (scenes[].animationHints.motion), replacing the skeleton's alternating push-in / pull-out. **Must** pass when the user picked one: `运镜效果: 缓慢推近(kenburns-in)` → `kenburns-in`; `运镜效果: 自动搭配(auto)` → `auto` (the template varies the motion scene by scene; do not write animationHints.motion yourself then)."
636
637
  },
637
638
  "ending_payload": {
638
639
  "type": "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixmate/cli",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "AI media generation skills for Claude Code / Codex — 12 skills covering image, video, voice, digital human, web screenshot, web recording, script, template registry, rendering, Jianying export, and video deconstruction.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -489,7 +489,7 @@ python3 <SkillDir>/scripts/gen_script.py \
489
489
  | `--payload KEY=VALUE` | Template-specific card choice, repeatable. `KEY` is a dotted path in `customPayloadSchema` (or `capabilities.frameLevelKeys`), e.g. `<field>=<value>`, `<object>.<field>=<value>`. Value is converted to the declared type (true/false, numbers) and checked against enum / range; undeclared keys are rejected. Written to every scene with sibling keys kept, applied before fit-caption duration estimation. | template default |
490
490
  | `--ending-payload KEY=VALUE` | Same as `--payload`, but written only to the ending scene, e.g. `<object>.<field>=<closing line>`. On countdown templates it also appends an ending scene after #1 (it carries the #1 index; fill its narration and the item field `capabilities.countdown.itemProp` names). Errors when the script has no ending scene. | none |
491
491
  | `--avatar-id` | Digital-human avatar id (`dh_xxx`) for templates declaring `capabilities.digitalHuman`. Adds asset `asset-avatar` (`type: avatar`, `source: gen-digital-human`, `payload.avatarId`) and sets `visuals.avatar.assetRef` on every scene whose slot reads it — one continuous presenter driven by the narration. Placement: template default, or `--payload digitalHuman.<field>=…` (a photo-only presenter is `--payload digitalHuman.posterUrl=<url>` without this flag). | none |
492
- | `--motion` | Camera motion for every scene (`animationHints.motion`): `static` / `kenburns-in` / `kenburns-out` / `pan-left` / `pan-right` / `float`. | skeleton alternates in / out |
492
+ | `--motion` | Camera motion for every scene (`animationHints.motion`): `static` / `kenburns-in` / `kenburns-out` / `pan-left` / `pan-right` / `float`. `auto` removes the motion from every scene so the template picks (image-slide rotates push / pan / pull / pan); nothing is locked then. | skeleton alternates in / out |
493
493
  | `--font-id` | Font library key (`font.uniq_id`), written to `global.font.fontId`; ab-render resolves and installs the real font at render time. Pass it whenever the user picked a font — the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)`; pass the id in the parentheses. Never pass a CSS family or the display name: an unknown key falls back to the template font with no error. | template font |
494
494
  | `--font-name` | Display name of the picked font → `global.font.fontName` (display only, never used for rendering). Pass it with `--font-id`. | — |
495
495
  | `--list-fonts` | Print the live font library (`/font/page`: id, name, lang, category, license) and exit; no script is generated and `--topic` is not needed. The ids it prints are the only valid `--font-id` values — use it when the user asks which fonts exist or names a font without its id. | — |
@@ -1104,7 +1104,38 @@ def _estimate_fit_caption_duration(
1104
1104
  return max(parts) + duration_model.get("tailPaddingSec", 0)
1105
1105
 
1106
1106
 
1107
- def _estimate_fit_images_duration(custom_payload: dict, model: dict | None, fps: int = 30) -> float:
1107
+
1108
+
1109
+ _READING_UNIT_RE = re.compile(r"[\u3400-\u9fff\uf900-\ufaff]|[A-Za-z0-9]+")
1110
+
1111
+
1112
+ def _reading_units(text: str) -> int:
1113
+ return len(_READING_UNIT_RE.findall(text or ""))
1114
+
1115
+
1116
+ def _sequence_floor_frames(sequence: dict, preset_timing: dict, text: str, fps: int) -> int:
1117
+
1118
+
1119
+
1120
+
1121
+
1122
+ min_sec = sequence.get("minSec")
1123
+ if not (isinstance(min_sec, (int, float)) and min_sec > 0):
1124
+ floor = preset_timing.get("readingFloor")
1125
+ units = _reading_units(text)
1126
+ if not isinstance(floor, dict) or units == 0:
1127
+ return 0
1128
+ min_sec = floor["baseSec"] + floor["secPerUnit"] * units
1129
+ return math.floor(min_sec * fps + 0.5)
1130
+
1131
+
1132
+ def _estimate_fit_images_duration(custom_payload: dict, model: dict | None, fps: int = 30,
1133
+ headline: str = "") -> float:
1134
+
1135
+
1136
+
1137
+
1138
+
1108
1139
 
1109
1140
 
1110
1141
 
@@ -1138,7 +1169,22 @@ def _estimate_fit_images_duration(custom_payload: dict, model: dict | None, fps:
1138
1169
  return max(1, math.floor(item.get("holdSec", item["sourceDurationSec"]) * fps + 0.5))
1139
1170
  return max(min_frames, math.floor(item.get("holdSec", base_hold) * fps + 0.5))
1140
1171
 
1141
- return sum(item_frames(item) for item in sequence["items"]) / fps
1172
+ frames = [item_frames(item) for item in sequence["items"]]
1173
+ natural = sum(frames)
1174
+
1175
+ title_override = ((sequence.get("text") or {}).get("title") or {}).get("text")
1176
+ candidates = (title_override, custom_payload.get("title"),
1177
+ (custom_payload.get("templateData") or {}).get("title"), headline)
1178
+ text = next((c for c in candidates if isinstance(c, str) and c.strip()), "")
1179
+ floor_frames = _sequence_floor_frames(sequence, preset_timing, text, fps)
1180
+ if floor_frames > natural:
1181
+ if all(item.get("type") == "video" for item in sequence["items"]):
1182
+ rate = timing.get("floorMinVideoRate")
1183
+ if rate is None:
1184
+ raise ValueError(f"fit-images with a floor requires durationModel.{key}.floorMinVideoRate")
1185
+ floor_frames = min(floor_frames, sum(math.floor(f / rate) for f in frames))
1186
+ natural = max(natural, floor_frames)
1187
+ return natural / fps
1142
1188
 
1143
1189
 
1144
1190
  def _probe_sequence_video_duration(url: str) -> float | None:
@@ -1391,7 +1437,7 @@ def _build_carousel_caption_dsl(
1391
1437
 
1392
1438
  effective_duration = duration
1393
1439
  if duration_strategy == "fit-images":
1394
- effective_duration = _estimate_fit_images_duration(custom_payload, duration_model)
1440
+ effective_duration = _estimate_fit_images_duration(custom_payload, duration_model, headline=headline)
1395
1441
  if duration_strategy == "fit-caption":
1396
1442
  est = _estimate_fit_caption_duration(custom_payload, duration_model, template_id)
1397
1443
  if est > 0:
@@ -1873,18 +1919,23 @@ def _apply_scene_payload(
1873
1919
 
1874
1920
  writes = _parse_payload_specs(template_config, payload_specs)
1875
1921
  if motion:
1876
- allowed = ("static", "kenburns-in", "kenburns-out", "pan-left", "pan-right", "float")
1922
+ allowed = ("auto", "static", "kenburns-in", "kenburns-out", "pan-left", "pan-right", "float")
1877
1923
  if motion not in allowed:
1878
1924
  raise ValueError(f"--motion {motion!r}: choose one of: {', '.join(allowed)}")
1879
1925
 
1880
1926
  for scene in dsl.get("scenes", []):
1881
1927
  _write_payload(scene.setdefault("customPayload", {}), writes)
1882
- if motion:
1883
- hints = scene.get("animationHints") if isinstance(scene.get("animationHints"), dict) else {}
1884
- scene["animationHints"] = {**hints, "motion": motion}
1928
+ hints = scene.get("animationHints") if isinstance(scene.get("animationHints"), dict) else None
1929
+ if motion == "auto":
1930
+
1931
+
1932
+ if hints is not None:
1933
+ hints.pop("motion", None)
1934
+ elif motion:
1935
+ scene["animationHints"] = {**(hints or {}), "motion": motion}
1885
1936
  for parts, _ in writes:
1886
1937
  _lock_scene_path(dsl, "customPayload." + ".".join(parts))
1887
- if motion:
1938
+ if motion and motion != "auto":
1888
1939
  _lock_scene_path(dsl, "animationHints.motion")
1889
1940
 
1890
1941
 
@@ -2817,7 +2868,8 @@ Examples:
2817
2868
  parser.add_argument(
2818
2869
  "--motion", default=None,
2819
2870
  help="Camera motion for every scene (animationHints.motion): static / kenburns-in / kenburns-out / "
2820
- "pan-left / pan-right / float. Replaces the skeleton's alternating default.",
2871
+ "pan-left / pan-right / float. Replaces the skeleton's alternating default. `auto` removes the "
2872
+ "motion from every scene so the template picks (image-slide rotates push / pan / pull / pan).",
2821
2873
  )
2822
2874
  parser.add_argument("--allow-digital-human", action="store_true", help="Allow digital-human assets")
2823
2875
  parser.add_argument("--avatar-id", default=None, help="Digital-human avatar id (dh_xxx) for templates declaring capabilities.digitalHuman: adds one gen-digital-human asset referenced by every scene's visuals.avatar.assetRef (one continuous presenter driven by the narration). Placement stays the template default unless set with --payload digitalHuman.<field>=...")
@@ -83,6 +83,7 @@
83
83
  "motion": {
84
84
  "type": "string",
85
85
  "enum": [
86
+ "auto",
86
87
  "static",
87
88
  "kenburns-in",
88
89
  "kenburns-out",
@@ -90,7 +91,7 @@
90
91
  "pan-right",
91
92
  "float"
92
93
  ],
93
- "description": "Camera motion for every scene (scenes[].animationHints.motion), replacing the skeleton's alternating push-in / pull-out. **Must** pass when the user picked one: `运镜效果: 缓慢推近(kenburns-in)` → `kenburns-in`."
94
+ "description": "Camera motion for every scene (scenes[].animationHints.motion), replacing the skeleton's alternating push-in / pull-out. **Must** pass when the user picked one: `运镜效果: 缓慢推近(kenburns-in)` → `kenburns-in`; `运镜效果: 自动搭配(auto)` → `auto` (the template varies the motion scene by scene; do not write animationHints.motion yourself then)."
94
95
  },
95
96
  "ending_payload": {
96
97
  "type": "array",