@ignix-social/editor 0.1.8 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ignix-social/editor",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Ignix Editor: render HTML video compositions (reels, edits, clips) on your own computer. Based on HyperFrames.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/cli.mjs CHANGED
@@ -80,7 +80,7 @@ const commands = {
80
80
  async doctor() {
81
81
  const major = Number(process.versions.node.split('.')[0]);
82
82
  log(`${major >= 22 ? '✓' : '✗'} Node ${process.versions.node}${major >= 22 ? '' : ' — Node 22 or newer is required'}`);
83
- log(`✓ Ignix Editor ${PKG.version} · HyperFrames engine ${UPSTREAM.version} (patches: ${UPSTREAM.patches.join(', ')})`);
83
+ log(`✓ Ignix Editor ${PKG.version} · engine ${UPSTREAM.version} (patches: ${UPSTREAM.patches.join(', ')})`);
84
84
  log(` Platform ${PLATFORM} · data in ${HOME}`);
85
85
  let ok = major >= 22;
86
86
  // doctor deja la máquina lista: descarga lo que falte en vez de solo avisar.
@@ -304,17 +304,22 @@ const commands = {
304
304
  process.exit(r.status ?? 1);
305
305
  },
306
306
 
307
+ // La skill donde la buscan los agentes: Claude (~/.claude/skills) y Codex y otros
308
+ // (~/.agents/skills). Con --dir, solo ahí.
307
309
  async skills(argv) {
308
310
  const { pos, opts } = parseArgs(argv, { dir: 'str' });
309
- if (pos[0] !== 'add') fail('Usage: ignix-editor skills add [--dir ~/.claude/skills]');
310
- const target = resolve(opts.dir ?? join(homedir(), '.claude', 'skills'), 'ignix-editor');
311
- rmSync(target, { recursive: true, force: true });
312
- cpSync(join(PKG_DIR, 'skills', 'ignix-editor'), target, { recursive: true });
313
- log(`✓ Skill installed in ${target}`);
311
+ if (pos[0] !== 'add') fail('Usage: ignix-editor skills add [--dir <skills folder>]');
312
+ const dirs = opts.dir ? [resolve(opts.dir)] : [join(homedir(), '.claude', 'skills'), join(homedir(), '.agents', 'skills')];
313
+ for (const dir of dirs) {
314
+ const target = join(dir, 'ignix-editor');
315
+ rmSync(target, { recursive: true, force: true });
316
+ cpSync(join(PKG_DIR, 'skills', 'ignix-editor'), target, { recursive: true });
317
+ log(`✓ Skill installed in ${target}`);
318
+ }
314
319
  },
315
320
 
316
321
  async version() {
317
- console.log(`ignix-editor ${PKG.version} (HyperFrames engine ${UPSTREAM.version})`);
322
+ console.log(`ignix-editor ${PKG.version} (engine ${UPSTREAM.version})`);
318
323
  },
319
324
 
320
325
  async help() {
@@ -334,7 +339,7 @@ const commands = {
334
339
  ignix-editor fetch [--whisper small] [--no-analysis] downloads everything up front (each file SHA-256-checked)
335
340
  ignix-editor serve local service for Ignix Creator (JSON lines on stdin/stdout)
336
341
 
337
- The folder defaults to the current one. Based on HyperFrames (Apache-2.0) by HeyGen, Inc. See NOTICE.`);
342
+ The folder defaults to the current one. Open source (Apache-2.0); credits in NOTICE.`);
338
343
  },
339
344
  };
340
345
 
package/src/engine.mjs CHANGED
@@ -80,15 +80,23 @@ export async function renderProject(dir, output, { lufs = -14, normalize = true,
80
80
  mkdirSync(dirname(output), { recursive: true });
81
81
  const raw = join(build, 'raw.mp4');
82
82
  const args = ['render', build, '-o', raw, ...engineArgs];
83
- // Avance: «Capturing frame N/M» del motor; luego codifica y monta.
83
+ // Avance: «Capturing frame N/M» del motor; luego codifica y monta. Y si la animación no llegó a
84
+ // registrarse, el motor renderiza igual (con un aviso) un vídeo congelado: eso es un fallo.
85
+ let frozen = false;
84
86
  const onLine = (line) => {
87
+ if (/sub_timeline_readiness_timeout|still unregistered|Root timeline not bound/.test(line)) frozen = true;
85
88
  const m = line.match(/Capturing frame (\d+)\/(\d+)/);
86
89
  if (m) onProgress?.({ stage: 'rendering', fraction: Math.min(0.99, Number(m[1]) / Math.max(1, Number(m[2]))) });
87
90
  else if (/Encoding video|Assembling final video/.test(line)) onProgress?.({ stage: 'encoding', fraction: 0.95 });
88
91
  };
89
- const code = onProgress || signal ? await runEngineAsync(args, env, dir, { onLine, signal }) : runEngine(args, env, dir, { quiet });
92
+ const code = await runEngineAsync(args, env, dir, { onLine, signal });
90
93
  if (signal?.aborted) throw Object.assign(new Error('cancelled'), { code: 'cancelled' });
91
94
  if (code !== 0 || !existsSync(raw)) throw new Error('The render failed (see the messages above).');
95
+ if (frozen) {
96
+ throw new Error(
97
+ 'The animation never started, so the video would stay frozen on its first frame. Usually GSAP is not loaded before ignix-frame.js, or IgnixFrame.run threw an error: run `ignix-editor check` and fix what it reports, then render again.',
98
+ );
99
+ }
92
100
  let level = null;
93
101
  if (normalize !== false && hasAudio(ffprobe, raw)) {
94
102
  const r = normalizeLoudness(ffmpeg, raw, output, { lufs: Number(lufs ?? -14) });
@@ -12,6 +12,12 @@
12
12
  * `id` es el data-composition-id del nodo raíz. Necesita GSAP cargado antes.
13
13
  */
14
14
  (function () {
15
+ // GSAP va antes que este fichero (la plantilla de `init` lo pone). Si un agente reescribió el
16
+ // HTML sin él, se carga aquí mientras la página aún se lee: sin GSAP nada se movería y el
17
+ // vídeo saldría congelado en su primer fotograma (pasó dos veces, 2026-10-06).
18
+ if (!window.gsap && document.readyState === 'loading') {
19
+ document.write('<script src="assets/gsap.min.js"><\/script>');
20
+ }
15
21
  const clamp01 = (x) => Math.min(1, Math.max(0, x));
16
22
 
17
23
  // Curva cúbica de Bézier como en CSS (x1, y1, x2, y2).
package/src/validate.mjs CHANGED
@@ -3,7 +3,8 @@
3
3
  // · <video>/<audio> creados desde JS (el extractor lee el HTML, no la página viva: salen negros o mudos);
4
4
  // · recursos de internet (el render no debe depender de la red: fuentes que llegan tarde);
5
5
  // · ids que el motor trata de forma especial (id="caption" cambió la fuente de los subtítulos).
6
- import { readFileSync } from 'node:fs';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { dirname, join } from 'node:path';
7
8
  import { parse } from 'parse5';
8
9
 
9
10
  // Avisos de parse5 que no cambian cómo se pinta la página.
@@ -65,7 +66,9 @@ export function validateHtml(file) {
65
66
  if (frame) {
66
67
  const gsap = scripts.find((x) => /gsap(\.min)?\.js$/i.test(x.src));
67
68
  if (!gsap || gsap.at > frame.at) {
68
- issues.push({ level: 'error', line: lineOf(src, frame.at), rule: 'gsap_before_frame', msg: `ignix-frame.js needs GSAP loaded first. Put <script src="assets/gsap.min.js"></script> right above <script src="ignix-frame.js"></script> (as \`ignix-editor init\` writes it).` });
69
+ // ignix-frame.js lo carga solo desde assets/gsap.min.js: si el fichero está, es un aviso.
70
+ const there = existsSync(join(dirname(file), 'assets', 'gsap.min.js'));
71
+ issues.push({ level: there ? 'warning' : 'error', line: lineOf(src, frame.at), rule: 'gsap_before_frame', msg: `ignix-frame.js needs GSAP loaded first. Put <script src="assets/gsap.min.js"></script> right above <script src="ignix-frame.js"></script> (as \`ignix-editor init\` writes it)${there ? '. ignix-frame.js loads it from assets/ meanwhile.' : ': assets/gsap.min.js is missing too.'}` });
69
72
  }
70
73
  }
71
74
  for (const m of src.matchAll(/@import\s+url\(\s*["']?https?:|url\(\s*["']?https?:\/\/fonts\./gi)) {