@ignix-social/editor 0.1.15 → 0.1.16

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.15",
3
+ "version": "0.1.16",
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",
@@ -7,14 +7,14 @@ description: Build and render videos with Ignix Editor (HTML + GSAP compositions
7
7
 
8
8
  Ignix Editor turns an HTML page into an MP4. You write `index.html` (layout in HTML/CSS, motion in JS), the editor renders it frame by frame in a headless browser and encodes it with ffmpeg. The Ignix playbooks say WHAT to make; this skill says HOW to write it so that what you write is what renders.
9
9
 
10
- Commands (`npx @ignix-social/editor <cmd>` if `ignix-editor` is not on the PATH):
10
+ Commands. `ignix-editor` means the copy the Ignix playbook installed: `~/.ignix-editor/cli/node_modules/.bin/ignix-editor` (Windows PowerShell: `& "$env:USERPROFILE\.ignix-editor\cli\node_modules\.bin\ignix-editor.cmd"`; PowerShell blocks `.ps1` scripts by default, so use the `.cmd` and `npm.cmd`, and `$env:USERPROFILE` instead of `~`). Do not use `npx`: it may fetch another version.
11
11
 
12
12
  | Command | What it does |
13
13
  |---|---|
14
14
  | `ignix-editor doctor` | Node 22+ and the H.264 encoder; downloads its own ffmpeg and whisper the first time. Run it first. |
15
15
  | `ignix-editor init <name>` | Creates `~/ignix-videos/<name>` with a working skeleton, GSAP, local fonts and `ignix-frame.js`. |
16
16
  | `ignix-editor transcribe <file> --lang es [--script "…"] -o words.json` | Words with start/end seconds, on this machine. |
17
- | `ignix-editor faces <video> -o work/faces.json` | Where the face is over time (macOS, Apple Vision): a smoothed path `{t, cx, cy, size}` (0–1, top-left origin) to frame the person. |
17
+ | `ignix-editor faces <video> -o work/faces.json` | Where the face is over time (macOS and Windows; the first run downloads the face detector): a smoothed path `{t, cx, cy, size}` (0–1, top-left origin) to frame the person. |
18
18
  | `ignix-editor check [dir]` | HTML well-formed, no network assets, media rules, layout/contrast/motion audit. |
19
19
  | `ignix-editor snapshot [dir] --at 0,1.5,3` | PNG frames into `<dir>/snapshots/` to LOOK at. `[dir]` defaults to the current folder. |
20
20
  | `ignix-editor render [dir] [-o out.mp4]` | Renders to `<dir>/renders/<dir>.mp4`, audio normalised to −14 LUFS. |
package/src/binaries.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  // (descarga oficial de Google la primera vez).
5
5
  import { createHash } from 'node:crypto';
6
6
  import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
7
- import { Readable } from 'node:stream';
7
+ import { Readable, Transform } from 'node:stream';
8
8
  import { pipeline } from 'node:stream/promises';
9
9
  import { homedir } from 'node:os';
10
10
  import { dirname, join } from 'node:path';
@@ -75,9 +75,40 @@ export async function ensureWhisperModel(model = 'medium', log = () => {}) {
75
75
  if (existsSync(dest)) return dest;
76
76
  mkdirSync(dir, { recursive: true });
77
77
  log(`Downloading the ${model} transcription model (${(m.size / 1e9).toFixed(1)} GB, first time only)…`);
78
- const res = await fetch(`https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${m.file}`);
79
- if (!res.ok) throw new Error(`Could not download the ${model} model: ${res.status}`);
80
- await pipeline(Readable.fromWeb(res.body), createWriteStream(`${dest}.part`));
78
+ // Con avance y con freno (2026-10-07): en las pruebas de Windows esta descarga tardó 52 min
79
+ // sin decir nada, y un agente que no ve avance cree que se ha colgado y la corta. Se avisa
80
+ // cada 10 % y, si en 2 min no llega ni un byte, se para con un error claro.
81
+ const ctrl = new AbortController();
82
+ const STALL_MS = 120_000;
83
+ let stall = setTimeout(() => ctrl.abort(), STALL_MS);
84
+ let got = 0;
85
+ let next = 10;
86
+ const progress = new Transform({
87
+ transform(chunk, _enc, cb) {
88
+ clearTimeout(stall);
89
+ stall = setTimeout(() => ctrl.abort(), STALL_MS);
90
+ got += chunk.length;
91
+ const pct = Math.floor((got / m.size) * 100);
92
+ if (pct >= next && pct < 100) {
93
+ log(` ${pct} % (${Math.round(got / 1e6)} of ${Math.round(m.size / 1e6)} MB)`);
94
+ next = Math.floor(pct / 10) * 10 + 10;
95
+ }
96
+ cb(null, chunk);
97
+ },
98
+ });
99
+ try {
100
+ const res = await fetch(`https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${m.file}`, { signal: ctrl.signal });
101
+ if (!res.ok) throw new Error(`Could not download the ${model} model: ${res.status}`);
102
+ await pipeline(Readable.fromWeb(res.body), progress, createWriteStream(`${dest}.part`));
103
+ } catch (err) {
104
+ rmSync(`${dest}.part`, { force: true });
105
+ if (ctrl.signal.aborted) {
106
+ throw new Error(`The ${model} transcription model download stalled (no data for 2 minutes). Check the internet connection and run the command again; it starts over.`);
107
+ }
108
+ throw err;
109
+ } finally {
110
+ clearTimeout(stall);
111
+ }
81
112
  // El sha256 es el que publica Hugging Face para ese fichero (su oid de LFS).
82
113
  const h = createHash('sha256');
83
114
  await pipeline(createReadStream(`${dest}.part`), h);