videobook 0.3.1 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -6,6 +6,68 @@ and the package uses [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.2] - 2026-10-26
10
+
11
+ ### Added
12
+
13
+ - The KIE Grok Imagine Video 1.5 preview's 1080p tier, priced at $0.04 per
14
+ second. At 1080p it takes exactly one image (`image_urls`); 480p and 720p
15
+ still take more.
16
+
17
+ ### Changed
18
+
19
+ - Every `@apicity` package is at 0.19.0.
20
+ - The KIE Grok Imagine Video 1.5 preview defaults to 1080p, its highest
21
+ resolution, as `kie:grok-imagine/image-to-video` already does. A request
22
+ with more than one image needs 480p or 720p and is refused before
23
+ anything is spent.
24
+ - The KIE Grok Imagine Video 1.5 preview sends `nsfw_checker: false` by
25
+ default, the default KIE documents and every other KIE video model with
26
+ the setting already sends. 0.3.1 sent `true`; pass `nsfw_checker: true` to
27
+ keep KIE's content filter on.
28
+ - KIE Seedance 2.5 keeps generating audio by default. apicity 0.19.0 now
29
+ defaults `generate_audio` to `false`, but Videobook still sends `true`
30
+ unless a request turns audio off.
31
+ - MachGen estimates follow the 0.19.0 pricebook. A MiniMax H3 run with a
32
+ first and a last frame is priced at the image-to-video rate ($0.035 per
33
+ second at 480p instead of $0.05), and Wan 3.0 references that include a
34
+ video add the reference video's seconds to the output's.
35
+ - KIE OmniHuman 2.0 generations turn KIE's watermark off by default
36
+ (`enable_watermark: false`); pass `enable_watermark: true` to keep it.
37
+ OmniHuman 2.0 videos generated with 0.3.1 carry the watermark. OmniHuman
38
+ 1.5 has no watermark option.
39
+
40
+ ### Fixed
41
+
42
+ - A catalog model the installed apicity packages cannot resolve no longer
43
+ fails the whole model listing, which left every book looking empty on
44
+ load. The listing skips that model and logs a `model-unresolvable`
45
+ warning naming it.
46
+ - Queued KIE OmniHuman 1.5 and 2.0 generations pass their driving audio's
47
+ length to the cost estimate, so the KIE paygate estimate is bounded and the
48
+ usage row is priced.
49
+ - Queued KIE HappyHorse video edit and Kling 3.0 motion control generations
50
+ pass their source video's length, and Volcengine lip sync passes its audio's
51
+ length, so their paygate estimates and usage rows are priced too.
52
+ `estimate_cost` now prices Volcengine lip sync from the audio, whose length
53
+ the output follows, instead of the source video.
54
+ - The usage counter groups each catalog generation under its catalog media.
55
+ 34 KIE and fal models, among them OmniHuman, the HeyGen digital twins, the
56
+ Gemini and ElevenLabs speech models and the Bria image models, were counted
57
+ as analysis, and fal Wan 2.7 image edit as video.
58
+ - KIE Suno music and sound generations now reach the usage counter as audio,
59
+ priced at KIE's flat rate per generation: $0.06 for music and $0.0125 for
60
+ sounds. Their status polls settle the row: a lyrics-only result stays
61
+ pending, and a failed generation is not counted as spend.
62
+ - KIE generations stay pending in the usage counter until their task
63
+ settles. A queuing or generating KIE task, or a Veo task that is still
64
+ generating, was counted as settled spend at its first status poll, and a
65
+ Veo generation that failed while generating (`successFlag` 3) was counted
66
+ as completed spend.
67
+ - A Veo generation that fails while generating now fails with KIE's error
68
+ message at the next status poll. It was treated as still processing and
69
+ polled until the pending-task timeout.
70
+
9
71
  ## [0.3.1] - 2026-10-25
10
72
 
11
73
  ### Added
@@ -18,6 +18,7 @@ const EVENTS = [
18
18
  "watermark-probe-fallback", "generation-error-clear-failed", "timeline-orientation-failed",
19
19
  "generation-cost-estimate", "job-error", "semantic-index-deferred",
20
20
  "tag-asset-failed", "voice-session-closed", "alive-captions-regions-failed",
21
+ "model-unresolvable",
21
22
  ];
22
23
  const events = new Set(EVENTS);
23
24
  const errorCodes = new Set([
@@ -25,9 +26,14 @@ const errorCodes = new Set([
25
26
  "EIO", "EMFILE", "ENFILE", "ENOMEM", "ECONNREFUSED", "ECONNRESET",
26
27
  "ETIMEDOUT", "EPIPE", "ENOTFOUND", "EADDRINUSE", "EADDRNOTAVAIL", "ABORT_ERR",
27
28
  ]);
28
- const names = { tool: new Set(), jobType: new Set() };
29
+ const names = { tool: new Set(), jobType: new Set(), model: new Set() };
30
+ const NAME_PATTERNS = {
31
+ tool: /^[a-z][a-z0-9_]{0,95}$/,
32
+ jobType: /^[a-z][a-z0-9_]{0,95}$/,
33
+ model: /^[a-z][a-z0-9-]{0,31}:[a-z0-9][a-z0-9._/-]{0,159}$/,
34
+ };
29
35
  export function registerDiagnosticName(kind, name) {
30
- if (typeof name === "string" && name.trim() === name && /^[a-z][a-z0-9_]{0,95}$/.test(name))
36
+ if (typeof name === "string" && NAME_PATTERNS[kind].test(name))
31
37
  names[kind].add(name);
32
38
  }
33
39
  function ownValue(value, key) {
@@ -82,7 +88,7 @@ export function diagnosticRecord(event, fields = {}) {
82
88
  if (typeof value === "string" && ENUM_FIELDS[key].includes(value))
83
89
  result[key] = value;
84
90
  }
85
- for (const key of ["tool", "jobType"]) {
91
+ for (const key of ["tool", "jobType", "model"]) {
86
92
  const value = ownValue(fields, key);
87
93
  if (typeof value === "string" && names[key].has(value))
88
94
  result[key] = value;
@@ -5,7 +5,12 @@ import { LIGHTNING_COLD_OPEN_SECONDS, LIGHTNING_SCENE_ASPECT_RATIO, LIGHTNING_SC
5
5
  const IMAGE_LIMIT = 30;
6
6
  const VIDEO_LIMIT = 10;
7
7
  const LEGACY_SCENE_RESOLUTION = "2K";
8
+ const SCENE_MODELS = ["kie:nano-banana-2-1"];
8
9
  const SCENE_RESOLUTIONS = [LEGACY_SCENE_RESOLUTION, "4K"];
10
+ const SCENE_OUTPUT_FORMATS = ["jpg"];
11
+ const VIDEO_MODELS = ["kie:bytedance/seedance-2-5"];
12
+ const VIDEO_RESOLUTIONS = ["1080p"];
13
+ const VIDEO_OUTPUT_FORMATS = ["mp4"];
9
14
  export const goalMediaRefSchema = z.object({
10
15
  kind: z.enum(["image", "video"]),
11
16
  artifactId: z.string().min(1),
@@ -22,21 +27,21 @@ export const goalPlanV3Schema = z.object({
22
27
  scene: z.object({
23
28
  prompt: z.string().trim().min(1).max(4000),
24
29
  tool: z.literal("generate_image"),
25
- model: z.literal(LIGHTNING_SCENE_MODEL),
30
+ model: z.enum(SCENE_MODELS),
26
31
  aspectRatio: z.enum(["16:9", "9:16", "1:1"]),
27
32
  resolution: z.enum(SCENE_RESOLUTIONS),
28
- outputFormat: z.literal(LIGHTNING_SCENE_OUTPUT_FORMAT),
33
+ outputFormat: z.enum(SCENE_OUTPUT_FORMATS),
29
34
  imageRefs: z.array(goalMediaRefSchema).max(IMAGE_LIMIT),
30
35
  }).strict(),
31
36
  video: z.object({
32
37
  prompt: z.string().trim().min(1).max(4000),
33
38
  tool: z.literal("generate_video"),
34
- model: z.literal(LIGHTNING_VIDEO_MODEL),
39
+ model: z.enum(VIDEO_MODELS),
35
40
  aspectRatio: z.enum(["16:9", "9:16", "1:1", "adaptive"]),
36
- resolution: z.literal(LIGHTNING_VIDEO_RESOLUTION),
41
+ resolution: z.enum(VIDEO_RESOLUTIONS),
37
42
  durationSeconds: z.number().int().min(4).max(30),
38
43
  generateAudio: z.literal(true),
39
- outputFormat: z.literal(LIGHTNING_VIDEO_OUTPUT_FORMAT),
44
+ outputFormat: z.enum(VIDEO_OUTPUT_FORMATS),
40
45
  imageRefs: z.array(goalMediaRefSchema).max(IMAGE_LIMIT),
41
46
  videoRefs: z.array(goalMediaRefSchema).max(VIDEO_LIMIT),
42
47
  }).strict(),
@@ -2,6 +2,7 @@ import { uploadKieFile } from "../usage-providers.js";
2
2
  import { prepareAssetForUpload, uploadBytesToFreeHost, } from "../upload.js";
3
3
  import { getApiKey } from "../api-key.js";
4
4
  import { logger } from "../logger.js";
5
+ import { providerEndpoints } from "../media/endpoints.js";
5
6
  // Upload prepared bytes to Kie's native storage via POST /api/file-stream-upload.
6
7
  // Kie workers run in China — using their first-party CDN avoids the
7
8
  // InvalidParameter.DataInspection failures we'd hit when the free-host URL
@@ -12,7 +13,8 @@ async function uploadPreparedToKie(data, hashedFilename, contentType) {
12
13
  if (apiKey) {
13
14
  try {
14
15
  const blob = new Blob([new Uint8Array(data)], { type: contentType });
15
- return await uploadKieFile({ apiKey, timeout: 120_000 }, blob, hashedFilename);
16
+ const uploadBaseURL = providerEndpoints().kieUploadBaseURL;
17
+ return await uploadKieFile({ apiKey, timeout: 120_000, ...(uploadBaseURL ? { uploadBaseURL } : {}) }, blob, hashedFilename);
16
18
  }
17
19
  catch (err) {
18
20
  logger.warn("kie-upload-fallback", { error: err });
@@ -22,24 +22,33 @@ async function replaceAudioMeta(engine, ref) {
22
22
  const input = await resolveRefInput(engine, "audio", ref);
23
23
  return { replaceAudioWith: { assetId: input.assetId, filename: input.filename } };
24
24
  }
25
+ async function sourceAudioSeconds(engine, assetIds) {
26
+ for (const assetId of assetIds) {
27
+ const metadata = await engine.metadata.artifacts.read(assetId, "original");
28
+ const seconds = metadata.ok ? metadata.value.duration : undefined;
29
+ if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0)
30
+ return seconds;
31
+ }
32
+ return undefined;
33
+ }
25
34
  export async function generationSourceCostHints(engine, refs, job) {
26
35
  const draftHints = await seedanceDraftCostHints(engine, job.model, job.options ?? {});
27
36
  if (Object.keys(draftHints).length > 0)
28
37
  return draftHints;
38
+ const model = resolveModel(job.model);
39
+ if (model.entry.outputLength === "audio") {
40
+ const seconds = await sourceAudioSeconds(engine, refs.sourceAudioIds);
41
+ return seconds === undefined ? {} : { durationSeconds: seconds };
42
+ }
29
43
  const videoSeconds = await referenceVideoDuration(engine, refs.sourceVideoIds);
30
44
  if (videoSeconds !== undefined) {
31
- return { inputDurationSeconds: videoSeconds, ...(isSeedanceEdit(job.model, job.mode) ? { durationSeconds: videoSeconds } : {}) };
45
+ const followsVideo = model.entry.outputLength === "video" || isSeedanceEdit(job.model, job.mode);
46
+ return { inputDurationSeconds: videoSeconds, ...(followsVideo ? { durationSeconds: videoSeconds } : {}) };
32
47
  }
33
- if (resolveModel(job.model).provider !== "fal")
48
+ if (model.provider !== "fal")
34
49
  return {};
35
- for (const assetId of refs.sourceAudioIds) {
36
- const metadata = await engine.metadata.artifacts.read(assetId, "original");
37
- const seconds = metadata.ok ? metadata.value.duration : undefined;
38
- if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0) {
39
- return { inputDurationSeconds: seconds };
40
- }
41
- }
42
- return {};
50
+ const seconds = await sourceAudioSeconds(engine, refs.sourceAudioIds);
51
+ return seconds === undefined ? {} : { inputDurationSeconds: seconds };
43
52
  }
44
53
  function generationMeta(job, model, refs) {
45
54
  return {
@@ -174,7 +174,7 @@ export const MODEL_ENTRIES = [
174
174
  { id: "v2v_refs", label: "Edit with references", roles: ["video", "image"], requireAny: ["video"] },
175
175
  ],
176
176
  },
177
- { id: "kie:kling-3.0/motion-control", media: "video", operations: ["motion_control"], guide: "kling30", dialect: "natural", defaults: { mode: "720p" } },
177
+ { id: "kie:kling-3.0/motion-control", media: "video", operations: ["motion_control"], guide: "kling30", dialect: "natural", defaults: { mode: "720p" }, outputLength: "video" },
178
178
  { id: "kie:kling-4.0/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "kling4" },
179
179
  { id: "kie:kling-4.0/keyframe-to-video", media: "video", operations: ["image_to_video"], dialect: "kling4_keyframes", refs: { image: "keyframe_urls" } },
180
180
  { id: "kie:gemini-omni-video", media: "video", operations: ["image_to_video", "edit_video"], dialect: "natural_source_video", defaults: { duration: "8" }, modes: [{ id: "i2v", label: "Image to video", roles: ["image"] }, { id: "v2v", label: "Edit video", roles: ["video", "image"], requireAny: ["video"] }], refItem: { video: { start: 0, ends: 8 } }, aliases: { clip_start: "video_list.0.start", clip_end: "video_list.0.ends" } },
@@ -183,13 +183,13 @@ export const MODEL_ENTRIES = [
183
183
  { id: "kie:happyhorse-1-1/text-to-video", media: "video", operations: ["text_to_video"] },
184
184
  { id: "kie:happyhorse-1-1/image-to-video", media: "video", operations: ["image_to_video"], dialect: "strip_frames" },
185
185
  { id: "kie:happyhorse-1-1/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "brackets" },
186
- { id: "kie:happyhorse/video-edit", media: "video", operations: ["edit_video"], dialect: "happyhorse_edit" },
186
+ { id: "kie:happyhorse/video-edit", media: "video", operations: ["edit_video"], dialect: "happyhorse_edit", outputLength: "video" },
187
187
  { id: "kie:minimax-h3/text-to-video", media: "video", operations: ["text_to_video"] },
188
188
  { id: "kie:minimax-h3/image-to-video", media: "video", operations: ["image_to_video"], dialect: "strip_frames" },
189
189
  { id: "kie:minimax-h3/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "natural", rules: { refDurationMs: MiniMaxH3ReferenceClipDurationMs } },
190
- { id: "kie:volcengine/video-to-video-lip-sync", media: "video", operations: ["lip_sync"] },
191
- { id: "kie:omnihuman-1-5", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural" },
192
- { id: "kie:omnihuman-2-0", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural" },
190
+ { id: "kie:volcengine/video-to-video-lip-sync", media: "video", operations: ["lip_sync"], outputLength: "audio" },
191
+ { id: "kie:omnihuman-1-5", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural", outputLength: "audio" },
192
+ { id: "kie:omnihuman-2-0", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural", defaults: { enable_watermark: false }, outputLength: "audio" },
193
193
  { id: "kie:vidu-q4/image-to-video", media: "video", operations: ["image_to_video"], dialect: "natural_kinds" },
194
194
  { id: "kie:vidu-q4/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "natural_kinds" },
195
195
  { id: "kie:pixverse-v6-max/text-to-video", media: "video", operations: ["text_to_video"], defaults: { quality: "1080p" } },
@@ -139,6 +139,14 @@ function build(entry) {
139
139
  modes: entry.modes ? [...entry.modes] : defaultModes(slots),
140
140
  };
141
141
  }
142
+ export function resolveEntry(entry) {
143
+ const cached = cache.get(entry.id);
144
+ if (cached)
145
+ return cached;
146
+ const resolved = build(entry);
147
+ cache.set(entry.id, resolved);
148
+ return resolved;
149
+ }
142
150
  export function resolveModel(model) {
143
151
  const cached = cache.get(model);
144
152
  if (cached)
@@ -146,9 +154,7 @@ export function resolveModel(model) {
146
154
  const entry = modelEntry(model);
147
155
  if (!entry)
148
156
  throw new UnknownModelError(model);
149
- const resolved = build(entry);
150
- cache.set(model, resolved);
151
- return resolved;
157
+ return resolveEntry(entry);
152
158
  }
153
159
  export function tryResolveModel(model) {
154
160
  try {
@@ -1,7 +1,9 @@
1
1
  import { modelFrontierRankings } from "./frontier.js";
2
2
  import { PROVIDER_ENV_KEYS } from "./adapters/index.js";
3
3
  import { MODEL_ENTRIES } from "./catalog.js";
4
- import { resolveModel } from "./descriptor.js";
4
+ import { resolveEntry } from "./descriptor.js";
5
+ import { registerDiagnosticName } from "../diagnostics.js";
6
+ import { logger } from "../logger.js";
5
7
  export function modelIsHidden(entry) {
6
8
  return entry.hidden === true && modelFrontierRankings(entry.id).length === 0;
7
9
  }
@@ -22,10 +24,30 @@ export function modelListing(model) {
22
24
  frontier: modelFrontierRankings(model.id),
23
25
  };
24
26
  }
27
+ for (const entry of MODEL_ENTRIES)
28
+ registerDiagnosticName("model", entry.id);
29
+ const unresolvableReported = new Set();
30
+ function reportUnresolvable(entry, error) {
31
+ if (unresolvableReported.has(entry.id))
32
+ return;
33
+ unresolvableReported.add(entry.id);
34
+ logger.warn("model-unresolvable", { model: entry.id, error });
35
+ }
36
+ function resolveOrSkip(entry) {
37
+ try {
38
+ return [resolveEntry(entry)];
39
+ }
40
+ catch (error) {
41
+ reportUnresolvable(entry, error);
42
+ return [];
43
+ }
44
+ }
45
+ export function resolvableModels(entries) {
46
+ return entries.flatMap(resolveOrSkip);
47
+ }
25
48
  export function listModels(filter = {}) {
26
- return MODEL_ENTRIES
49
+ return resolvableModels(MODEL_ENTRIES
27
50
  .filter((entry) => filter.includeHidden || !modelIsHidden(entry))
28
51
  .filter((entry) => !filter.media || entry.media === filter.media)
29
- .filter((entry) => !filter.operation || entry.operations.includes(filter.operation))
30
- .map((entry) => resolveModel(entry.id));
52
+ .filter((entry) => !filter.operation || entry.operations.includes(filter.operation)));
31
53
  }
@@ -80,8 +80,8 @@ function historicalAverage(warnings) {
80
80
  }
81
81
  async function priceRequest(provider, model, payload, endpoint, hints) {
82
82
  try {
83
- const request = { provider, payload, ...(provider === "fal" ? { endpoint } : {}), ...(hints ? { costHints: hints } : {}) };
84
- const estimate = provider === "fal"
83
+ const request = { provider, payload, ...(endpoint ? { endpoint } : {}), ...(hints ? { costHints: hints } : {}) };
84
+ const estimate = provider === "fal" && endpoint
85
85
  ? await estimateFalRequest(endpoint, payload, hints)
86
86
  : cost.estimate(request);
87
87
  const historical = estimate.usd > 0 && historicalAverage(estimate.warnings);
@@ -92,7 +92,7 @@ async function priceRequest(provider, model, payload, endpoint, hints) {
92
92
  ...unknownPrice(historical ? estimate.warnings : []),
93
93
  costUsd: estimate.usd,
94
94
  basis: estimate.source === "free" ? "free" : "estimated",
95
- rateSource: rateFor(provider, model)?.source.url ?? null,
95
+ rateSource: (rateFor(provider, model) ?? (endpoint ? rateFor(provider, endpoint) : undefined))?.source.url ?? null,
96
96
  pricingVersion: `apicity-${COST_PACKAGE_VERSION}:${estimate.rateAsOf ?? (historical ? "fal-historical" : PRICING_AS_OF)}`,
97
97
  units: estimate.breakdown.units ?? null,
98
98
  unit: estimate.breakdown.unit ?? null,
@@ -102,11 +102,24 @@ async function priceRequest(provider, model, payload, endpoint, hints) {
102
102
  return unknownPrice();
103
103
  }
104
104
  }
105
- function activityFor(model, path, operation = "") {
105
+ const MEDIA_ACTIVITY = { image: "images", video: "video", audio: "audio" };
106
+ function kieSunoApiId(provider, path) {
107
+ const match = provider === "kie" ? /\/api\/v1\/generate(\/sounds)?$/u.exec(path) : null;
108
+ if (!match)
109
+ return undefined;
110
+ return match[1] ? "suno/sounds-generate" : "suno/generate";
111
+ }
112
+ function catalogMedia(provider, model, path) {
113
+ return modelEntry(provider === "fal" ? `fal:${path.slice(1)}` : `${provider}:${kieSunoApiId(provider, path) ?? model}`)?.media;
114
+ }
115
+ function activityFor(provider, model, path, operation = "") {
106
116
  if (/\/(responses|completions)$/u.test(path) && /chat|rewrite_prompt/u.test(operation))
107
117
  return "chat";
108
118
  if (/transcrib|speech.to.text|analy|describe|tag_|rename|suggest|translate|vision|image.subjects|regions|verify/iu.test(`${operation} ${path}`))
109
119
  return "analysis";
120
+ const media = catalogMedia(provider, model, path);
121
+ if (media)
122
+ return MEDIA_ACTIVITY[media];
110
123
  if (/video|veo|wan\/|kling|seedance|happyhorse|lipsync/iu.test(`${model} ${path}`))
111
124
  return "video";
112
125
  if (/audio|speech|music|sound|sfx|suno|vocal/iu.test(`${model} ${path}`))
@@ -121,7 +134,7 @@ function isBillable(provider, path, method) {
121
134
  if (provider === "openai" || provider === "xai")
122
135
  return /\/(responses|chat\/completions|images\/(generations|edits)|videos\/(generations|edits|extensions)|audio\/(speech|transcriptions|translations)|embeddings|tts|stt)$/u.test(path);
123
136
  if (provider === "kie")
124
- return /\/(createTask|veo\/(generate|extend))$/u.test(path);
137
+ return /\/(createTask|veo\/(generate|extend))$/u.test(path) || kieSunoApiId(provider, path) !== undefined;
125
138
  if (provider === "fal")
126
139
  return !/\/requests\//u.test(path)
127
140
  && (/^\/(fal-ai|alibaba)\//u.test(path) || modelEntry(`fal:${path.slice(1)}`) !== undefined);
@@ -169,7 +182,8 @@ function responseStatus(body, accepted) {
169
182
  const data = object(body.data);
170
183
  const output = object(body.output);
171
184
  const state = String(body.status ?? data.state ?? data.status ?? output.task_status ?? "").toLowerCase();
172
- if (body.error || data.failCode || (typeof body.code === "number" && body.code >= 400) || data.successFlag === 2 || ["failed", "fail", "error"].includes(state))
185
+ if (body.error || data.failCode || (typeof body.code === "number" && body.code >= 400) || data.successFlag === 2 || data.successFlag === 3
186
+ || ["failed", "fail", "error", "create_task_failed", "generate_audio_failed", "sensitive_word_error"].includes(state))
173
187
  return "failed";
174
188
  if (["cancelled", "canceled"].includes(state))
175
189
  return "cancelled";
@@ -177,7 +191,8 @@ function responseStatus(body, accepted) {
177
191
  return "uncertain";
178
192
  if (data.successFlag === 1 || ["success", "succeeded", "completed", "done"].includes(state))
179
193
  return "completed";
180
- if (["created", "pending", "queued", "in_queue", "running", "in_progress", "processing", "started", "waiting"].includes(state))
194
+ if (data.successFlag === 0
195
+ || ["created", "pending", "queued", "in_queue", "running", "in_progress", "processing", "started", "waiting", "queuing", "generating", "text_success"].includes(state))
181
196
  return "pending";
182
197
  return accepted ? "pending" : "completed";
183
198
  }
@@ -232,12 +247,12 @@ export function providerUsageFetch(provider, transport = fetch) {
232
247
  catch {
233
248
  void 0;
234
249
  }
235
- const activity = provider === "machgen" ? "video" : activityFor(model, url.pathname, context.operation);
250
+ const activity = provider === "machgen" ? "video" : activityFor(provider, model, url.pathname, context.operation);
236
251
  entry = {
237
252
  id: randomUUID(), provider, model, activity, status: "pending",
238
253
  tokenBased: /\/(responses|completions|embeddings)$/u.test(url.pathname) || rateFor(provider, model)?.kind === "tokens",
239
254
  operation: context.operation ?? null, artifactId: context.artifactId ?? null,
240
- ...await priceRequest(provider, model, payload, endpoint, hints),
255
+ ...await priceRequest(provider, model, payload, provider === "fal" ? endpoint : kieSunoApiId(provider, url.pathname), hints),
241
256
  };
242
257
  safeRecord(engine, entry);
243
258
  }
@@ -51,13 +51,18 @@ async function sourceDuration(engine, refs) {
51
51
  }
52
52
  return undefined;
53
53
  }
54
- async function mediaHints(engine, refs) {
54
+ async function mediaHints(engine, refs, model) {
55
+ const audioSeconds = () => sourceDuration(engine, (refs.audio ?? []).filter((ref) => !ref.startsWith("http")));
56
+ if (model.entry.outputLength === "audio") {
57
+ const seconds = await audioSeconds();
58
+ return seconds === undefined ? {} : { durationSeconds: seconds };
59
+ }
55
60
  const videos = (refs.video ?? []).filter((ref) => !ref.startsWith("http"));
56
61
  const videoSeconds = videos.length > 0 ? await referenceVideoDuration(engine, videos) : undefined;
57
62
  if (videoSeconds !== undefined)
58
63
  return { inputDurationSeconds: videoSeconds };
59
- const audioSeconds = await sourceDuration(engine, (refs.audio ?? []).filter((ref) => !ref.startsWith("http")));
60
- return audioSeconds === undefined ? {} : { inputDurationSeconds: audioSeconds };
64
+ const seconds = await audioSeconds();
65
+ return seconds === undefined ? {} : { inputDurationSeconds: seconds };
61
66
  }
62
67
  async function estimateMedia(engine, input) {
63
68
  const modelId = text(input.model);
@@ -71,7 +76,7 @@ async function estimateMedia(engine, input) {
71
76
  const sourceError = await checkSeedanceEditSource(engine, model, refs, input.mode);
72
77
  if (sourceError)
73
78
  return unavailable(sourceError);
74
- const hints = { ...await mediaHints(engine, refs), ...await seedanceDraftCostHints(engine, model.id, options) };
79
+ const hints = { ...await mediaHints(engine, refs, model), ...await seedanceDraftCostHints(engine, model.id, options) };
75
80
  const result = await estimateGeneration(model, {
76
81
  prompt: text(input.prompt),
77
82
  refUrls: placeholderRefs(refs),
@@ -778,7 +778,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
778
778
  try {
779
779
  const recordInfo = await provider.veo.get.api.v1.veo.recordInfo(task.taskId);
780
780
  const data = recordInfo.data;
781
- // Veo successFlag: 0=submitted/pending, 1=success, 2=fail, 3=processing
781
+ // Veo successFlag: 0=generating, 1=success, 2=task creation failed, 3=generation failed
782
782
  if (data?.successFlag === 1) {
783
783
  const urls = data.response?.resultUrls ?? [];
784
784
  if (urls.length === 0) {
@@ -806,7 +806,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
806
806
  });
807
807
  result.completed.push({ assetId: task.artifactId, taskType: task.taskType, jobId });
808
808
  }
809
- else if (data?.successFlag === 2) {
809
+ else if (data?.successFlag === 2 || data?.successFlag === 3) {
810
810
  const failMsg = data.errorMessage ?? "Veo task failed";
811
811
  result.failed.push({ assetId: task.artifactId, taskType: task.taskType, failMsg, failedAt: new Date().toISOString(), prompt });
812
812
  await failPendingTaskAndLog(engine, task.artifactId, task.taskId, { message: failMsg, prompt });
@@ -816,7 +816,7 @@ export async function pollPendingProviderTasks(engine, book_id, options = {}) {
816
816
  assetId: task.artifactId,
817
817
  taskType: task.taskType,
818
818
  progress: 0,
819
- state: data?.successFlag === 3 ? "processing" : "waiting",
819
+ state: "waiting",
820
820
  prompt,
821
821
  startedAt: task.createdAt,
822
822
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "videobook",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Local-first MCP server and web UI for AI video creation: notebooks, generation, editing, rendering and publishing.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -36,20 +36,20 @@
36
36
  "start": "node dist/index.js"
37
37
  },
38
38
  "dependencies": {
39
- "@apicity/alibaba": "^0.18.1",
40
- "@apicity/b2": "^0.18.1",
41
- "@apicity/cost": "^0.18.1",
42
- "@apicity/dropbox": "^0.18.1",
43
- "@apicity/elevenlabs": "^0.18.1",
44
- "@apicity/fal": "^0.18.1",
45
- "@apicity/free-media-upload": "^0.18.1",
46
- "@apicity/har": "^0.18.1",
47
- "@apicity/kie": "^0.18.1",
48
- "@apicity/kimicoding": "^0.18.1",
49
- "@apicity/machgen": "^0.18.1",
50
- "@apicity/openai": "^0.18.1",
51
- "@apicity/x": "^0.18.1",
52
- "@apicity/xai": "^0.18.1",
39
+ "@apicity/alibaba": "^0.19.0",
40
+ "@apicity/b2": "^0.19.0",
41
+ "@apicity/cost": "^0.19.0",
42
+ "@apicity/dropbox": "^0.19.0",
43
+ "@apicity/elevenlabs": "^0.19.0",
44
+ "@apicity/fal": "^0.19.0",
45
+ "@apicity/free-media-upload": "^0.19.0",
46
+ "@apicity/har": "^0.19.0",
47
+ "@apicity/kie": "^0.19.0",
48
+ "@apicity/kimicoding": "^0.19.0",
49
+ "@apicity/machgen": "^0.19.0",
50
+ "@apicity/openai": "^0.19.0",
51
+ "@apicity/x": "^0.19.0",
52
+ "@apicity/xai": "^0.19.0",
53
53
  "@devmehq/open-graph-extractor": "^1.1.1",
54
54
  "@ffprobe-installer/ffprobe": "2.1.2",
55
55
  "@modelcontextprotocol/client": "^2.2.0",
@@ -1 +1 @@
1
- import{c as F,j as t,a as A}from"./vendor-react-Dj7D1fD4.js";import{u as be,a as pe,V as Se,A as xe,P as Ne}from"./vendor-remotion-CH1HXrlc.js";import{ei as Ce,ej as ke,ek as we,el as $e,em as Pe,en as de,eo as Te,ep as me,eq as Re,er as Ae,es as Me,et as _e,eu as Ee,ev as Oe,ew as fe,by as We}from"./videobook-b9VODNOA.js";import{g as Le,a6 as ze,_ as Fe,h as He,X as Ie}from"./vendor-icons-D3KrGMJY.js";import"./vendor-BWf-WCsY.js";import"./vendor-mcp-BSR9acpG.js";const Be={face:"#63c7d8",text:"#f0a860",graphic:"#f0a860"};function De(a){const e=F.c(25),{phrase:s,cue:n,settings:o,sourceWidth:c,sourceHeight:l}=a,{width:i,height:h}=pe();let r,f,m,d;if(e[0]!==h||e[1]!==s.id||e[2]!==s.regions||e[3]!==o||e[4]!==l||e[5]!==c||e[6]!==i){const j={sourceWidth:c,sourceHeight:l,targetWidth:i,targetHeight:h};let v;e[11]!==s.regions||e[12]!==o?(v=new Set($e(s.regions,o)),e[11]=s.regions,e[12]=o,e[13]=v):v=e[13];const C=v;let y;e[14]!==i?(y=Math.round(i/480),e[14]=i,e[15]=y):y=e[15],f=Math.max(2,y),r=xe,e[16]===Symbol.for("react.memo_cache_sentinel")?(m={pointerEvents:"none"},e[16]=m):m=e[16],d=s.regions.map((w,$)=>{const b=Pe(w,j);return t.jsx("div",{style:{position:"absolute",left:b.x*i,top:b.y*h,width:b.width*i,height:b.height*h,border:`${f}px dashed ${Be[w.kind]}`,opacity:C.has(w)?1:.4,borderRadius:f*2}},`${s.id}-${$}`)}),e[0]=h,e[1]=s.id,e[2]=s.regions,e[3]=o,e[4]=l,e[5]=c,e[6]=i,e[7]=r,e[8]=f,e[9]=m,e[10]=d}else r=e[7],f=e[8],m=e[9],d=e[10];let g;e[17]!==n.column||e[18]!==f?(g=t.jsx(Ue,{column:n.column,stroke:f}),e[17]=n.column,e[18]=f,e[19]=g):g=e[19];let u;return e[20]!==r||e[21]!==m||e[22]!==d||e[23]!==g?(u=t.jsxs(r,{style:m,children:[d,g]}),e[20]=r,e[21]=m,e[22]=d,e[23]=g,e[24]=u):u=e[24],u}function Ue(a){const e=F.c(7),{column:s,stroke:n}=a,o=`${Math.max(1,n/2)}px dashed rgba(114, 231, 170, 0.6)`,c=n*2;let l;return e[0]!==s.height||e[1]!==s.width||e[2]!==s.x||e[3]!==s.y||e[4]!==o||e[5]!==c?(l=t.jsx("div",{style:{position:"absolute",left:s.x,top:s.y,width:s.width,height:s.height,border:o,borderRadius:c}}),e[0]=s.height,e[1]=s.width,e[2]=s.x,e[3]=s.y,e[4]=o,e[5]=c,e[6]=l):l=e[6],l}function Ke(a){const e=F.c(34),{mediaUrl:s,sourceWidth:n,sourceHeight:o,cues:c,phrases:l,settings:i,showRegions:h}=a,r=be(),{fps:f,width:m,height:d}=pe();let g;e[0]!==d||e[1]!==o||e[2]!==n||e[3]!==m?(g=ke({sourceWidth:n,sourceHeight:o,targetWidth:m,targetHeight:d}),e[0]=d,e[1]=o,e[2]=n,e[3]=m,e[4]=g):g=e[4];const u=g,j=r/f;let v;e[5]!==c||e[6]!==j?(v=Ce(c,j),e[5]=c,e[6]=j,e[7]=v):v=e[7];const C=v;let y;e[8]!==C||e[9]!==l?(y=C?l.find(M=>M.id===C.id):void 0,e[8]=C,e[9]=l,e[10]=y):y=e[10];const w=y;let $;e[11]===Symbol.for("react.memo_cache_sentinel")?($={background:"#000000"},e[11]=$):$=e[11];let b;e[12]!==u.height||e[13]!==u.width||e[14]!==u.x||e[15]!==u.y?(b={position:"absolute",left:u.x,top:u.y,width:u.width,height:u.height},e[12]=u.height,e[13]=u.width,e[14]=u.x,e[15]=u.y,e[16]=b):b=e[16];let N;e[17]!==s||e[18]!==b?(N=t.jsx(Se,{src:s,style:b}),e[17]=s,e[18]=b,e[19]=N):N=e[19];let P;e[20]!==C||e[21]!==w||e[22]!==i||e[23]!==h||e[24]!==o||e[25]!==n?(P=h&&C&&w&&t.jsx(De,{phrase:w,cue:C,settings:i,sourceWidth:n,sourceHeight:o}),e[20]=C,e[21]=w,e[22]=i,e[23]=h,e[24]=o,e[25]=n,e[26]=P):P=e[26];let p;e[27]!==c||e[28]!==i?(p=t.jsx(we,{cues:c,settings:i,strokeWidth:6,strokeColor:"#000000",shadowBlur:6,shadowOpacity:60}),e[27]=c,e[28]=i,e[29]=p):p=e[29];let k;return e[30]!==N||e[31]!==P||e[32]!==p?(k=t.jsxs(xe,{style:$,children:[N,P,p]}),e[30]=N,e[31]=P,e[32]=p,e[33]=k):k=e[33],k}const ue=[{id:"stack",label:"Stack",hint:"Words land one by one and build a stacked block beside the subject, each line sized to fill the column."},{id:"punch",label:"Punch",hint:"One word at a time, as big as the free space allows. Best on fast verses."},{id:"karaoke",label:"Karaoke",hint:"The whole stacked phrase shows dimmed and lights up word by word."}],Ve=[{id:"pop",label:"Pop"},{id:"slam",label:"Slam"},{id:"rise",label:"Rise"}],qe=[{name:"Green",color:"#72E7AA"},{name:"Pink",color:"#F0B9D0"},{name:"Cyan",color:"#63C7D8"},{name:"Amber",color:"#E7C372"}];function Ge(a,e){if(!a.drifted)return de(a.start);const s=a.driftMs>0?"+":"";return e?`${de(a.start)} · snapped`:`${de(a.start)} · ${s}${a.driftMs}ms`}function Xe(a){const e=F.c(21),{rows:s,phraseLabels:n,activePhrase:o,time:c,snapped:l,onSeek:i}=a;let h;e[0]===Symbol.for("react.memo_cache_sentinel")?(h=t.jsx("h3",{children:"Transcript"}),e[0]=h):h=e[0];let r;e[1]!==s.length?(r=t.jsxs("header",{className:"alive-panel-header",children:[h,t.jsxs("p",{children:[s.length," words · timed to the vocal"]})]}),e[1]=s.length,e[2]=r):r=e[2];let f;if(e[3]!==o||e[4]!==i||e[5]!==n||e[6]!==s||e[7]!==l||e[8]!==c){let g;e[10]!==o||e[11]!==i||e[12]!==s||e[13]!==l||e[14]!==c?(g=(u,j)=>t.jsxs("div",{className:"alive-phrase","data-active":j===o||void 0,children:[t.jsxs("div",{className:"alive-phrase-head",children:[t.jsxs("span",{children:["Phrase ",j+1]}),t.jsx("span",{children:u})]}),t.jsx("div",{className:"alive-phrase-words",children:s.filter(v=>v.phraseIndex===j).map(v=>t.jsxs("button",{type:"button",className:"alive-word","data-active":c>=v.start&&c<v.end||void 0,"data-drifted":v.drifted&&!l||void 0,onClick:()=>i(v.start+.03),children:[t.jsx("span",{children:v.text}),t.jsx("small",{children:Ge(v,l)})]},v.key))})]},`${j}-${u}`),e[10]=o,e[11]=i,e[12]=s,e[13]=l,e[14]=c,e[15]=g):g=e[15],f=n.map(g),e[3]=o,e[4]=i,e[5]=n,e[6]=s,e[7]=l,e[8]=c,e[9]=f}else f=e[9];let m;e[16]!==f?(m=t.jsx("div",{className:"alive-transcript-list",children:f}),e[16]=f,e[17]=m):m=e[17];let d;return e[18]!==r||e[19]!==m?(d=t.jsxs("section",{className:"alive-panel alive-transcript","aria-label":"Transcript",children:[r,m]}),e[18]=r,e[19]=m,e[20]=d):d=e[20],d}function ve(a){const e=F.c(10),{label:s,options:n,value:o,onChange:c}=a;let l;if(e[0]!==c||e[1]!==n||e[2]!==o){let h;e[4]!==c||e[5]!==o?(h=r=>t.jsx("button",{type:"button","aria-pressed":r.id===o,onClick:()=>c(r.id),children:r.label},r.id),e[4]=c,e[5]=o,e[6]=h):h=e[6],l=n.map(h),e[0]=c,e[1]=n,e[2]=o,e[3]=l}else l=e[3];let i;return e[7]!==s||e[8]!==l?(i=t.jsx("div",{className:"alive-segmented",role:"group","aria-label":s,children:l}),e[7]=s,e[8]=l,e[9]=i):i=e[9],i}function re(a){const e=F.c(8),{checked:s,onChange:n,children:o}=a;let c;e[0]!==n?(c=h=>n(h.target.checked),e[0]=n,e[1]=c):c=e[1];let l;e[2]!==s||e[3]!==c?(l=t.jsx("input",{type:"checkbox",checked:s,onChange:c}),e[2]=s,e[3]=c,e[4]=l):l=e[4];let i;return e[5]!==o||e[6]!==l?(i=t.jsxs("label",{className:"alive-toggle",children:[l,o]}),e[5]=o,e[6]=l,e[7]=i):i=e[7],i}function Ye(a){const e=F.c(69),{settings:s,showRegions:n,regionsAnalyzed:o,working:c,onChange:l,onShowRegions:i,onReanalyze:h}=a;let r;e[0]!==s.style?(r=ue.find(S=>S.id===s.style)?.hint,e[0]=s.style,e[1]=r):r=e[1];const f=r;let m;e[2]===Symbol.for("react.memo_cache_sentinel")?(m=t.jsx("h4",{children:"Style"}),e[2]=m):m=e[2];let d;e[3]!==l?(d=S=>l({style:S}),e[3]=l,e[4]=d):d=e[4];let g;e[5]!==s.style||e[6]!==d?(g=t.jsx(ve,{label:"Caption style",options:ue,value:s.style,onChange:d}),e[5]=s.style,e[6]=d,e[7]=g):g=e[7];let u;e[8]!==f?(u=t.jsx("p",{className:"alive-hint",children:f}),e[8]=f,e[9]=u):u=e[9];let j;e[10]!==g||e[11]!==u?(j=t.jsxs("div",{className:"alive-field",children:[m,g,u]}),e[10]=g,e[11]=u,e[12]=j):j=e[12];let v;e[13]===Symbol.for("react.memo_cache_sentinel")?(v=t.jsx("h4",{children:"Word entrance"}),e[13]=v):v=e[13];let C;e[14]!==l?(C=S=>l({motion:S}),e[14]=l,e[15]=C):C=e[15];let y;e[16]!==s.motion||e[17]!==C?(y=t.jsxs("div",{className:"alive-field",children:[v,t.jsx(ve,{label:"Word entrance",options:Ve,value:s.motion,onChange:C})]}),e[16]=s.motion,e[17]=C,e[18]=y):y=e[18];let w;e[19]===Symbol.for("react.memo_cache_sentinel")?(w=t.jsx("h4",{children:"Frame awareness"}),e[19]=w):w=e[19];let $;e[20]!==l?($=S=>l({avoidFaces:S}),e[20]=l,e[21]=$):$=e[21];let b;e[22]!==s.avoidFaces||e[23]!==$?(b=t.jsx(re,{checked:s.avoidFaces,onChange:$,children:"Keep words off faces"}),e[22]=s.avoidFaces,e[23]=$,e[24]=b):b=e[24];let N;e[25]!==l?(N=S=>l({avoidText:S}),e[25]=l,e[26]=N):N=e[26];let P;e[27]!==s.avoidText||e[28]!==N?(P=t.jsx(re,{checked:s.avoidText,onChange:N,children:"Keep words off on-screen text"}),e[27]=s.avoidText,e[28]=N,e[29]=P):P=e[29];let p;e[30]!==i||e[31]!==n?(p=t.jsx(re,{checked:n,onChange:i,children:"Show detected regions"}),e[30]=i,e[31]=n,e[32]=p):p=e[32];let k;e[33]!==o?(k=!o&&t.jsx("p",{className:"alive-hint alive-warning",children:"Face and text detection did not run, so stacks use the default columns. Set OPENAI_API_KEY and re-run the analysis."}),e[33]=o,e[34]=k):k=e[34];let M;e[35]!==b||e[36]!==P||e[37]!==p||e[38]!==k?(M=t.jsxs("div",{className:"alive-field",children:[w,b,P,p,k]}),e[35]=b,e[36]=P,e[37]=p,e[38]=k,e[39]=M):M=e[39];let D;e[40]===Symbol.for("react.memo_cache_sentinel")?(D=t.jsx("h4",{children:"Audio"}),e[40]=D):D=e[40];let O;e[41]!==l?(O=S=>l({energySizing:S}),e[41]=l,e[42]=O):O=e[42];let _;e[43]!==s.energySizing||e[44]!==O?(_=t.jsxs("div",{className:"alive-field",children:[D,t.jsx(re,{checked:s.energySizing,onChange:O,children:"Size words by vocal energy"})]}),e[43]=s.energySizing,e[44]=O,e[45]=_):_=e[45];let H;e[46]===Symbol.for("react.memo_cache_sentinel")?(H=t.jsx("h4",{children:"Hit color"}),e[46]=H):H=e[46];let W;e[47]!==l||e[48]!==s.hitColor?(W=qe.map(S=>t.jsx("button",{type:"button","aria-label":S.name,"aria-pressed":s.hitColor.toUpperCase()===S.color,onClick:()=>l({hitColor:S.color}),children:t.jsx("span",{style:{background:S.color}})},S.color)),e[47]=l,e[48]=s.hitColor,e[49]=W):W=e[49];let R;e[50]===Symbol.for("react.memo_cache_sentinel")?(R=t.jsx("span",{children:"Text"}),e[50]=R):R=e[50];let x;e[51]!==l?(x=S=>l({textColor:S.target.value.toUpperCase()}),e[51]=l,e[52]=x):x=e[52];let L;e[53]!==s.textColor||e[54]!==x?(L=t.jsxs("label",{className:"alive-color-input",children:[R,t.jsx("input",{type:"color",value:s.textColor,onChange:x})]}),e[53]=s.textColor,e[54]=x,e[55]=L):L=e[55];let I;e[56]!==W||e[57]!==L?(I=t.jsxs("div",{className:"alive-field",children:[H,t.jsxs("div",{className:"alive-swatches",children:[W,L]})]}),e[56]=W,e[57]=L,e[58]=I):I=e[58];let z;e[59]!==h||e[60]!==c?(z=t.jsx("div",{className:"alive-field",children:t.jsx("button",{type:"button",className:"alive-secondary",disabled:c,onClick:h,children:"Re-run analysis"})}),e[59]=h,e[60]=c,e[61]=z):z=e[61];let B;return e[62]!==M||e[63]!==_||e[64]!==I||e[65]!==z||e[66]!==j||e[67]!==y?(B=t.jsxs("section",{className:"alive-panel alive-settings","aria-label":"Caption settings",children:[j,y,M,_,I,z]}),e[62]=M,e[63]=_,e[64]=I,e[65]=z,e[66]=j,e[67]=y,e[68]=B):B=e[68],B}const Je=90;function U(a,e){return`${Math.max(0,a)/e*100}%`}function Qe(a,e,s){if(e.count===0)return`All ${s} words already sit on their vocal onsets`;if(a)return`${e.count} words snapped to their vocal onsets`;const n=e.minMs===e.maxMs?`${e.maxMs}`:`${e.minMs}–${e.maxMs}`;return`${e.count} words start ${n} ms off the vocal`}function Ze(a){const e=a>120?10:a>30?5:1,s=[];for(let n=0;n<=a;n+=e)s.push(n);return s}function et(a,e,s){const n=F.c(5),o=A.useRef(null);let c,l;return n[0]!==e||n[1]!==s||n[2]!==a?(c=()=>{const i=o.current;if(!i||!s)return;const h=a/e*i.scrollWidth;(h<i.scrollLeft||h>i.scrollLeft+i.clientWidth*.85)&&(i.scrollLeft=Math.max(0,h-i.clientWidth*.2))},l=[a,e,s],n[0]=e,n[1]=s,n[2]=a,n[3]=c,n[4]=l):(c=n[3],l=n[4]),A.useEffect(c,l),o}function tt(a){const e=F.c(105),{duration:s,fps:n,time:o,playing:c,envelope:l,rows:i,cues:h,activeCue:r,snapped:f,drift:m}=a,d=Math.max(s,.1),g=et(o,d,c);let u;e[0]!==a||e[1]!==d?(u=T=>{const E=T.currentTarget.getBoundingClientRect();a.onSeek((T.clientX-E.left)/E.width*d)},e[0]=a,e[1]=d,e[2]=u):u=e[2];const j=u;let v;e[3]!==a?(v=()=>a.onStep(-1),e[3]=a,e[4]=v):v=e[4];let C;e[5]===Symbol.for("react.memo_cache_sentinel")?(C=t.jsx(Le,{size:16}),e[5]=C):C=e[5];let y;e[6]!==v?(y=t.jsx("button",{type:"button","aria-label":"Previous frame",onClick:v,children:C}),e[6]=v,e[7]=y):y=e[7];const w=c?"Pause":"Play";let $;e[8]!==c?($=c?t.jsx(ze,{size:16}):t.jsx(Fe,{size:16}),e[8]=c,e[9]=$):$=e[9];let b;e[10]!==a.onTogglePlay||e[11]!==w||e[12]!==$?(b=t.jsx("button",{type:"button",className:"alive-play","aria-label":w,onClick:a.onTogglePlay,children:$}),e[10]=a.onTogglePlay,e[11]=w,e[12]=$,e[13]=b):b=e[13];let N;e[14]!==a?(N=()=>a.onStep(1),e[14]=a,e[15]=N):N=e[15];let P;e[16]===Symbol.for("react.memo_cache_sentinel")?(P=t.jsx(He,{size:16}),e[16]=P):P=e[16];let p;e[17]!==N?(p=t.jsx("button",{type:"button","aria-label":"Next frame",onClick:N,children:P}),e[17]=N,e[18]=p):p=e[18];let k;e[19]!==o?(k=de(o),e[19]=o,e[20]=k):k=e[20];let M;e[21]!==k?(M=t.jsx("strong",{className:"alive-clock",children:k}),e[21]=k,e[22]=M):M=e[22];const D=Math.floor(o*n),O=Math.floor(d*n);let _;e[23]!==D||e[24]!==O?(_=t.jsxs("span",{className:"alive-muted",children:["frame ",D," / ",O]}),e[23]=D,e[24]=O,e[25]=_):_=e[25];let H;e[26]===Symbol.for("react.memo_cache_sentinel")?(H=t.jsx("span",{className:"alive-spacer"}),e[26]=H):H=e[26];const W=m.count>0&&!f||void 0;let R;e[27]!==m||e[28]!==i.length||e[29]!==f?(R=Qe(f,m,i.length),e[27]=m,e[28]=i.length,e[29]=f,e[30]=R):R=e[30];let x;e[31]!==W||e[32]!==R?(x=t.jsx("span",{className:"alive-drift","data-drifted":W,children:R}),e[31]=W,e[32]=R,e[33]=x):x=e[33];const L=f?"alive-secondary":"alive-primary alive-warn",I=m.count===0,z=f?"Use transcript timing":"Snap words to audio";let B;e[34]!==a.onToggleSnap||e[35]!==L||e[36]!==I||e[37]!==z?(B=t.jsx("button",{type:"button",className:L,disabled:I,onClick:a.onToggleSnap,children:z}),e[34]=a.onToggleSnap,e[35]=L,e[36]=I,e[37]=z,e[38]=B):B=e[38];let S;e[39]!==M||e[40]!==_||e[41]!==x||e[42]!==B||e[43]!==y||e[44]!==b||e[45]!==p?(S=t.jsxs("div",{className:"alive-transport",children:[y,b,p,M,_,H,x,B]}),e[39]=M,e[40]=_,e[41]=x,e[42]=B,e[43]=y,e[44]=b,e[45]=p,e[46]=S):S=e[46];let ne;e[47]===Symbol.for("react.memo_cache_sentinel")?(ne=t.jsxs("div",{className:"alive-lane-labels","aria-hidden":"true",children:[t.jsx("span",{className:"alive-lane-ruler"}),t.jsx("span",{className:"alive-lane-audio",children:"Audio"}),t.jsx("span",{className:"alive-lane-words",children:"Words"}),t.jsx("span",{className:"alive-lane-stacks",children:"Stacks"})]}),e[47]=ne):ne=e[47];const he=d*Je;let K;e[48]!==he?(K={minWidth:he},e[48]=he,e[49]=K):K=e[49];let V;e[50]!==d?(V=Ze(d).map(T=>t.jsxs("span",{style:{left:U(T,d)},children:[T,"s"]},T)),e[50]=d,e[51]=V):V=e[51];let q;e[52]!==V?(q=t.jsx("div",{className:"alive-lane-ruler",children:V}),e[52]=V,e[53]=q):q=e[53];let G;e[54]!==l?(G=Te(l),e[54]=l,e[55]=G):G=e[55];let X;e[56]!==G?(X=t.jsx("svg",{viewBox:"0 0 1000 100",preserveAspectRatio:"none","aria-hidden":"true",children:t.jsx("path",{d:G})}),e[56]=G,e[57]=X):X=e[57];let Y;e[58]!==i||e[59]!==d||e[60]!==f?(Y=!f&&i.filter(st).map(T=>t.jsx("span",{className:"alive-onset",style:{left:U(T.onset,d)}},T.key)),e[58]=i,e[59]=d,e[60]=f,e[61]=Y):Y=e[61];let J;e[62]!==X||e[63]!==Y?(J=t.jsxs("div",{className:"alive-lane-audio",children:[X,Y]}),e[62]=X,e[63]=Y,e[64]=J):J=e[64];let Q;if(e[65]!==i||e[66]!==d||e[67]!==f||e[68]!==o){let T;e[70]!==d||e[71]!==f||e[72]!==o?(T=E=>t.jsx("span",{className:"alive-chip","data-active":o>=E.start&&o<E.end||void 0,"data-drifted":E.drifted&&!f||void 0,style:{left:U(E.start,d),width:U(E.end-E.start,d)},children:E.text},E.key),e[70]=d,e[71]=f,e[72]=o,e[73]=T):T=e[73],Q=i.map(T),e[65]=i,e[66]=d,e[67]=f,e[68]=o,e[69]=Q}else Q=e[69];let Z;e[74]!==Q?(Z=t.jsx("div",{className:"alive-lane-words",children:Q}),e[74]=Q,e[75]=Z):Z=e[75];let ee;if(e[76]!==r||e[77]!==h||e[78]!==d){let T;e[80]!==r||e[81]!==d?(T=(E,ye)=>t.jsx("span",{className:"alive-stack-bar","data-active":ye===r||void 0,style:{left:U(E.start,d),width:U(E.end-E.start,d)},children:me(E)},E.id),e[80]=r,e[81]=d,e[82]=T):T=e[82],ee=h.map(T),e[76]=r,e[77]=h,e[78]=d,e[79]=ee}else ee=e[79];let te;e[83]!==ee?(te=t.jsx("div",{className:"alive-lane-stacks",children:ee}),e[83]=ee,e[84]=te):te=e[84];let se;e[85]!==d||e[86]!==o?(se=U(o,d),e[85]=d,e[86]=o,e[87]=se):se=e[87];let le;e[88]!==se?(le=t.jsx("span",{className:"alive-playhead",style:{left:se}}),e[88]=se,e[89]=le):le=e[89];let ae;e[90]!==j||e[91]!==K||e[92]!==q||e[93]!==J||e[94]!==Z||e[95]!==te||e[96]!==le?(ae=t.jsxs("div",{className:"alive-lane-tracks",style:K,onClick:j,role:"presentation",children:[q,J,Z,te,le]}),e[90]=j,e[91]=K,e[92]=q,e[93]=J,e[94]=Z,e[95]=te,e[96]=le,e[97]=ae):ae=e[97];let ie;e[98]!==g||e[99]!==ae?(ie=t.jsxs("div",{className:"alive-lanes",children:[ne,t.jsx("div",{className:"alive-lane-scroll",ref:g,children:ae})]}),e[98]=g,e[99]=ae,e[100]=ie):ie=e[100];let oe;e[101]===Symbol.for("react.memo_cache_sentinel")?(oe=t.jsxs("div",{className:"alive-legend","aria-hidden":"true",children:[t.jsxs("span",{children:[t.jsx("i",{className:"alive-legend-chip"}),"Transcript word"]}),t.jsxs("span",{children:[t.jsx("i",{className:"alive-legend-drift"}),"Word off its vocal onset"]}),t.jsxs("span",{children:[t.jsx("i",{className:"alive-legend-onset"}),"Detected vocal onset"]})]}),e[101]=oe):oe=e[101];let ce;return e[102]!==S||e[103]!==ie?(ce=t.jsxs("section",{className:"alive-panel alive-timeline","aria-label":"Caption timeline",children:[S,ie,oe]}),e[102]=S,e[103]=ie,e[104]=ce):ce=e[104],ce}function st(a){return a.drifted}const ge=30;function lt(a){const e=F.c(3),s=A.useRef(null);let n,o;return e[0]!==a?(n=()=>{const c=s.current;return c?.showModal(),c?.querySelector(".alive-close")?.focus(),()=>{c?.close(),We(a)}},o=[a],e[0]=a,e[1]=n,e[2]=o):(n=e[1],o=e[2]),A.useEffect(n,o),s}function at(a){const e=F.c(6),[s,n]=A.useState(0),[o,c]=A.useState(!1);let l,i;e[0]!==a?(l=()=>{const r=a.current;if(!r)return;const f=g=>n(g.detail.frame),m=()=>c(!0),d=()=>c(!1);return r.addEventListener("frameupdate",f),r.addEventListener("play",m),r.addEventListener("pause",d),()=>{r.removeEventListener("frameupdate",f),r.removeEventListener("play",m),r.removeEventListener("pause",d)}},i=[a],e[0]=a,e[1]=l,e[2]=i):(l=e[1],i=e[2]),A.useEffect(l,i);let h;return e[3]!==s||e[4]!==o?(h={frame:s,playing:o},e[3]=s,e[4]=o,e[5]=h):h=e[5],h}function ft(a){const e=F.c(14),{asset:s,state:n,returnFocus:o,onClose:c}=a,l=lt(o),i=`Alive captions for ${s.label}`;let h;e[0]!==c?(h=m=>{m.preventDefault(),c()},e[0]=c,e[1]=h):h=e[1];let r;e[2]!==s.label||e[3]!==c||e[4]!==a||e[5]!==n.data||e[6]!==n.message||e[7]!==n.status?(r=n.status==="ready"?t.jsx(it,{...a,data:n.data}):t.jsxs("div",{className:"alive-studio-message",children:[t.jsx(je,{label:s.label,detail:null,onClose:c}),t.jsx("p",{role:n.status==="error"?"alert":"status",children:n.status==="error"?n.message:"Analysing vocals, phrases and faces…"})]}),e[2]=s.label,e[3]=c,e[4]=a,e[5]=n.data,e[6]=n.message,e[7]=n.status,e[8]=r):r=e[8];let f;return e[9]!==l||e[10]!==i||e[11]!==h||e[12]!==r?(f=t.jsx("dialog",{className:"alive-studio","aria-label":i,onCancel:h,ref:l,children:r}),e[9]=l,e[10]=i,e[11]=h,e[12]=r,e[13]=f):f=e[13],f}function je(a){const e=F.c(12),{label:s,detail:n,onClose:o,children:c}=a;let l;e[0]===Symbol.for("react.memo_cache_sentinel")?(l=t.jsx("h2",{children:"Alive captions"}),e[0]=l):l=e[0];const i=n?` · ${n}`:"";let h;e[1]!==s||e[2]!==i?(h=t.jsxs("span",{className:"alive-muted",children:[s,i]}),e[1]=s,e[2]=i,e[3]=h):h=e[3];let r;e[4]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx("span",{className:"alive-spacer"}),e[4]=r):r=e[4];let f;e[5]===Symbol.for("react.memo_cache_sentinel")?(f=t.jsx(Ie,{size:16}),e[5]=f):f=e[5];let m;e[6]!==o?(m=t.jsx("button",{type:"button",className:"alive-close","aria-label":"Close alive captions",onClick:o,children:f}),e[6]=o,e[7]=m):m=e[7];let d;return e[8]!==c||e[9]!==h||e[10]!==m?(d=t.jsxs("header",{className:"alive-header",children:[l,h,r,c,m]}),e[8]=c,e[9]=h,e[10]=m,e[11]=d):d=e[11],d}function it({asset:a,data:e,onClose:s,onSave:n,onRender:o,onReanalyze:c}){const{analysis:l}=e,i=Math.round(a.fps??ge)||ge,h=Math.max(1,Math.round(l.durationSeconds*i)),[r,f]=A.useState(e.settings),[m,d]=A.useState(()=>Re(a.width,a.height)),[g,u]=A.useState(!0),[j,v]=A.useState(null),[C,y]=A.useState(null),w=A.useRef(null),{frame:$,playing:b}=at(w),N=$/i,P=A.useMemo(()=>Ae(l,m),[l,m]),p=A.useMemo(()=>Me(l.phrases,r,P),[l.phrases,r,P]),k=A.useMemo(()=>_e(l,r),[l,r]),M=A.useMemo(()=>Ee(k),[k]),D=A.useMemo(()=>({mediaUrl:a.mediaUrl,sourceWidth:l.sourceWidth,sourceHeight:l.sourceHeight,cues:p,phrases:l.phrases,settings:r,showRegions:g}),[a.mediaUrl,l,p,r,g]),O=Oe(p,N),_=x=>w.current?.seekTo(Math.max(0,Math.min(h-1,Math.round(x*i)))),H=x=>f(L=>({...L,...x})),W=async(x,L,I)=>{v(x),y(null);try{await L(),y(I)}catch(z){y(z instanceof Error?z.message:String(z))}finally{v(null)}},R=fe.find(x=>x.id===m)??fe[0];return t.jsxs("div",{className:"alive-studio-body",children:[t.jsxs(je,{label:a.label,detail:`${l.sourceWidth}×${l.sourceHeight} · ${i} fps`,onClose:s,children:[t.jsx("div",{className:"alive-segmented",role:"group","aria-label":"Preview orientation",children:fe.map(x=>t.jsx("button",{type:"button","aria-pressed":x.id===m,onClick:()=>d(x.id),children:x.label},x.id))}),t.jsx("button",{type:"button",className:"alive-secondary",disabled:j!==null,onClick:()=>{W("Saving",()=>n(r),"Saved. Renders of this video now use alive captions.")},children:"Save"}),t.jsxs("button",{type:"button",className:"alive-primary",disabled:j!==null,onClick:()=>{W("Rendering",()=>o(r,m),`Saved and queued a ${R.label} render.`)},children:["Render ",R.label]})]}),t.jsxs("div",{className:"alive-main",children:[t.jsx(Xe,{rows:k,phraseLabels:p.map(me),activePhrase:O,time:N,snapped:r.snapToOnsets,onSeek:_}),t.jsxs("div",{className:"alive-stage",children:[t.jsx("div",{className:"alive-player",style:{aspectRatio:`${R.width} / ${R.height}`},children:t.jsx(Ne,{ref:w,component:Ke,inputProps:D,durationInFrames:h,fps:i,compositionWidth:R.width,compositionHeight:R.height,style:{width:"100%",height:"100%"},clickToPlay:!0,spaceKeyToPlayOrPause:!0})}),t.jsx("p",{className:"alive-status",role:"status",children:j?`${j}…`:C??(O>=0?`${me(p[O])} · ${p[O].lines.length} lines`:"Between phrases")})]}),t.jsx(Ye,{settings:r,showRegions:g,regionsAnalyzed:l.regionsAnalyzed,working:j!==null,onChange:H,onShowRegions:u,onReanalyze:()=>{W("Re-running analysis",c,"Analysis refreshed.")}})]}),t.jsx(tt,{duration:l.durationSeconds,fps:i,time:N,playing:b,envelope:l.envelope,rows:k,cues:p,activeCue:O,snapped:r.snapToOnsets,drift:M,onSeek:_,onStep:x=>_(N+x/i),onTogglePlay:()=>w.current?.toggle(),onToggleSnap:()=>H({snapToOnsets:!r.snapToOnsets})})]})}export{ft as AliveCaptionsStudio};
1
+ import{c as F,j as t,a as A}from"./vendor-react-Dj7D1fD4.js";import{u as be,a as pe,V as Se,A as xe,P as Ne}from"./vendor-remotion-CH1HXrlc.js";import{ei as Ce,ej as ke,ek as we,el as $e,em as Pe,en as de,eo as Te,ep as me,eq as Re,er as Ae,es as Me,et as _e,eu as Ee,ev as Oe,ew as fe,by as We}from"./videobook-B8dYNQrG.js";import{g as Le,a6 as ze,_ as Fe,h as He,X as Ie}from"./vendor-icons-D3KrGMJY.js";import"./vendor-Ca2-b3hB.js";import"./vendor-mcp-9gUvOX4H.js";const Be={face:"#63c7d8",text:"#f0a860",graphic:"#f0a860"};function De(a){const e=F.c(25),{phrase:s,cue:n,settings:o,sourceWidth:c,sourceHeight:l}=a,{width:i,height:h}=pe();let r,f,m,d;if(e[0]!==h||e[1]!==s.id||e[2]!==s.regions||e[3]!==o||e[4]!==l||e[5]!==c||e[6]!==i){const j={sourceWidth:c,sourceHeight:l,targetWidth:i,targetHeight:h};let v;e[11]!==s.regions||e[12]!==o?(v=new Set($e(s.regions,o)),e[11]=s.regions,e[12]=o,e[13]=v):v=e[13];const C=v;let y;e[14]!==i?(y=Math.round(i/480),e[14]=i,e[15]=y):y=e[15],f=Math.max(2,y),r=xe,e[16]===Symbol.for("react.memo_cache_sentinel")?(m={pointerEvents:"none"},e[16]=m):m=e[16],d=s.regions.map((w,$)=>{const b=Pe(w,j);return t.jsx("div",{style:{position:"absolute",left:b.x*i,top:b.y*h,width:b.width*i,height:b.height*h,border:`${f}px dashed ${Be[w.kind]}`,opacity:C.has(w)?1:.4,borderRadius:f*2}},`${s.id}-${$}`)}),e[0]=h,e[1]=s.id,e[2]=s.regions,e[3]=o,e[4]=l,e[5]=c,e[6]=i,e[7]=r,e[8]=f,e[9]=m,e[10]=d}else r=e[7],f=e[8],m=e[9],d=e[10];let g;e[17]!==n.column||e[18]!==f?(g=t.jsx(Ue,{column:n.column,stroke:f}),e[17]=n.column,e[18]=f,e[19]=g):g=e[19];let u;return e[20]!==r||e[21]!==m||e[22]!==d||e[23]!==g?(u=t.jsxs(r,{style:m,children:[d,g]}),e[20]=r,e[21]=m,e[22]=d,e[23]=g,e[24]=u):u=e[24],u}function Ue(a){const e=F.c(7),{column:s,stroke:n}=a,o=`${Math.max(1,n/2)}px dashed rgba(114, 231, 170, 0.6)`,c=n*2;let l;return e[0]!==s.height||e[1]!==s.width||e[2]!==s.x||e[3]!==s.y||e[4]!==o||e[5]!==c?(l=t.jsx("div",{style:{position:"absolute",left:s.x,top:s.y,width:s.width,height:s.height,border:o,borderRadius:c}}),e[0]=s.height,e[1]=s.width,e[2]=s.x,e[3]=s.y,e[4]=o,e[5]=c,e[6]=l):l=e[6],l}function Ke(a){const e=F.c(34),{mediaUrl:s,sourceWidth:n,sourceHeight:o,cues:c,phrases:l,settings:i,showRegions:h}=a,r=be(),{fps:f,width:m,height:d}=pe();let g;e[0]!==d||e[1]!==o||e[2]!==n||e[3]!==m?(g=ke({sourceWidth:n,sourceHeight:o,targetWidth:m,targetHeight:d}),e[0]=d,e[1]=o,e[2]=n,e[3]=m,e[4]=g):g=e[4];const u=g,j=r/f;let v;e[5]!==c||e[6]!==j?(v=Ce(c,j),e[5]=c,e[6]=j,e[7]=v):v=e[7];const C=v;let y;e[8]!==C||e[9]!==l?(y=C?l.find(M=>M.id===C.id):void 0,e[8]=C,e[9]=l,e[10]=y):y=e[10];const w=y;let $;e[11]===Symbol.for("react.memo_cache_sentinel")?($={background:"#000000"},e[11]=$):$=e[11];let b;e[12]!==u.height||e[13]!==u.width||e[14]!==u.x||e[15]!==u.y?(b={position:"absolute",left:u.x,top:u.y,width:u.width,height:u.height},e[12]=u.height,e[13]=u.width,e[14]=u.x,e[15]=u.y,e[16]=b):b=e[16];let N;e[17]!==s||e[18]!==b?(N=t.jsx(Se,{src:s,style:b}),e[17]=s,e[18]=b,e[19]=N):N=e[19];let P;e[20]!==C||e[21]!==w||e[22]!==i||e[23]!==h||e[24]!==o||e[25]!==n?(P=h&&C&&w&&t.jsx(De,{phrase:w,cue:C,settings:i,sourceWidth:n,sourceHeight:o}),e[20]=C,e[21]=w,e[22]=i,e[23]=h,e[24]=o,e[25]=n,e[26]=P):P=e[26];let p;e[27]!==c||e[28]!==i?(p=t.jsx(we,{cues:c,settings:i,strokeWidth:6,strokeColor:"#000000",shadowBlur:6,shadowOpacity:60}),e[27]=c,e[28]=i,e[29]=p):p=e[29];let k;return e[30]!==N||e[31]!==P||e[32]!==p?(k=t.jsxs(xe,{style:$,children:[N,P,p]}),e[30]=N,e[31]=P,e[32]=p,e[33]=k):k=e[33],k}const ue=[{id:"stack",label:"Stack",hint:"Words land one by one and build a stacked block beside the subject, each line sized to fill the column."},{id:"punch",label:"Punch",hint:"One word at a time, as big as the free space allows. Best on fast verses."},{id:"karaoke",label:"Karaoke",hint:"The whole stacked phrase shows dimmed and lights up word by word."}],Ve=[{id:"pop",label:"Pop"},{id:"slam",label:"Slam"},{id:"rise",label:"Rise"}],qe=[{name:"Green",color:"#72E7AA"},{name:"Pink",color:"#F0B9D0"},{name:"Cyan",color:"#63C7D8"},{name:"Amber",color:"#E7C372"}];function Ge(a,e){if(!a.drifted)return de(a.start);const s=a.driftMs>0?"+":"";return e?`${de(a.start)} · snapped`:`${de(a.start)} · ${s}${a.driftMs}ms`}function Xe(a){const e=F.c(21),{rows:s,phraseLabels:n,activePhrase:o,time:c,snapped:l,onSeek:i}=a;let h;e[0]===Symbol.for("react.memo_cache_sentinel")?(h=t.jsx("h3",{children:"Transcript"}),e[0]=h):h=e[0];let r;e[1]!==s.length?(r=t.jsxs("header",{className:"alive-panel-header",children:[h,t.jsxs("p",{children:[s.length," words · timed to the vocal"]})]}),e[1]=s.length,e[2]=r):r=e[2];let f;if(e[3]!==o||e[4]!==i||e[5]!==n||e[6]!==s||e[7]!==l||e[8]!==c){let g;e[10]!==o||e[11]!==i||e[12]!==s||e[13]!==l||e[14]!==c?(g=(u,j)=>t.jsxs("div",{className:"alive-phrase","data-active":j===o||void 0,children:[t.jsxs("div",{className:"alive-phrase-head",children:[t.jsxs("span",{children:["Phrase ",j+1]}),t.jsx("span",{children:u})]}),t.jsx("div",{className:"alive-phrase-words",children:s.filter(v=>v.phraseIndex===j).map(v=>t.jsxs("button",{type:"button",className:"alive-word","data-active":c>=v.start&&c<v.end||void 0,"data-drifted":v.drifted&&!l||void 0,onClick:()=>i(v.start+.03),children:[t.jsx("span",{children:v.text}),t.jsx("small",{children:Ge(v,l)})]},v.key))})]},`${j}-${u}`),e[10]=o,e[11]=i,e[12]=s,e[13]=l,e[14]=c,e[15]=g):g=e[15],f=n.map(g),e[3]=o,e[4]=i,e[5]=n,e[6]=s,e[7]=l,e[8]=c,e[9]=f}else f=e[9];let m;e[16]!==f?(m=t.jsx("div",{className:"alive-transcript-list",children:f}),e[16]=f,e[17]=m):m=e[17];let d;return e[18]!==r||e[19]!==m?(d=t.jsxs("section",{className:"alive-panel alive-transcript","aria-label":"Transcript",children:[r,m]}),e[18]=r,e[19]=m,e[20]=d):d=e[20],d}function ve(a){const e=F.c(10),{label:s,options:n,value:o,onChange:c}=a;let l;if(e[0]!==c||e[1]!==n||e[2]!==o){let h;e[4]!==c||e[5]!==o?(h=r=>t.jsx("button",{type:"button","aria-pressed":r.id===o,onClick:()=>c(r.id),children:r.label},r.id),e[4]=c,e[5]=o,e[6]=h):h=e[6],l=n.map(h),e[0]=c,e[1]=n,e[2]=o,e[3]=l}else l=e[3];let i;return e[7]!==s||e[8]!==l?(i=t.jsx("div",{className:"alive-segmented",role:"group","aria-label":s,children:l}),e[7]=s,e[8]=l,e[9]=i):i=e[9],i}function re(a){const e=F.c(8),{checked:s,onChange:n,children:o}=a;let c;e[0]!==n?(c=h=>n(h.target.checked),e[0]=n,e[1]=c):c=e[1];let l;e[2]!==s||e[3]!==c?(l=t.jsx("input",{type:"checkbox",checked:s,onChange:c}),e[2]=s,e[3]=c,e[4]=l):l=e[4];let i;return e[5]!==o||e[6]!==l?(i=t.jsxs("label",{className:"alive-toggle",children:[l,o]}),e[5]=o,e[6]=l,e[7]=i):i=e[7],i}function Ye(a){const e=F.c(69),{settings:s,showRegions:n,regionsAnalyzed:o,working:c,onChange:l,onShowRegions:i,onReanalyze:h}=a;let r;e[0]!==s.style?(r=ue.find(S=>S.id===s.style)?.hint,e[0]=s.style,e[1]=r):r=e[1];const f=r;let m;e[2]===Symbol.for("react.memo_cache_sentinel")?(m=t.jsx("h4",{children:"Style"}),e[2]=m):m=e[2];let d;e[3]!==l?(d=S=>l({style:S}),e[3]=l,e[4]=d):d=e[4];let g;e[5]!==s.style||e[6]!==d?(g=t.jsx(ve,{label:"Caption style",options:ue,value:s.style,onChange:d}),e[5]=s.style,e[6]=d,e[7]=g):g=e[7];let u;e[8]!==f?(u=t.jsx("p",{className:"alive-hint",children:f}),e[8]=f,e[9]=u):u=e[9];let j;e[10]!==g||e[11]!==u?(j=t.jsxs("div",{className:"alive-field",children:[m,g,u]}),e[10]=g,e[11]=u,e[12]=j):j=e[12];let v;e[13]===Symbol.for("react.memo_cache_sentinel")?(v=t.jsx("h4",{children:"Word entrance"}),e[13]=v):v=e[13];let C;e[14]!==l?(C=S=>l({motion:S}),e[14]=l,e[15]=C):C=e[15];let y;e[16]!==s.motion||e[17]!==C?(y=t.jsxs("div",{className:"alive-field",children:[v,t.jsx(ve,{label:"Word entrance",options:Ve,value:s.motion,onChange:C})]}),e[16]=s.motion,e[17]=C,e[18]=y):y=e[18];let w;e[19]===Symbol.for("react.memo_cache_sentinel")?(w=t.jsx("h4",{children:"Frame awareness"}),e[19]=w):w=e[19];let $;e[20]!==l?($=S=>l({avoidFaces:S}),e[20]=l,e[21]=$):$=e[21];let b;e[22]!==s.avoidFaces||e[23]!==$?(b=t.jsx(re,{checked:s.avoidFaces,onChange:$,children:"Keep words off faces"}),e[22]=s.avoidFaces,e[23]=$,e[24]=b):b=e[24];let N;e[25]!==l?(N=S=>l({avoidText:S}),e[25]=l,e[26]=N):N=e[26];let P;e[27]!==s.avoidText||e[28]!==N?(P=t.jsx(re,{checked:s.avoidText,onChange:N,children:"Keep words off on-screen text"}),e[27]=s.avoidText,e[28]=N,e[29]=P):P=e[29];let p;e[30]!==i||e[31]!==n?(p=t.jsx(re,{checked:n,onChange:i,children:"Show detected regions"}),e[30]=i,e[31]=n,e[32]=p):p=e[32];let k;e[33]!==o?(k=!o&&t.jsx("p",{className:"alive-hint alive-warning",children:"Face and text detection did not run, so stacks use the default columns. Set OPENAI_API_KEY and re-run the analysis."}),e[33]=o,e[34]=k):k=e[34];let M;e[35]!==b||e[36]!==P||e[37]!==p||e[38]!==k?(M=t.jsxs("div",{className:"alive-field",children:[w,b,P,p,k]}),e[35]=b,e[36]=P,e[37]=p,e[38]=k,e[39]=M):M=e[39];let D;e[40]===Symbol.for("react.memo_cache_sentinel")?(D=t.jsx("h4",{children:"Audio"}),e[40]=D):D=e[40];let O;e[41]!==l?(O=S=>l({energySizing:S}),e[41]=l,e[42]=O):O=e[42];let _;e[43]!==s.energySizing||e[44]!==O?(_=t.jsxs("div",{className:"alive-field",children:[D,t.jsx(re,{checked:s.energySizing,onChange:O,children:"Size words by vocal energy"})]}),e[43]=s.energySizing,e[44]=O,e[45]=_):_=e[45];let H;e[46]===Symbol.for("react.memo_cache_sentinel")?(H=t.jsx("h4",{children:"Hit color"}),e[46]=H):H=e[46];let W;e[47]!==l||e[48]!==s.hitColor?(W=qe.map(S=>t.jsx("button",{type:"button","aria-label":S.name,"aria-pressed":s.hitColor.toUpperCase()===S.color,onClick:()=>l({hitColor:S.color}),children:t.jsx("span",{style:{background:S.color}})},S.color)),e[47]=l,e[48]=s.hitColor,e[49]=W):W=e[49];let R;e[50]===Symbol.for("react.memo_cache_sentinel")?(R=t.jsx("span",{children:"Text"}),e[50]=R):R=e[50];let x;e[51]!==l?(x=S=>l({textColor:S.target.value.toUpperCase()}),e[51]=l,e[52]=x):x=e[52];let L;e[53]!==s.textColor||e[54]!==x?(L=t.jsxs("label",{className:"alive-color-input",children:[R,t.jsx("input",{type:"color",value:s.textColor,onChange:x})]}),e[53]=s.textColor,e[54]=x,e[55]=L):L=e[55];let I;e[56]!==W||e[57]!==L?(I=t.jsxs("div",{className:"alive-field",children:[H,t.jsxs("div",{className:"alive-swatches",children:[W,L]})]}),e[56]=W,e[57]=L,e[58]=I):I=e[58];let z;e[59]!==h||e[60]!==c?(z=t.jsx("div",{className:"alive-field",children:t.jsx("button",{type:"button",className:"alive-secondary",disabled:c,onClick:h,children:"Re-run analysis"})}),e[59]=h,e[60]=c,e[61]=z):z=e[61];let B;return e[62]!==M||e[63]!==_||e[64]!==I||e[65]!==z||e[66]!==j||e[67]!==y?(B=t.jsxs("section",{className:"alive-panel alive-settings","aria-label":"Caption settings",children:[j,y,M,_,I,z]}),e[62]=M,e[63]=_,e[64]=I,e[65]=z,e[66]=j,e[67]=y,e[68]=B):B=e[68],B}const Je=90;function U(a,e){return`${Math.max(0,a)/e*100}%`}function Qe(a,e,s){if(e.count===0)return`All ${s} words already sit on their vocal onsets`;if(a)return`${e.count} words snapped to their vocal onsets`;const n=e.minMs===e.maxMs?`${e.maxMs}`:`${e.minMs}–${e.maxMs}`;return`${e.count} words start ${n} ms off the vocal`}function Ze(a){const e=a>120?10:a>30?5:1,s=[];for(let n=0;n<=a;n+=e)s.push(n);return s}function et(a,e,s){const n=F.c(5),o=A.useRef(null);let c,l;return n[0]!==e||n[1]!==s||n[2]!==a?(c=()=>{const i=o.current;if(!i||!s)return;const h=a/e*i.scrollWidth;(h<i.scrollLeft||h>i.scrollLeft+i.clientWidth*.85)&&(i.scrollLeft=Math.max(0,h-i.clientWidth*.2))},l=[a,e,s],n[0]=e,n[1]=s,n[2]=a,n[3]=c,n[4]=l):(c=n[3],l=n[4]),A.useEffect(c,l),o}function tt(a){const e=F.c(105),{duration:s,fps:n,time:o,playing:c,envelope:l,rows:i,cues:h,activeCue:r,snapped:f,drift:m}=a,d=Math.max(s,.1),g=et(o,d,c);let u;e[0]!==a||e[1]!==d?(u=T=>{const E=T.currentTarget.getBoundingClientRect();a.onSeek((T.clientX-E.left)/E.width*d)},e[0]=a,e[1]=d,e[2]=u):u=e[2];const j=u;let v;e[3]!==a?(v=()=>a.onStep(-1),e[3]=a,e[4]=v):v=e[4];let C;e[5]===Symbol.for("react.memo_cache_sentinel")?(C=t.jsx(Le,{size:16}),e[5]=C):C=e[5];let y;e[6]!==v?(y=t.jsx("button",{type:"button","aria-label":"Previous frame",onClick:v,children:C}),e[6]=v,e[7]=y):y=e[7];const w=c?"Pause":"Play";let $;e[8]!==c?($=c?t.jsx(ze,{size:16}):t.jsx(Fe,{size:16}),e[8]=c,e[9]=$):$=e[9];let b;e[10]!==a.onTogglePlay||e[11]!==w||e[12]!==$?(b=t.jsx("button",{type:"button",className:"alive-play","aria-label":w,onClick:a.onTogglePlay,children:$}),e[10]=a.onTogglePlay,e[11]=w,e[12]=$,e[13]=b):b=e[13];let N;e[14]!==a?(N=()=>a.onStep(1),e[14]=a,e[15]=N):N=e[15];let P;e[16]===Symbol.for("react.memo_cache_sentinel")?(P=t.jsx(He,{size:16}),e[16]=P):P=e[16];let p;e[17]!==N?(p=t.jsx("button",{type:"button","aria-label":"Next frame",onClick:N,children:P}),e[17]=N,e[18]=p):p=e[18];let k;e[19]!==o?(k=de(o),e[19]=o,e[20]=k):k=e[20];let M;e[21]!==k?(M=t.jsx("strong",{className:"alive-clock",children:k}),e[21]=k,e[22]=M):M=e[22];const D=Math.floor(o*n),O=Math.floor(d*n);let _;e[23]!==D||e[24]!==O?(_=t.jsxs("span",{className:"alive-muted",children:["frame ",D," / ",O]}),e[23]=D,e[24]=O,e[25]=_):_=e[25];let H;e[26]===Symbol.for("react.memo_cache_sentinel")?(H=t.jsx("span",{className:"alive-spacer"}),e[26]=H):H=e[26];const W=m.count>0&&!f||void 0;let R;e[27]!==m||e[28]!==i.length||e[29]!==f?(R=Qe(f,m,i.length),e[27]=m,e[28]=i.length,e[29]=f,e[30]=R):R=e[30];let x;e[31]!==W||e[32]!==R?(x=t.jsx("span",{className:"alive-drift","data-drifted":W,children:R}),e[31]=W,e[32]=R,e[33]=x):x=e[33];const L=f?"alive-secondary":"alive-primary alive-warn",I=m.count===0,z=f?"Use transcript timing":"Snap words to audio";let B;e[34]!==a.onToggleSnap||e[35]!==L||e[36]!==I||e[37]!==z?(B=t.jsx("button",{type:"button",className:L,disabled:I,onClick:a.onToggleSnap,children:z}),e[34]=a.onToggleSnap,e[35]=L,e[36]=I,e[37]=z,e[38]=B):B=e[38];let S;e[39]!==M||e[40]!==_||e[41]!==x||e[42]!==B||e[43]!==y||e[44]!==b||e[45]!==p?(S=t.jsxs("div",{className:"alive-transport",children:[y,b,p,M,_,H,x,B]}),e[39]=M,e[40]=_,e[41]=x,e[42]=B,e[43]=y,e[44]=b,e[45]=p,e[46]=S):S=e[46];let ne;e[47]===Symbol.for("react.memo_cache_sentinel")?(ne=t.jsxs("div",{className:"alive-lane-labels","aria-hidden":"true",children:[t.jsx("span",{className:"alive-lane-ruler"}),t.jsx("span",{className:"alive-lane-audio",children:"Audio"}),t.jsx("span",{className:"alive-lane-words",children:"Words"}),t.jsx("span",{className:"alive-lane-stacks",children:"Stacks"})]}),e[47]=ne):ne=e[47];const he=d*Je;let K;e[48]!==he?(K={minWidth:he},e[48]=he,e[49]=K):K=e[49];let V;e[50]!==d?(V=Ze(d).map(T=>t.jsxs("span",{style:{left:U(T,d)},children:[T,"s"]},T)),e[50]=d,e[51]=V):V=e[51];let q;e[52]!==V?(q=t.jsx("div",{className:"alive-lane-ruler",children:V}),e[52]=V,e[53]=q):q=e[53];let G;e[54]!==l?(G=Te(l),e[54]=l,e[55]=G):G=e[55];let X;e[56]!==G?(X=t.jsx("svg",{viewBox:"0 0 1000 100",preserveAspectRatio:"none","aria-hidden":"true",children:t.jsx("path",{d:G})}),e[56]=G,e[57]=X):X=e[57];let Y;e[58]!==i||e[59]!==d||e[60]!==f?(Y=!f&&i.filter(st).map(T=>t.jsx("span",{className:"alive-onset",style:{left:U(T.onset,d)}},T.key)),e[58]=i,e[59]=d,e[60]=f,e[61]=Y):Y=e[61];let J;e[62]!==X||e[63]!==Y?(J=t.jsxs("div",{className:"alive-lane-audio",children:[X,Y]}),e[62]=X,e[63]=Y,e[64]=J):J=e[64];let Q;if(e[65]!==i||e[66]!==d||e[67]!==f||e[68]!==o){let T;e[70]!==d||e[71]!==f||e[72]!==o?(T=E=>t.jsx("span",{className:"alive-chip","data-active":o>=E.start&&o<E.end||void 0,"data-drifted":E.drifted&&!f||void 0,style:{left:U(E.start,d),width:U(E.end-E.start,d)},children:E.text},E.key),e[70]=d,e[71]=f,e[72]=o,e[73]=T):T=e[73],Q=i.map(T),e[65]=i,e[66]=d,e[67]=f,e[68]=o,e[69]=Q}else Q=e[69];let Z;e[74]!==Q?(Z=t.jsx("div",{className:"alive-lane-words",children:Q}),e[74]=Q,e[75]=Z):Z=e[75];let ee;if(e[76]!==r||e[77]!==h||e[78]!==d){let T;e[80]!==r||e[81]!==d?(T=(E,ye)=>t.jsx("span",{className:"alive-stack-bar","data-active":ye===r||void 0,style:{left:U(E.start,d),width:U(E.end-E.start,d)},children:me(E)},E.id),e[80]=r,e[81]=d,e[82]=T):T=e[82],ee=h.map(T),e[76]=r,e[77]=h,e[78]=d,e[79]=ee}else ee=e[79];let te;e[83]!==ee?(te=t.jsx("div",{className:"alive-lane-stacks",children:ee}),e[83]=ee,e[84]=te):te=e[84];let se;e[85]!==d||e[86]!==o?(se=U(o,d),e[85]=d,e[86]=o,e[87]=se):se=e[87];let le;e[88]!==se?(le=t.jsx("span",{className:"alive-playhead",style:{left:se}}),e[88]=se,e[89]=le):le=e[89];let ae;e[90]!==j||e[91]!==K||e[92]!==q||e[93]!==J||e[94]!==Z||e[95]!==te||e[96]!==le?(ae=t.jsxs("div",{className:"alive-lane-tracks",style:K,onClick:j,role:"presentation",children:[q,J,Z,te,le]}),e[90]=j,e[91]=K,e[92]=q,e[93]=J,e[94]=Z,e[95]=te,e[96]=le,e[97]=ae):ae=e[97];let ie;e[98]!==g||e[99]!==ae?(ie=t.jsxs("div",{className:"alive-lanes",children:[ne,t.jsx("div",{className:"alive-lane-scroll",ref:g,children:ae})]}),e[98]=g,e[99]=ae,e[100]=ie):ie=e[100];let oe;e[101]===Symbol.for("react.memo_cache_sentinel")?(oe=t.jsxs("div",{className:"alive-legend","aria-hidden":"true",children:[t.jsxs("span",{children:[t.jsx("i",{className:"alive-legend-chip"}),"Transcript word"]}),t.jsxs("span",{children:[t.jsx("i",{className:"alive-legend-drift"}),"Word off its vocal onset"]}),t.jsxs("span",{children:[t.jsx("i",{className:"alive-legend-onset"}),"Detected vocal onset"]})]}),e[101]=oe):oe=e[101];let ce;return e[102]!==S||e[103]!==ie?(ce=t.jsxs("section",{className:"alive-panel alive-timeline","aria-label":"Caption timeline",children:[S,ie,oe]}),e[102]=S,e[103]=ie,e[104]=ce):ce=e[104],ce}function st(a){return a.drifted}const ge=30;function lt(a){const e=F.c(3),s=A.useRef(null);let n,o;return e[0]!==a?(n=()=>{const c=s.current;return c?.showModal(),c?.querySelector(".alive-close")?.focus(),()=>{c?.close(),We(a)}},o=[a],e[0]=a,e[1]=n,e[2]=o):(n=e[1],o=e[2]),A.useEffect(n,o),s}function at(a){const e=F.c(6),[s,n]=A.useState(0),[o,c]=A.useState(!1);let l,i;e[0]!==a?(l=()=>{const r=a.current;if(!r)return;const f=g=>n(g.detail.frame),m=()=>c(!0),d=()=>c(!1);return r.addEventListener("frameupdate",f),r.addEventListener("play",m),r.addEventListener("pause",d),()=>{r.removeEventListener("frameupdate",f),r.removeEventListener("play",m),r.removeEventListener("pause",d)}},i=[a],e[0]=a,e[1]=l,e[2]=i):(l=e[1],i=e[2]),A.useEffect(l,i);let h;return e[3]!==s||e[4]!==o?(h={frame:s,playing:o},e[3]=s,e[4]=o,e[5]=h):h=e[5],h}function ft(a){const e=F.c(14),{asset:s,state:n,returnFocus:o,onClose:c}=a,l=lt(o),i=`Alive captions for ${s.label}`;let h;e[0]!==c?(h=m=>{m.preventDefault(),c()},e[0]=c,e[1]=h):h=e[1];let r;e[2]!==s.label||e[3]!==c||e[4]!==a||e[5]!==n.data||e[6]!==n.message||e[7]!==n.status?(r=n.status==="ready"?t.jsx(it,{...a,data:n.data}):t.jsxs("div",{className:"alive-studio-message",children:[t.jsx(je,{label:s.label,detail:null,onClose:c}),t.jsx("p",{role:n.status==="error"?"alert":"status",children:n.status==="error"?n.message:"Analysing vocals, phrases and faces…"})]}),e[2]=s.label,e[3]=c,e[4]=a,e[5]=n.data,e[6]=n.message,e[7]=n.status,e[8]=r):r=e[8];let f;return e[9]!==l||e[10]!==i||e[11]!==h||e[12]!==r?(f=t.jsx("dialog",{className:"alive-studio","aria-label":i,onCancel:h,ref:l,children:r}),e[9]=l,e[10]=i,e[11]=h,e[12]=r,e[13]=f):f=e[13],f}function je(a){const e=F.c(12),{label:s,detail:n,onClose:o,children:c}=a;let l;e[0]===Symbol.for("react.memo_cache_sentinel")?(l=t.jsx("h2",{children:"Alive captions"}),e[0]=l):l=e[0];const i=n?` · ${n}`:"";let h;e[1]!==s||e[2]!==i?(h=t.jsxs("span",{className:"alive-muted",children:[s,i]}),e[1]=s,e[2]=i,e[3]=h):h=e[3];let r;e[4]===Symbol.for("react.memo_cache_sentinel")?(r=t.jsx("span",{className:"alive-spacer"}),e[4]=r):r=e[4];let f;e[5]===Symbol.for("react.memo_cache_sentinel")?(f=t.jsx(Ie,{size:16}),e[5]=f):f=e[5];let m;e[6]!==o?(m=t.jsx("button",{type:"button",className:"alive-close","aria-label":"Close alive captions",onClick:o,children:f}),e[6]=o,e[7]=m):m=e[7];let d;return e[8]!==c||e[9]!==h||e[10]!==m?(d=t.jsxs("header",{className:"alive-header",children:[l,h,r,c,m]}),e[8]=c,e[9]=h,e[10]=m,e[11]=d):d=e[11],d}function it({asset:a,data:e,onClose:s,onSave:n,onRender:o,onReanalyze:c}){const{analysis:l}=e,i=Math.round(a.fps??ge)||ge,h=Math.max(1,Math.round(l.durationSeconds*i)),[r,f]=A.useState(e.settings),[m,d]=A.useState(()=>Re(a.width,a.height)),[g,u]=A.useState(!0),[j,v]=A.useState(null),[C,y]=A.useState(null),w=A.useRef(null),{frame:$,playing:b}=at(w),N=$/i,P=A.useMemo(()=>Ae(l,m),[l,m]),p=A.useMemo(()=>Me(l.phrases,r,P),[l.phrases,r,P]),k=A.useMemo(()=>_e(l,r),[l,r]),M=A.useMemo(()=>Ee(k),[k]),D=A.useMemo(()=>({mediaUrl:a.mediaUrl,sourceWidth:l.sourceWidth,sourceHeight:l.sourceHeight,cues:p,phrases:l.phrases,settings:r,showRegions:g}),[a.mediaUrl,l,p,r,g]),O=Oe(p,N),_=x=>w.current?.seekTo(Math.max(0,Math.min(h-1,Math.round(x*i)))),H=x=>f(L=>({...L,...x})),W=async(x,L,I)=>{v(x),y(null);try{await L(),y(I)}catch(z){y(z instanceof Error?z.message:String(z))}finally{v(null)}},R=fe.find(x=>x.id===m)??fe[0];return t.jsxs("div",{className:"alive-studio-body",children:[t.jsxs(je,{label:a.label,detail:`${l.sourceWidth}×${l.sourceHeight} · ${i} fps`,onClose:s,children:[t.jsx("div",{className:"alive-segmented",role:"group","aria-label":"Preview orientation",children:fe.map(x=>t.jsx("button",{type:"button","aria-pressed":x.id===m,onClick:()=>d(x.id),children:x.label},x.id))}),t.jsx("button",{type:"button",className:"alive-secondary",disabled:j!==null,onClick:()=>{W("Saving",()=>n(r),"Saved. Renders of this video now use alive captions.")},children:"Save"}),t.jsxs("button",{type:"button",className:"alive-primary",disabled:j!==null,onClick:()=>{W("Rendering",()=>o(r,m),`Saved and queued a ${R.label} render.`)},children:["Render ",R.label]})]}),t.jsxs("div",{className:"alive-main",children:[t.jsx(Xe,{rows:k,phraseLabels:p.map(me),activePhrase:O,time:N,snapped:r.snapToOnsets,onSeek:_}),t.jsxs("div",{className:"alive-stage",children:[t.jsx("div",{className:"alive-player",style:{aspectRatio:`${R.width} / ${R.height}`},children:t.jsx(Ne,{ref:w,component:Ke,inputProps:D,durationInFrames:h,fps:i,compositionWidth:R.width,compositionHeight:R.height,style:{width:"100%",height:"100%"},clickToPlay:!0,spaceKeyToPlayOrPause:!0})}),t.jsx("p",{className:"alive-status",role:"status",children:j?`${j}…`:C??(O>=0?`${me(p[O])} · ${p[O].lines.length} lines`:"Between phrases")})]}),t.jsx(Ye,{settings:r,showRegions:g,regionsAnalyzed:l.regionsAnalyzed,working:j!==null,onChange:H,onShowRegions:u,onReanalyze:()=>{W("Re-running analysis",c,"Analysis refreshed.")}})]}),t.jsx(tt,{duration:l.durationSeconds,fps:i,time:N,playing:b,envelope:l.envelope,rows:k,cues:p,activeCue:O,snapped:r.snapToOnsets,drift:M,onSeek:_,onStep:x=>_(N+x/i),onTogglePlay:()=>w.current?.toggle(),onToggleSnap:()=>H({snapToOnsets:!r.snapToOnsets})})]})}export{ft as AliveCaptionsStudio};