@ignix-social/editor 0.1.5 → 0.1.7

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/src/engine.mjs ADDED
@@ -0,0 +1,98 @@
1
+ // El motor (HyperFrames, en vendor/) con nuestro entorno: sin telemetría, sin actualizaciones
2
+ // solas, con nuestros binarios; y el render completo de un proyecto (copia de trabajo, motor,
3
+ // volumen a −14 LUFS). Lo usan el CLI y el servicio para Creator.
4
+ import { spawn, spawnSync } from 'node:child_process';
5
+ import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { HOME, PLATFORM, TOOLS, ensureTool, findTool } from './binaries.mjs';
9
+ import { linkMedia, prepare } from './prepare.mjs';
10
+ import { hasAudio, loudness, normalizeLoudness } from './audio.mjs';
11
+
12
+ const PKG_DIR = join(dirname(fileURLToPath(import.meta.url)), '..');
13
+ export const ENGINE = join(PKG_DIR, 'vendor', 'hyperframes', 'bin', 'hyperframes.mjs');
14
+
15
+ export async function engineEnv(log = () => {}) {
16
+ const env = {
17
+ ...process.env,
18
+ HYPERFRAMES_NO_TELEMETRY: '1',
19
+ DO_NOT_TRACK: '1',
20
+ HYPERFRAMES_NO_UPDATE_CHECK: '1',
21
+ HYPERFRAMES_NO_AUTO_INSTALL: '1',
22
+ HYPERFRAMES_SKIP_SKILLS: '1',
23
+ HYPERFRAMES_FONT_CACHE_DIR: join(HOME, 'cache', 'fonts'),
24
+ };
25
+ const ffmpeg = await ensureTool('ffmpeg', log);
26
+ const ffprobe = await ensureTool('ffprobe', log);
27
+ env.HYPERFRAMES_FFMPEG_PATH = ffmpeg;
28
+ env.HYPERFRAMES_FFPROBE_PATH = ffprobe;
29
+ const whisper = findTool('whisper');
30
+ if (whisper.path) env.HYPERFRAMES_WHISPER_PATH = whisper.path;
31
+ // Por si alguna parte del motor llama a «ffmpeg» por su nombre: los nuestros van primero en el
32
+ // PATH. Uno por proceso (Creator puede renderizar y analizar a la vez), borrado al salir.
33
+ const shim = join(HOME, 'shim', `${PLATFORM}-${process.pid}`);
34
+ rmSync(shim, { recursive: true, force: true });
35
+ mkdirSync(shim, { recursive: true });
36
+ process.once('exit', () => rmSync(shim, { recursive: true, force: true }));
37
+ linkMedia(ffmpeg, join(shim, TOOLS.ffmpeg.file));
38
+ linkMedia(ffprobe, join(shim, TOOLS.ffprobe.file));
39
+ env.PATH = `${shim}${process.platform === 'win32' ? ';' : ':'}${env.PATH ?? ''}`;
40
+ return { env, ffmpeg, ffprobe };
41
+ }
42
+
43
+ /** Lanza el motor. `quiet`: su salida va a stderr (el servicio reserva stdout al protocolo). */
44
+ export function runEngine(args, env, cwd, { quiet = false } = {}) {
45
+ const r = spawnSync(process.execPath, [ENGINE, ...args], { stdio: quiet ? ['ignore', 2, 2] : 'inherit', env, cwd });
46
+ return r.status ?? 1;
47
+ }
48
+
49
+ /**
50
+ * Lanza el motor sin bloquear: su salida va a stderr y cada línea pasa por `onLine` (para leer
51
+ * el avance). `signal` lo para (SIGTERM; el motor cierra su navegador).
52
+ */
53
+ export function runEngineAsync(args, env, cwd, { onLine = () => {}, signal } = {}) {
54
+ return new Promise((resolve) => {
55
+ const p = spawn(process.execPath, [ENGINE, ...args], { stdio: ['ignore', 'pipe', 'pipe'], env, cwd });
56
+ const relay = (d) => {
57
+ process.stderr.write(d);
58
+ for (const line of String(d).split(/[\r\n]+/)) if (line.trim()) onLine(line);
59
+ };
60
+ p.stdout.on('data', relay);
61
+ p.stderr.on('data', relay);
62
+ const stop = () => p.kill('SIGTERM');
63
+ signal?.addEventListener('abort', stop, { once: true });
64
+ p.on('close', (code) => {
65
+ signal?.removeEventListener('abort', stop);
66
+ resolve(code ?? 1);
67
+ });
68
+ p.on('error', () => resolve(1));
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Renderiza el proyecto `dir` en `output`: copia de trabajo con los tiempos ajustados, el motor
74
+ * y, si hay sonido, la mezcla a `lufs` (−14 por defecto; `normalize: false` lo deja tal cual).
75
+ */
76
+ export async function renderProject(dir, output, { lufs = -14, normalize = true, engineArgs = [], quiet = false, onProgress, signal, browser, log = () => {} } = {}) {
77
+ const { build, snapped } = prepare(dir);
78
+ const { env, ffmpeg, ffprobe } = await engineEnv(log);
79
+ if (browser) env.HYPERFRAMES_BROWSER_PATH = browser;
80
+ mkdirSync(dirname(output), { recursive: true });
81
+ const raw = join(build, 'raw.mp4');
82
+ const args = ['render', build, '-o', raw, ...engineArgs];
83
+ // Avance: «Capturing frame N/M» del motor; luego codifica y monta.
84
+ const onLine = (line) => {
85
+ const m = line.match(/Capturing frame (\d+)\/(\d+)/);
86
+ if (m) onProgress?.({ stage: 'rendering', fraction: Math.min(0.99, Number(m[1]) / Math.max(1, Number(m[2]))) });
87
+ else if (/Encoding video|Assembling final video/.test(line)) onProgress?.({ stage: 'encoding', fraction: 0.95 });
88
+ };
89
+ const code = onProgress || signal ? await runEngineAsync(args, env, dir, { onLine, signal }) : runEngine(args, env, dir, { quiet });
90
+ if (signal?.aborted) throw Object.assign(new Error('cancelled'), { code: 'cancelled' });
91
+ if (code !== 0 || !existsSync(raw)) throw new Error('The render failed (see the messages above).');
92
+ let level = null;
93
+ if (normalize !== false && hasAudio(ffprobe, raw)) {
94
+ const r = normalizeLoudness(ffmpeg, raw, output, { lufs: Number(lufs ?? -14) });
95
+ level = { before: r.before, after: loudness(ffmpeg, output) };
96
+ } else cpSync(raw, output);
97
+ return { output, snapped, loudness: level };
98
+ }
package/src/fetch.mjs ADDED
@@ -0,0 +1,30 @@
1
+ // `ignix-editor fetch`: descarga de una vez lo que el Editor necesita (ffmpeg, ffprobe, whisper y
2
+ // su modelo, el motor de análisis y sus modelos), cada cosa con su sha256. Ignix Creator lo lanza
3
+ // al instalar el motor de vídeo para que ningún render ni análisis tenga que esperar a una
4
+ // descarga. Con --json informa por stdout con líneas JSON: progress {item, fraction, received,
5
+ // total} (bytes), done, error {code}.
6
+ import { ensureTool, ensureWhisperModel, findTool, toolSize, whisperModelPath, WHISPER_MODELS } from './binaries.mjs';
7
+ import { existsSync } from 'node:fs';
8
+ import { analysisMissing, ensureModel, loadOrt } from './analysis/runtime.mjs';
9
+
10
+ export async function fetchAll({ whisper = 'small', analysis = true, json = false, log = () => {} } = {}) {
11
+ const emit = (e) => json && process.stdout.write(`${JSON.stringify(e)}\n`);
12
+ if (!WHISPER_MODELS[whisper]) throw Object.assign(new Error(`Unknown model: ${whisper}`), { code: 'invalid_args' });
13
+ const todo = [];
14
+ for (const name of ['ffmpeg', 'ffprobe', 'whisper']) {
15
+ if (!findTool(name).path) todo.push({ item: name, size: toolSize(name), run: () => ensureTool(name, log) });
16
+ }
17
+ if (!existsSync(whisperModelPath(whisper))) todo.push({ item: `whisper-${whisper}`, size: WHISPER_MODELS[whisper].size, run: () => ensureWhisperModel(whisper, log) });
18
+ if (analysis) {
19
+ for (const m of analysisMissing()) todo.push({ ...m, run: () => (m.item === 'runtime' ? loadOrt(log) : ensureModel(m.item, log)) });
20
+ }
21
+ const total = todo.reduce((n, t) => n + t.size, 0);
22
+ let done = 0;
23
+ for (const t of todo) {
24
+ emit({ t: 'progress', item: t.item, fraction: +(done / (total || 1)).toFixed(4), received: done, total });
25
+ await t.run();
26
+ done += t.size;
27
+ }
28
+ emit({ t: 'progress', item: 'done', fraction: 1, received: total, total });
29
+ return { downloaded: todo.map((t) => t.item), bytes: todo.reduce((n, t) => n + t.size, 0) };
30
+ }
@@ -8,6 +8,7 @@
8
8
  "no-telemetry",
9
9
  "no-auto-update",
10
10
  "mf-encoder",
11
- "no-desktop-promo"
11
+ "no-desktop-promo",
12
+ "chrome-first-launch"
12
13
  ]
13
14
  }
@@ -211,7 +211,7 @@ async function chromeLaunchOutcome(executablePath, found, signal) {
211
211
  try {
212
212
  const { stdout } = await runCancellableProcess(executablePath, ["--version"], {
213
213
  signal,
214
- timeoutMs: 5e3,
214
+ timeoutMs: 6e4, // ignix-editor: primera apertura de una copia nueva
215
215
  maxBufferBytes: 64 * 1024
216
216
  });
217
217
  return { ...found, versionMajor: extractMajorVersion(stdout) };