videobook 0.1.0 → 0.2.1
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 +45 -0
- package/README.md +80 -18
- package/dist/cli/args.js +53 -3
- package/dist/cli/config-command.js +103 -0
- package/dist/cli/doctor-command.js +51 -17
- package/dist/cli/key-report.js +16 -0
- package/dist/cli/main.js +14 -1
- package/dist/cli/mcp-command.js +6 -0
- package/dist/cli/run-command.js +60 -16
- package/dist/cli/serve-command.js +5 -0
- package/dist/cli/setup-command.js +60 -0
- package/dist/dancing-workflow.js +2 -1
- package/dist/env-file.js +91 -0
- package/dist/env-secrets.js +38 -74
- package/dist/ffprobe-binary.js +29 -7
- package/dist/hydration.js +56 -0
- package/dist/jobs/archive-clip.js +2 -1
- package/dist/jobs/download-video.js +2 -1
- package/dist/load-env.js +5 -2
- package/dist/one-password.js +200 -0
- package/dist/queue.js +4 -0
- package/dist/serve.js +5 -30
- package/dist/tools/videobook-domain.js +24 -9
- package/dist/ytdlp-binary.js +65 -0
- package/package.json +4 -12
- package/ui/assets/{alive-captions-DPEr1nKM.js → alive-captions-e-n3xmCd.js} +1 -1
- package/ui/assets/{confirm-bodies-D8ouHXKy.js → confirm-bodies-DS6jh0Kb.js} +1 -1
- package/ui/assets/{fullscreen-B7nw4YbE.js → fullscreen-VY7f4mJ-.js} +1 -1
- package/ui/assets/{goal-ui-DK-xTCK0.js → goal-ui-B__NgZYz.js} +1 -1
- package/ui/assets/{index-kS1905s5.js → index-BPco0HoT.js} +2 -2
- package/ui/assets/{inspector-MlzQbTVo.js → inspector-BBuZIaqg.js} +1 -1
- package/ui/assets/{jobs-ui-CoinOWx0.js → jobs-ui-vmEH4zBT.js} +1 -1
- package/ui/assets/{music-video-Cbe80kmQ.js → music-video-CklLKD1R.js} +1 -1
- package/ui/assets/{ui-xo_Y1yQg.js → ui-CtYXY_po.js} +1 -1
- package/ui/assets/{vendor-DfTCWgKm.js → vendor-OClqq_Da.js} +2 -2
- package/ui/assets/{vendor-flow-BXbeWxW7.js → vendor-flow-DY81i6dC.js} +1 -1
- package/ui/assets/{vendor-mcp-BTlEJcHV.js → vendor-mcp-BsGNAD4I.js} +1 -1
- package/ui/assets/{videobook-Qlp9HQFu.js → videobook-lhLK6e6Q.js} +1 -1
- package/ui/index.html +11 -11
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
export const OP_VAULT_SETTING = "VIDEOBOOK_OP_VAULT";
|
|
7
|
+
export const OP_TOKEN_SETTING = "VIDEOBOOK_OP_SERVICE_TOKEN";
|
|
8
|
+
const OP_TIMEOUT_MS = 10_000;
|
|
9
|
+
const TEMPLATE_UNSAFE = /[\r\n]|\{\{|\}\}/u;
|
|
10
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
11
|
+
export function isOpReference(value) {
|
|
12
|
+
return typeof value === "string" && value.trim().startsWith("op://");
|
|
13
|
+
}
|
|
14
|
+
export function vaultReference(vault, key) {
|
|
15
|
+
return `op://${vault}/${key}/password`;
|
|
16
|
+
}
|
|
17
|
+
export function resolveServiceToken(raw, env) {
|
|
18
|
+
const value = raw?.trim();
|
|
19
|
+
if (!value)
|
|
20
|
+
return { value: null, source: null };
|
|
21
|
+
const name = value.startsWith("env:") ? value.slice(4) : value.startsWith("$") ? value.slice(1) : value;
|
|
22
|
+
if (value !== name || /^[A-Z_][A-Z0-9_]*$/u.test(value)) {
|
|
23
|
+
if (!ENV_NAME.test(name))
|
|
24
|
+
return { value: null, source: `env:${name}` };
|
|
25
|
+
return { value: env[name]?.trim() || null, source: `env:${name}` };
|
|
26
|
+
}
|
|
27
|
+
return { value, source: "literal" };
|
|
28
|
+
}
|
|
29
|
+
export function readOnePasswordSettings(env) {
|
|
30
|
+
return {
|
|
31
|
+
vault: env[OP_VAULT_SETTING]?.trim() || null,
|
|
32
|
+
token: resolveServiceToken(env[OP_TOKEN_SETTING], env),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function redactToken(text, token) {
|
|
36
|
+
return token ? text.split(token).join("***") : text;
|
|
37
|
+
}
|
|
38
|
+
export function planReferences(env, managedKeys, vault, vaultTitles) {
|
|
39
|
+
const explicit = Object.entries(env)
|
|
40
|
+
.filter((entry) => isOpReference(entry[1]))
|
|
41
|
+
.map(([key, reference]) => [key, reference.trim()]);
|
|
42
|
+
const covered = new Set(explicit.map(([key]) => key));
|
|
43
|
+
const convention = vault
|
|
44
|
+
? managedKeys
|
|
45
|
+
.filter((key) => !covered.has(key) && !env[key]?.trim() && vaultTitles.has(key))
|
|
46
|
+
.map((key) => [key, vaultReference(vault, key)])
|
|
47
|
+
: [];
|
|
48
|
+
return { explicit, convention };
|
|
49
|
+
}
|
|
50
|
+
function startMarker(nonce, key) {
|
|
51
|
+
return `<<<${nonce}:${key}>>>`;
|
|
52
|
+
}
|
|
53
|
+
function endMarker(nonce, key) {
|
|
54
|
+
return `<<<${nonce}/${key}>>>`;
|
|
55
|
+
}
|
|
56
|
+
export function injectionTemplate(entries, nonce) {
|
|
57
|
+
return entries.map(([key, reference]) => `${startMarker(nonce, key)}{{ ${reference} }}${endMarker(nonce, key)}`).join("\n");
|
|
58
|
+
}
|
|
59
|
+
export function parseInjection(output, keys, nonce) {
|
|
60
|
+
const values = new Map();
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
const start = output.indexOf(startMarker(nonce, key));
|
|
63
|
+
if (start < 0)
|
|
64
|
+
continue;
|
|
65
|
+
const from = start + startMarker(nonce, key).length;
|
|
66
|
+
const end = output.indexOf(endMarker(nonce, key), from);
|
|
67
|
+
if (end < 0)
|
|
68
|
+
continue;
|
|
69
|
+
const value = output.slice(from, end);
|
|
70
|
+
if (value !== "")
|
|
71
|
+
values.set(key, value);
|
|
72
|
+
}
|
|
73
|
+
return values;
|
|
74
|
+
}
|
|
75
|
+
function opEnvironment(env, token) {
|
|
76
|
+
return token ? { ...env, OP_SERVICE_ACCOUNT_TOKEN: token } : env;
|
|
77
|
+
}
|
|
78
|
+
export function opErrorMessage(error, token) {
|
|
79
|
+
const failure = error;
|
|
80
|
+
if (failure.code === "ENOENT")
|
|
81
|
+
return "the 1Password CLI (op) is not on PATH";
|
|
82
|
+
if (failure.code === "ETIMEDOUT" || failure.signal === "SIGTERM")
|
|
83
|
+
return "op timed out";
|
|
84
|
+
const line = String(failure.stderr ?? "").trim().split("\n")[0]?.trim().replace(/^\[ERROR\]\s+\S+\s+\S+\s+/u, "");
|
|
85
|
+
return redactToken(line || failure.message || "op failed", token);
|
|
86
|
+
}
|
|
87
|
+
function runOp(args, env, token) {
|
|
88
|
+
return execFileSync("op", args, {
|
|
89
|
+
env: opEnvironment(env, token),
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
timeout: OP_TIMEOUT_MS,
|
|
92
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
93
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export function listVaultItemTitles(vault, env, token) {
|
|
97
|
+
const parsed = JSON.parse(runOp(["item", "list", "--vault", vault, "--format", "json"], env, token));
|
|
98
|
+
if (!Array.isArray(parsed))
|
|
99
|
+
throw new Error("op item list did not return a list");
|
|
100
|
+
return new Set(parsed.flatMap((item) => {
|
|
101
|
+
const title = item && typeof item === "object" ? item.title : undefined;
|
|
102
|
+
return typeof title === "string" ? [title] : [];
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
function injectBatch(entries, env, token) {
|
|
106
|
+
const nonce = randomUUID().replaceAll("-", "");
|
|
107
|
+
const directory = mkdtempSync(join(tmpdir(), "videobook-op-"));
|
|
108
|
+
try {
|
|
109
|
+
const template = join(directory, "template");
|
|
110
|
+
writeFileSync(template, injectionTemplate(entries, nonce), { mode: 0o600 });
|
|
111
|
+
const output = runOp(["inject", "--in-file", template], env, token);
|
|
112
|
+
return parseInjection(output, entries.map(([key]) => key), nonce);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
rmSync(directory, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function readOne(key, reference, env, token) {
|
|
119
|
+
try {
|
|
120
|
+
const value = runOp(["read", "--no-newline", reference], env, token);
|
|
121
|
+
return value === ""
|
|
122
|
+
? { values: new Map(), failures: [{ key, reference, reason: "the item has no value" }] }
|
|
123
|
+
: { values: new Map([[key, value]]), failures: [] };
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return { values: new Map(), failures: [{ key, reference, reason: opErrorMessage(error, token) }] };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function mergeResults(results) {
|
|
130
|
+
return {
|
|
131
|
+
values: new Map(results.flatMap((result) => [...result.values])),
|
|
132
|
+
failures: results.flatMap((result) => result.failures),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function resolveEntries(entries, env, token) {
|
|
136
|
+
const unsafe = entries.filter(([, reference]) => TEMPLATE_UNSAFE.test(reference));
|
|
137
|
+
const safe = entries.filter(([, reference]) => !TEMPLATE_UNSAFE.test(reference));
|
|
138
|
+
const refused = unsafe.map(([key, reference]) => ({ key, reference, reason: "malformed op:// reference" }));
|
|
139
|
+
if (safe.length === 0)
|
|
140
|
+
return { values: new Map(), failures: refused };
|
|
141
|
+
try {
|
|
142
|
+
const values = injectBatch(safe, env, token);
|
|
143
|
+
const missing = safe.filter(([key]) => !values.has(key)).map(([key, reference]) => ({ key, reference, reason: "op inject returned no value" }));
|
|
144
|
+
return { values, failures: [...refused, ...missing] };
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
const individual = mergeResults(safe.map(([key, reference]) => readOne(key, reference, env, token)));
|
|
148
|
+
return { values: individual.values, failures: [...refused, ...individual.failures] };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function vaultTitles(settings, env) {
|
|
152
|
+
if (!settings.vault)
|
|
153
|
+
return { titles: new Set(), error: null };
|
|
154
|
+
try {
|
|
155
|
+
return { titles: listVaultItemTitles(settings.vault, env, settings.token.value), error: null };
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return { titles: new Set(), error: `could not list vault ${settings.vault}: ${opErrorMessage(error, settings.token.value)}` };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function emptyResolution(settings) {
|
|
162
|
+
return {
|
|
163
|
+
attempted: false,
|
|
164
|
+
vault: settings.vault,
|
|
165
|
+
tokenSource: settings.token.source,
|
|
166
|
+
fromReferences: [],
|
|
167
|
+
fromVault: [],
|
|
168
|
+
unresolved: [],
|
|
169
|
+
error: null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function resolveOnePasswordEnv(env, managedKeys) {
|
|
173
|
+
const settings = readOnePasswordSettings(env);
|
|
174
|
+
const hasReferences = Object.values(env).some((value) => isOpReference(value));
|
|
175
|
+
if (!hasReferences && !settings.vault)
|
|
176
|
+
return emptyResolution(settings);
|
|
177
|
+
const listing = vaultTitles(settings, env);
|
|
178
|
+
const plan = planReferences(env, managedKeys, settings.vault, listing.titles);
|
|
179
|
+
const result = resolveEntries([...plan.explicit, ...plan.convention], env, settings.token.value);
|
|
180
|
+
for (const [key] of plan.explicit) {
|
|
181
|
+
const value = result.values.get(key);
|
|
182
|
+
if (value === undefined)
|
|
183
|
+
delete env[key];
|
|
184
|
+
else
|
|
185
|
+
env[key] = value;
|
|
186
|
+
}
|
|
187
|
+
for (const [key] of plan.convention) {
|
|
188
|
+
const value = result.values.get(key);
|
|
189
|
+
if (value !== undefined)
|
|
190
|
+
env[key] = value;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
...emptyResolution(settings),
|
|
194
|
+
attempted: true,
|
|
195
|
+
fromReferences: plan.explicit.map(([key]) => key).filter((key) => result.values.has(key)),
|
|
196
|
+
fromVault: plan.convention.map(([key]) => key).filter((key) => result.values.has(key)),
|
|
197
|
+
unresolved: result.failures,
|
|
198
|
+
error: listing.error,
|
|
199
|
+
};
|
|
200
|
+
}
|
package/dist/queue.js
CHANGED
|
@@ -517,6 +517,10 @@ export function enqueueOptions(jobId, payload) {
|
|
|
517
517
|
artifactWorkOrientation: classified?.orientation ?? null,
|
|
518
518
|
};
|
|
519
519
|
}
|
|
520
|
+
export function jobIdForKey(jobKey, bookId) {
|
|
521
|
+
const entry = jobIdIndex.get(jobKey);
|
|
522
|
+
return entry && entry.bookId === bookId ? entry.internalId : null;
|
|
523
|
+
}
|
|
520
524
|
function rememberJob(jobId, entry) {
|
|
521
525
|
jobIdIndex.set(jobId, entry);
|
|
522
526
|
while (jobIdIndex.size > JOB_INDEX_MAX) {
|
package/dist/serve.js
CHANGED
|
@@ -3,45 +3,19 @@ import { setTimeout as delay } from "node:timers/promises";
|
|
|
3
3
|
import { stopAceServer } from "./acestep/process.js";
|
|
4
4
|
import { createApp } from "./app.js";
|
|
5
5
|
import { bookManager, scopedEngine } from "./engine.js";
|
|
6
|
+
import { getDefaultSecretStore } from "./env-secrets.js";
|
|
7
|
+
import { hydrateLibrary } from "./hydration.js";
|
|
6
8
|
import { registerAllJobHandlers } from "./jobs/index.js";
|
|
7
9
|
import { logger } from "./logger.js";
|
|
8
|
-
import {
|
|
10
|
+
import { stopPostMetricsScheduler } from "./posts/post-metrics.js";
|
|
9
11
|
import { queue } from "./queue.js";
|
|
10
|
-
import {
|
|
12
|
+
import { stopScheduler } from "./scheduled-posts.js";
|
|
11
13
|
import { SERVER_VERSION } from "./server.js";
|
|
12
14
|
import { ServerAlreadyRunningError, acquireServerLock, inspectServerOwner, } from "./server-lock.js";
|
|
13
15
|
import { attachVoiceAudio } from "./voice-audio.js";
|
|
14
16
|
const LOOPBACK_HOST = "127.0.0.1";
|
|
15
17
|
const SHUTDOWN_WATCHDOG_MS = 20_000;
|
|
16
18
|
const HTTP_CLOSE_GRACE_MS = 2_000;
|
|
17
|
-
function openBooks(books) {
|
|
18
|
-
return books.list().flatMap((book) => {
|
|
19
|
-
const selected = books.get(book.bookId);
|
|
20
|
-
return selected.ok ? [selected.value] : [];
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
async function hydrateLibrary(books, options) {
|
|
24
|
-
await books.ready();
|
|
25
|
-
if (options.devFixtures) {
|
|
26
|
-
const { ensureDancingWorkflow } = await import("./dancing-workflow.js");
|
|
27
|
-
await ensureDancingWorkflow(books);
|
|
28
|
-
}
|
|
29
|
-
const imageSubjects = await import("./jobs/image-subjects.js");
|
|
30
|
-
const runNotebook = await import("./jobs/run-notebook.js");
|
|
31
|
-
for (const book of openBooks(books)) {
|
|
32
|
-
await book.engine.jobs.queue.reap();
|
|
33
|
-
await imageSubjects.reconcileImageSubjectJobFailures(book.engine, book.bookId);
|
|
34
|
-
await runNotebook.abortLeasedNotebookRunsOnStartup(book.engine);
|
|
35
|
-
await runNotebook.reconcileGenerationMediaOutputs(book.engine);
|
|
36
|
-
await runNotebook.reconcileQueuedNotebookCells(book.engine);
|
|
37
|
-
}
|
|
38
|
-
await queue.start();
|
|
39
|
-
for (const book of openBooks(books)) {
|
|
40
|
-
await runNotebook.reconcileAwaitingNotebookOutputs(book.engine, book.bookId);
|
|
41
|
-
}
|
|
42
|
-
await loadAndArmAll();
|
|
43
|
-
startPostMetricsScheduler();
|
|
44
|
-
}
|
|
45
19
|
async function shutdownServices(services) {
|
|
46
20
|
stopScheduler();
|
|
47
21
|
stopPostMetricsScheduler();
|
|
@@ -110,6 +84,7 @@ async function claimLibrary(server, libraryDir, port, url) {
|
|
|
110
84
|
export async function startServer(options) {
|
|
111
85
|
const libraryDir = bookManager.rootDir;
|
|
112
86
|
await refuseRunningOwner(libraryDir);
|
|
87
|
+
getDefaultSecretStore().resolveOnePassword();
|
|
113
88
|
registerAllJobHandlers(scopedEngine);
|
|
114
89
|
const services = createApp({ ui: options.ui, uiDir: options.uiDir });
|
|
115
90
|
const server = services.app.listen(options.port, LOOPBACK_HOST);
|
|
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
import { v7 as uuidv7 } from "uuid";
|
|
7
7
|
import { ensureGenerateAudioOutput, normalizeLegacyExtractCells, } from "../notebook-cell-outputs.js";
|
|
8
8
|
import { mutateNotebook, withNotebookMutation } from "../notebook-mutations.js";
|
|
9
|
-
import { queue, stopBookQueue } from "../queue.js";
|
|
9
|
+
import { jobIdForKey, queue, stopBookQueue } from "../queue.js";
|
|
10
10
|
import { claimNotebookGenerationPlan, failQueuedNotebookCells, generationPlanClaimOutputCellId, notebookCellDefinitionFingerprint, notebookCellIsPaid, notebookCellIsRunnable, notebookCellMediaType, markNotebookCellQueued, prepareNotebookGeneration, } from "../jobs/run-notebook.js";
|
|
11
11
|
import { buildGenerationInsertPlan, continueQueuedInsert, } from "./command-plan.js";
|
|
12
12
|
import { assessToolCallEstimate, logUnavailableCostEstimate, } from "../jobs/cost-estimate-assessment.js";
|
|
@@ -271,6 +271,24 @@ function pinnedResultFromInput(input) {
|
|
|
271
271
|
createdAt: input.created_at,
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
|
+
function jobsByIdOrKey(engine, bookId, ids, keys) {
|
|
275
|
+
const byId = ids.map((id) => ({ id, jobKey: null }));
|
|
276
|
+
const byKey = keys.flatMap((jobKey) => {
|
|
277
|
+
const id = jobIdForKey(jobKey, bookId);
|
|
278
|
+
return id === null ? [] : [{ id, jobKey }];
|
|
279
|
+
});
|
|
280
|
+
return [...byId, ...byKey].flatMap(({ id, jobKey }) => {
|
|
281
|
+
const job = engine.jobs.queue.get(id);
|
|
282
|
+
return job ? [{ job, jobKey }] : [];
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function selectJobs(engine, args) {
|
|
286
|
+
const ids = Array.isArray(args.job_ids) ? args.job_ids.map(Number) : null;
|
|
287
|
+
const keys = Array.isArray(args.job_keys) ? args.job_keys.map(String) : null;
|
|
288
|
+
if (!ids && !keys)
|
|
289
|
+
return engine.jobs.queue.list({ limit: Number(args.limit) || 200 }).map((job) => ({ job, jobKey: null }));
|
|
290
|
+
return jobsByIdOrKey(engine, String(args.book_id), ids ?? [], keys ?? []);
|
|
291
|
+
}
|
|
274
292
|
function json(value) {
|
|
275
293
|
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
276
294
|
}
|
|
@@ -1898,21 +1916,18 @@ export function videobookDomainDefs(engine, books) {
|
|
|
1898
1916
|
},
|
|
1899
1917
|
{
|
|
1900
1918
|
name: "list_jobs",
|
|
1901
|
-
description: "List persistent jobs for a book",
|
|
1919
|
+
description: "List persistent jobs for a book. Filter by numeric job_ids, or by job_keys, the job_id strings other tools return while the server that queued them is running.",
|
|
1902
1920
|
scope: "book",
|
|
1903
1921
|
schema: {
|
|
1904
1922
|
...bookSchema,
|
|
1905
1923
|
limit: z.number().int().positive().max(500).optional(),
|
|
1906
1924
|
job_ids: z.array(z.number().int().positive()).min(1).max(100).optional(),
|
|
1925
|
+
job_keys: z.array(z.string().min(1).max(500)).min(1).max(100).optional(),
|
|
1907
1926
|
},
|
|
1908
1927
|
handler: async (args) => {
|
|
1909
|
-
const jobs =
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
})
|
|
1913
|
-
: engine.jobs.queue.list({ limit: Number(args.limit) || 200 });
|
|
1914
|
-
return json(await Promise.all(jobs.map(async (job) => ({
|
|
1915
|
-
...job, providerTask: await jobProviderTaskStatus(engine, job),
|
|
1928
|
+
const jobs = selectJobs(engine, args);
|
|
1929
|
+
return json(await Promise.all(jobs.map(async ({ job, jobKey }) => ({
|
|
1930
|
+
...job, ...(jobKey ? { jobKey } : {}), providerTask: await jobProviderTaskStatus(engine, job),
|
|
1916
1931
|
}))));
|
|
1917
1932
|
},
|
|
1918
1933
|
},
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
|
+
import { FFMPEG } from "./ffmpeg-binary.js";
|
|
5
|
+
const YTDLP_OVERRIDE = "VIDEOBOOK_YTDLP_PATH";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
export const YTDLP_MISSING_MESSAGE = "yt-dlp is not installed. Put yt-dlp on PATH (for example brew install yt-dlp), set VIDEOBOOK_YTDLP_PATH, "
|
|
8
|
+
+ "or reinstall with npm install -g videobook --allow-scripts=ffmpeg-static,ytdlp-nodejs,@dolthub/doltlite,usearch,onnxruntime-node,esbuild";
|
|
9
|
+
function isExecutableFile(path) {
|
|
10
|
+
try {
|
|
11
|
+
if (!statSync(path).isFile())
|
|
12
|
+
return false;
|
|
13
|
+
accessSync(path, constants.X_OK);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function executableNames(name, env) {
|
|
21
|
+
if (process.platform !== "win32")
|
|
22
|
+
return [name];
|
|
23
|
+
const extensions = (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
|
|
24
|
+
return [name, ...extensions.map((extension) => `${name}${extension.toLowerCase()}`)];
|
|
25
|
+
}
|
|
26
|
+
export function findOnPath(name, env = process.env) {
|
|
27
|
+
const directories = (env.PATH ?? "").split(delimiter).filter((directory) => isAbsolute(directory));
|
|
28
|
+
for (const directory of directories) {
|
|
29
|
+
const match = executableNames(name, env).map((candidate) => join(directory, candidate)).find(isExecutableFile);
|
|
30
|
+
if (match)
|
|
31
|
+
return match;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
export function bundledYtdlp() {
|
|
36
|
+
try {
|
|
37
|
+
const { helpers } = require("ytdlp-nodejs");
|
|
38
|
+
const path = helpers.findYtdlpBinary();
|
|
39
|
+
return path && isExecutableFile(path) ? path : null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function resolveYtdlp(env = process.env, bundled = bundledYtdlp()) {
|
|
46
|
+
const override = env[YTDLP_OVERRIDE]?.trim();
|
|
47
|
+
if (override)
|
|
48
|
+
return { path: override, source: "VIDEOBOOK_YTDLP_PATH" };
|
|
49
|
+
if (bundled)
|
|
50
|
+
return { path: bundled, source: "bundled" };
|
|
51
|
+
const onPath = findOnPath("yt-dlp", env);
|
|
52
|
+
return onPath ? { path: onPath, source: "PATH" } : null;
|
|
53
|
+
}
|
|
54
|
+
function ffmpegForYtdlp(env) {
|
|
55
|
+
if (isAbsolute(FFMPEG))
|
|
56
|
+
return isExecutableFile(FFMPEG) ? FFMPEG : null;
|
|
57
|
+
return findOnPath(FFMPEG, env);
|
|
58
|
+
}
|
|
59
|
+
export function ytdlpRunOptions(env = process.env, bundled = bundledYtdlp()) {
|
|
60
|
+
const binary = resolveYtdlp(env, bundled);
|
|
61
|
+
if (!binary)
|
|
62
|
+
throw new Error(YTDLP_MISSING_MESSAGE);
|
|
63
|
+
const ffmpeg = ffmpegForYtdlp(env);
|
|
64
|
+
return { binaryPath: binary.path, ...(ffmpeg ? { ffmpegPath: ffmpeg } : {}) };
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "videobook",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
"cli"
|
|
14
14
|
],
|
|
15
15
|
"license": "MIT",
|
|
16
|
-
"homepage": "https://
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/justintanner/videobook.git",
|
|
20
|
-
"directory": "server"
|
|
21
|
-
},
|
|
22
|
-
"bugs": {
|
|
23
|
-
"url": "https://github.com/justintanner/videobook/issues"
|
|
24
|
-
},
|
|
16
|
+
"homepage": "https://www.npmjs.com/package/videobook",
|
|
25
17
|
"type": "module",
|
|
26
18
|
"bin": {
|
|
27
19
|
"videobook": "bin/videobook.js"
|
|
@@ -57,6 +49,7 @@
|
|
|
57
49
|
"@apicity/x": "^0.16.1",
|
|
58
50
|
"@apicity/xai": "^0.16.1",
|
|
59
51
|
"@devmehq/open-graph-extractor": "^1.1.1",
|
|
52
|
+
"@ffprobe-installer/ffprobe": "2.1.2",
|
|
60
53
|
"@modelcontextprotocol/client": "^2.2.0",
|
|
61
54
|
"@modelcontextprotocol/node": "2.0.0",
|
|
62
55
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
@@ -68,7 +61,6 @@
|
|
|
68
61
|
"express": "^5.2.1",
|
|
69
62
|
"ffmpeg-static": "^5.3.0",
|
|
70
63
|
"ffmpeggy": "^3.1.3",
|
|
71
|
-
"ffprobe-static": "^3.1.0",
|
|
72
64
|
"heic-decode": "^2.1.0",
|
|
73
65
|
"hono": "^4.12.31",
|
|
74
66
|
"qrcode": "^1.5.4",
|
|
@@ -77,7 +69,7 @@
|
|
|
77
69
|
"remotion": "4.0.494",
|
|
78
70
|
"sharp": "0.35.5",
|
|
79
71
|
"uuid": "^14.0.0",
|
|
80
|
-
"videobook-engine": "5.7.
|
|
72
|
+
"videobook-engine": "5.7.1",
|
|
81
73
|
"ws": "^8.21.0",
|
|
82
74
|
"ytdlp-nodejs": "^3.4.4",
|
|
83
75
|
"zod": "^4.4.3"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as F,j as t,a as A}from"./vendor-react-Dj7D1fD4.js";import{u as be,a as xe,V as Se,A as pe,P as Ne}from"./vendor-remotion-CH1HXrlc.js";import{ea as Ce,eb as ke,ec as we,ed as $e,ee as Pe,ef as de,eg as Te,eh as me,ei as Re,ej as Ae,ek as Me,el as _e,em as Ee,en as Oe,eo as fe,bu as We}from"./videobook-Qlp9HQFu.js";import{g as Le,a6 as ze,_ as Fe,h as He,X as Ie}from"./vendor-icons-CGqhCIXV.js";import"./vendor-DfTCWgKm.js";import"./vendor-mcp-BTlEJcHV.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}=xe();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=pe,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}=xe();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 x;e[27]!==c||e[28]!==i?(x=t.jsx(we,{cues:c,settings:i,strokeWidth:6,strokeColor:"#000000",shadowBlur:6,shadowOpacity:60}),e[27]=c,e[28]=i,e[29]=x):x=e[29];let k;return e[30]!==N||e[31]!==P||e[32]!==x?(k=t.jsxs(pe,{style:$,children:[N,P,x]}),e[30]=N,e[31]=P,e[32]=x,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"}],Ge=[{name:"Green",color:"#72E7AA"},{name:"Pink",color:"#F0B9D0"},{name:"Cyan",color:"#63C7D8"},{name:"Amber",color:"#E7C372"}];function Xe(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 qe(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:Xe(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 x;e[30]!==i||e[31]!==n?(x=t.jsx(re,{checked:n,onChange:i,children:"Show detected regions"}),e[30]=i,e[31]=n,e[32]=x):x=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]!==x||e[38]!==k?(M=t.jsxs("div",{className:"alive-field",children:[w,b,P,x,k]}),e[35]=b,e[36]=P,e[37]=x,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=Ge.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 p;e[51]!==l?(p=S=>l({textColor:S.target.value.toUpperCase()}),e[51]=l,e[52]=p):p=e[52];let L;e[53]!==s.textColor||e[54]!==p?(L=t.jsxs("label",{className:"alive-color-input",children:[R,t.jsx("input",{type:"color",value:s.textColor,onChange:p})]}),e[53]=s.textColor,e[54]=p,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 x;e[17]!==N?(x=t.jsx("button",{type:"button","aria-label":"Next frame",onClick:N,children:P}),e[17]=N,e[18]=x):x=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 p;e[31]!==W||e[32]!==R?(p=t.jsx("span",{className:"alive-drift","data-drifted":W,children:R}),e[31]=W,e[32]=R,e[33]=p):p=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]!==p||e[42]!==B||e[43]!==y||e[44]!==b||e[45]!==x?(S=t.jsxs("div",{className:"alive-transport",children:[y,b,x,M,_,H,p,B]}),e[39]=M,e[40]=_,e[41]=p,e[42]=B,e[43]=y,e[44]=b,e[45]=x,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 G;e[52]!==V?(G=t.jsx("div",{className:"alive-lane-ruler",children:V}),e[52]=V,e[53]=G):G=e[53];let X;e[54]!==l?(X=Te(l),e[54]=l,e[55]=X):X=e[55];let q;e[56]!==X?(q=t.jsx("svg",{viewBox:"0 0 1000 100",preserveAspectRatio:"none","aria-hidden":"true",children:t.jsx("path",{d:X})}),e[56]=X,e[57]=q):q=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]!==q||e[63]!==Y?(J=t.jsxs("div",{className:"alive-lane-audio",children:[q,Y]}),e[62]=q,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]!==G||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:[G,J,Z,te,le]}),e[90]=j,e[91]=K,e[92]=G,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]),x=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:x,phrases:l.phrases,settings:r,showRegions:g}),[a.mediaUrl,l,x,r,g]),O=Oe(x,N),_=p=>w.current?.seekTo(Math.max(0,Math.min(h-1,Math.round(p*i)))),H=p=>f(L=>({...L,...p})),W=async(p,L,I)=>{v(p),y(null);try{await L(),y(I)}catch(z){y(z instanceof Error?z.message:String(z))}finally{v(null)}},R=fe.find(p=>p.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(p=>t.jsx("button",{type:"button","aria-pressed":p.id===m,onClick:()=>d(p.id),children:p.label},p.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(qe,{rows:k,phraseLabels:x.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(x[O])} · ${x[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:x,activeCue:O,snapped:r.snapToOnsets,drift:M,onSeek:_,onStep:p=>_(N+p/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 xe,V as Se,A as pe,P as Ne}from"./vendor-remotion-CH1HXrlc.js";import{ea as Ce,eb as ke,ec as we,ed as $e,ee as Pe,ef as de,eg as Te,eh as me,ei as Re,ej as Ae,ek as Me,el as _e,em as Ee,en as Oe,eo as fe,bu as We}from"./videobook-lhLK6e6Q.js";import{g as Le,a6 as ze,_ as Fe,h as He,X as Ie}from"./vendor-icons-CGqhCIXV.js";import"./vendor-OClqq_Da.js";import"./vendor-mcp-BsGNAD4I.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}=xe();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=pe,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}=xe();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 x;e[27]!==c||e[28]!==i?(x=t.jsx(we,{cues:c,settings:i,strokeWidth:6,strokeColor:"#000000",shadowBlur:6,shadowOpacity:60}),e[27]=c,e[28]=i,e[29]=x):x=e[29];let k;return e[30]!==N||e[31]!==P||e[32]!==x?(k=t.jsxs(pe,{style:$,children:[N,P,x]}),e[30]=N,e[31]=P,e[32]=x,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"}],Ge=[{name:"Green",color:"#72E7AA"},{name:"Pink",color:"#F0B9D0"},{name:"Cyan",color:"#63C7D8"},{name:"Amber",color:"#E7C372"}];function Xe(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 qe(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:Xe(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 x;e[30]!==i||e[31]!==n?(x=t.jsx(re,{checked:n,onChange:i,children:"Show detected regions"}),e[30]=i,e[31]=n,e[32]=x):x=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]!==x||e[38]!==k?(M=t.jsxs("div",{className:"alive-field",children:[w,b,P,x,k]}),e[35]=b,e[36]=P,e[37]=x,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=Ge.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 p;e[51]!==l?(p=S=>l({textColor:S.target.value.toUpperCase()}),e[51]=l,e[52]=p):p=e[52];let L;e[53]!==s.textColor||e[54]!==p?(L=t.jsxs("label",{className:"alive-color-input",children:[R,t.jsx("input",{type:"color",value:s.textColor,onChange:p})]}),e[53]=s.textColor,e[54]=p,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 x;e[17]!==N?(x=t.jsx("button",{type:"button","aria-label":"Next frame",onClick:N,children:P}),e[17]=N,e[18]=x):x=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 p;e[31]!==W||e[32]!==R?(p=t.jsx("span",{className:"alive-drift","data-drifted":W,children:R}),e[31]=W,e[32]=R,e[33]=p):p=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]!==p||e[42]!==B||e[43]!==y||e[44]!==b||e[45]!==x?(S=t.jsxs("div",{className:"alive-transport",children:[y,b,x,M,_,H,p,B]}),e[39]=M,e[40]=_,e[41]=p,e[42]=B,e[43]=y,e[44]=b,e[45]=x,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 G;e[52]!==V?(G=t.jsx("div",{className:"alive-lane-ruler",children:V}),e[52]=V,e[53]=G):G=e[53];let X;e[54]!==l?(X=Te(l),e[54]=l,e[55]=X):X=e[55];let q;e[56]!==X?(q=t.jsx("svg",{viewBox:"0 0 1000 100",preserveAspectRatio:"none","aria-hidden":"true",children:t.jsx("path",{d:X})}),e[56]=X,e[57]=q):q=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]!==q||e[63]!==Y?(J=t.jsxs("div",{className:"alive-lane-audio",children:[q,Y]}),e[62]=q,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]!==G||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:[G,J,Z,te,le]}),e[90]=j,e[91]=K,e[92]=G,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]),x=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:x,phrases:l.phrases,settings:r,showRegions:g}),[a.mediaUrl,l,x,r,g]),O=Oe(x,N),_=p=>w.current?.seekTo(Math.max(0,Math.min(h-1,Math.round(p*i)))),H=p=>f(L=>({...L,...p})),W=async(p,L,I)=>{v(p),y(null);try{await L(),y(I)}catch(z){y(z instanceof Error?z.message:String(z))}finally{v(null)}},R=fe.find(p=>p.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(p=>t.jsx("button",{type:"button","aria-pressed":p.id===m,onClick:()=>d(p.id),children:p.label},p.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(qe,{rows:k,phraseLabels:x.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(x[O])} · ${x[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:x,activeCue:O,snapped:r.snapToOnsets,drift:M,onSeek:_,onStep:p=>_(N+p/i),onTogglePlay:()=>w.current?.toggle(),onToggleSnap:()=>H({snapToOnsets:!r.snapToOnsets})})]})}export{ft as AliveCaptionsStudio};
|