@remixmate/cli 0.13.2 → 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.2",
4
- "generatedAt": "2026-10-25T07:41:14.830Z",
3
+ "version": "0.13.3",
4
+ "generatedAt": "2026-10-25T14:33:26.570Z",
5
5
  "skills": [
6
6
  {
7
7
  "id": "doc-parse",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixmate/cli",
3
- "version": "0.13.2",
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",
@@ -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: