@ignix-social/editor 0.1.28 → 0.1.29

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/NOTICE CHANGED
@@ -97,3 +97,5 @@ Inter (font, from @fontsource/inter) — SIL Open Font License 1.1, Copyright (c
97
97
 
98
98
  Ignix Creator's composition templates (src/creator) were ported from Ignix Creator's own
99
99
  code (Copyright (c) 2026 Ignix); no Remotion code is included.
100
+
101
+ Reference downloads use official standalone yt-dlp release binaries (https://github.com/yt-dlp/yt-dlp), downloaded separately and checked against release SHA-256 checksums. yt-dlp source is Unlicense; standalone executables include third-party dependencies with their own license notices, available with the official release/source distribution. These executables are not included in this npm package.
package/README.md CHANGED
@@ -6,12 +6,12 @@ Ignix Editor is for Ignix customers: it needs an active [Ignix plan](https://ign
6
6
 
7
7
  ```sh
8
8
  npx @ignix-social/editor license <token> # saves the license from the Ignix MCP
9
- npx @ignix-social/editor doctor # checks Node 22+, ffmpeg, whisper and the H.264 encoder
9
+ npx @ignix-social/editor doctor # checks Node 22+, ffmpeg, whisper, yt-dlp and the H.264 encoder
10
10
  npx @ignix-social/editor init my-reel # creates ~/ignix-videos/my-reel
11
11
  npx @ignix-social/editor render ~/ignix-videos/my-reel
12
12
  ```
13
13
 
14
- Commands: `license`, `doctor`, `init`, `check`, `snapshot`, `render`, `preview`, `transcribe`, `faces`, `ffmpeg`, `ffprobe`, `skills add`, `fetch`, `serve`, `creator-job`, `help`.
14
+ Commands: `license`, `doctor`, `init`, `check`, `snapshot`, `render`, `preview`, `download`, `transcribe`, `faces`, `ffmpeg`, `ffprobe`, `skills add`, `fetch`, `serve`, `creator-job`, `help`.
15
15
 
16
16
  - Everything runs locally; your footage never leaves your machine.
17
17
  - ffmpeg (GPL build, without libx264) and whisper.cpp are downloaded once per machine into `~/.ignix-editor`, checked against their SHA-256. Chrome for rendering is downloaded once from Google's official channel.
@@ -25,3 +25,5 @@ Requires Node.js 22 or newer. Runs on macOS (Intel and Apple Silicon) and Window
25
25
  ## License
26
26
 
27
27
  Apache-2.0. Ignix Editor is based on [HyperFrames](https://github.com/heygen-com/hyperframes) by HeyGen, Inc. (Apache-2.0), included in `vendor/hyperframes` with the modifications listed in `patches/`. See `NOTICE`. Ignix Editor is not affiliated with or endorsed by HeyGen.
28
+
29
+ Public references: `ignix-editor download <URL> -o work/ref.mp4` uses a pinned, SHA-256-checked official yt-dlp binary and bundled FFmpeg/Node, without browser cookies or user downloader configuration. Reuse the transcript supplied by Ignix; run local Whisper if it is absent or needs checking. Inspect frames for visual structure and names. Downloaded source footage is study material, not a render asset.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ignix-social/editor",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
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",
@@ -14,7 +14,8 @@ Commands. `ignix-editor` means the copy the Ignix playbook installed: `~/.ignix-
14
14
  | Command | What it does |
15
15
  |---|---|
16
16
  | `ignix-editor license <token>` | Saves the license from the Ignix MCP (`get_editor_license`). Without a token, shows whether there is a valid one and until when. |
17
- | `ignix-editor doctor` | Node 22+ and the H.264 encoder; downloads its own ffmpeg and whisper the first time. Run it first. |
17
+ | `ignix-editor download <URL> -o work/ref.mp4` | Downloads a public Instagram, TikTok or YouTube reference with bundled yt-dlp, FFmpeg and Node. No cookies. Keep it in `work/`, never `assets/`. Reuse the transcript supplied by Ignix; use local Whisper if absent. If downloading fails, use that saved speech transcript and disclose that visuals are unverified. |
18
+ | `ignix-editor doctor` | Node 22+ and the H.264 encoder; downloads its own ffmpeg, whisper and yt-dlp the first time. Run it first. |
18
19
  | `ignix-editor init <name>` | Creates `~/ignix-videos/<name>` with a working skeleton, GSAP, local fonts and `ignix-frame.js`. |
19
20
  | `ignix-editor transcribe <file> --lang es [--script "…"] -o words.json` | Words with start/end seconds, plus `pauses` ([start, end] of every silence ≥0.35 s, for cutting silences), on this machine. Over 2 min it prints whisper's progress; without a GPU it takes about as long as the audio. |
20
21
  | `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. |
@@ -273,6 +274,10 @@ run({ id, duration, fps }, (t) => O.draw(t));
273
274
  - Captions: groups of 2–3 words that never cross the end of a line; current word in the brand accent; font size shrinks for long groups so they fit the ~820 px column. Keep a real gap between words (`gap: 0 0.3em` on the flex row): if the active word grows (`scale`), it eats a smaller gap and «EDITORES DE» reads «EDITORESDE» — grow it at most ~5 %, or only change its colour.
274
275
  - The final mix is normalised to −14 LUFS by `render` (`--lufs` to change, `--no-normalize` to skip).
275
276
 
277
+ ### A reel modelled on a reference
278
+
279
+ When the user brings a reel they want theirs to look like, study it; never use it. Download it first with `ignix-editor download <URL> -o work/ref.mp4`. Read `inspiredBy.transcript` from the Ignix playbook when supplied: the app already obtained it, so the agent never calls APISmith. Reuse that speech evidence; local Whisper is for missing transcripts or recognition errors. If downloading fails, you can still adapt the saved speech argument, but say that visuals are unverified and keep the visual mapping provisional. If neither video nor transcript is available, ask for the file before proposing a script. Verify named recommendations against frames when available and preserve each reason; do not replace a comparison followed by specific recommendations with generic categories. Show the original preview or representative frames, then its sequence and the adaptation mapping before the script. Keep it in `work/` (never `assets/`), tile its frames (`ignix-editor ffmpeg -i work/ref.mp4 -vf "fps=2,scale=240:-1,tile=6x4" work/ref-%02d.jpg`) and transcribe it to read its layout, its beats and their timing, and the moments that carry it. Then rebuild that structure with your own pieces: the layout in CSS (e.g. a split screen with graphics in the top third, the presenter's video below and short captions on the seam), each beat's visual drawn for this brand, the key moment recreated with what you have (a gesture clip, cards fanning out, a reveal). None of it reaches the render: not its footage, music, words or branding. In the review, compare your stills with its contact sheet beat by beat.
280
+
276
281
  ## 5. Generate the HTML from data
277
282
 
278
283
  When there are many clips, lines or cues, keep them in data and let a script write the media tags and the timing file:
package/src/binaries.json CHANGED
@@ -20,6 +20,13 @@
20
20
  "size": 2912480,
21
21
  "sha256": "73fc58436c1e1a9d2cc3ea08bddf18bf78270d8c6940c5fd89a3c9cb31535efe",
22
22
  "build": "whisper.cpp v1.7.6, scripts/build-whisper-macos.sh"
23
+ },
24
+ "ytdlp": {
25
+ "file": "yt-dlp_macos",
26
+ "size": 37146048,
27
+ "sha256": "0f192b7ec147ab6288885d6351d9ab67367640029b4377576ef46dd79cf7b202",
28
+ "url": "https://github.com/yt-dlp/yt-dlp/releases/download/2026.08.19/yt-dlp_macos",
29
+ "build": "yt-dlp 2026.08.19 official standalone; SHA2-256SUMS verified"
23
30
  }
24
31
  },
25
32
  "darwin-arm64": {
@@ -40,6 +47,13 @@
40
47
  "size": 2573144,
41
48
  "sha256": "215d93a2edc77f8929311f127f368727859e13833e925652315ca5ea774595ee",
42
49
  "build": "whisper.cpp v1.7.6, CI binaries.yml (macos-15, run 37375441240)"
50
+ },
51
+ "ytdlp": {
52
+ "file": "yt-dlp_macos",
53
+ "size": 37146048,
54
+ "sha256": "0f192b7ec147ab6288885d6351d9ab67367640029b4377576ef46dd79cf7b202",
55
+ "url": "https://github.com/yt-dlp/yt-dlp/releases/download/2026.08.19/yt-dlp_macos",
56
+ "build": "yt-dlp 2026.08.19 official standalone; SHA2-256SUMS verified"
43
57
  }
44
58
  },
45
59
  "win32-x64": {
@@ -59,7 +73,32 @@
59
73
  "file": "whisper-cli.exe",
60
74
  "size": 1990144,
61
75
  "sha256": "26cef7a9b82544e4076a4977c93635bb1b324bd5ab3b4301d6d06e8224e580ea",
62
- "build": "whisper.cpp v1.7.6, MSVC estático sin OpenMP, CI windows.yml (run 37392626795)"
76
+ "build": "whisper.cpp v1.7.6, MSVC est\u00e1tico sin OpenMP, CI windows.yml (run 37392626795)"
77
+ },
78
+ "ytdlp": {
79
+ "file": "yt-dlp.exe",
80
+ "size": 17840399,
81
+ "sha256": "66674953fe251b89f4d08c5f0e35e0728679bd67ab3d7d05c0562af101dd3e7a",
82
+ "url": "https://github.com/yt-dlp/yt-dlp/releases/download/2026.08.19/yt-dlp.exe",
83
+ "build": "yt-dlp 2026.08.19 official standalone; SHA2-256SUMS verified"
84
+ }
85
+ },
86
+ "linux-x64": {
87
+ "ytdlp": {
88
+ "file": "yt-dlp_linux",
89
+ "size": 40446224,
90
+ "sha256": "58162f9bfdc27458ea47bfcb311cf47028f17d8154a8bf7d689861d46399230a",
91
+ "url": "https://github.com/yt-dlp/yt-dlp/releases/download/2026.08.19/yt-dlp_linux",
92
+ "build": "yt-dlp 2026.08.19 official standalone; SHA2-256SUMS verified"
93
+ }
94
+ },
95
+ "linux-arm64": {
96
+ "ytdlp": {
97
+ "file": "yt-dlp_linux_aarch64",
98
+ "size": 40167448,
99
+ "sha256": "b16e4dab368a816cd05d477d698a605a6ae87ccee1c8ffd38fa21d7254141fcc",
100
+ "url": "https://github.com/yt-dlp/yt-dlp/releases/download/2026.08.19/yt-dlp_linux_aarch64",
101
+ "build": "yt-dlp 2026.08.19 official standalone; SHA2-256SUMS verified"
63
102
  }
64
103
  }
65
104
  },
package/src/binaries.mjs CHANGED
@@ -17,6 +17,7 @@ const EXE = process.platform === 'win32' ? '.exe' : '';
17
17
  const manifest = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'binaries.json'), 'utf8'));
18
18
 
19
19
  export const TOOLS = {
20
+ ytdlp: { env: 'IGNIX_EDITOR_YTDLP', file: `yt-dlp${EXE}` },
20
21
  ffmpeg: { env: 'IGNIX_EDITOR_FFMPEG', file: `ffmpeg${EXE}` },
21
22
  ffprobe: { env: 'IGNIX_EDITOR_FFPROBE', file: `ffprobe${EXE}` },
22
23
  whisper: { env: 'IGNIX_EDITOR_WHISPER', file: `whisper-cli${EXE}` },
@@ -44,7 +45,7 @@ export async function ensureTool(name, log = () => {}) {
44
45
  if (!entry) {
45
46
  throw new Error(`${name} is not available for ${PLATFORM} yet. Meanwhile, point ${TOOLS[name].env} to a local ${TOOLS[name].file}.`);
46
47
  }
47
- const url = `${manifest.baseUrl}/${manifest.version}/${PLATFORM}/${entry.file}`;
48
+ const url = entry.url ?? `${manifest.baseUrl}/${manifest.version}/${PLATFORM}/${entry.file}`;
48
49
  log(`Downloading ${name} (${(entry.size / 1e6).toFixed(0)} MB)…`);
49
50
  const res = await fetch(url);
50
51
  if (!res.ok) throw new Error(`Could not download ${name}: ${res.status} ${url}`);
package/src/cli.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { downloadReference } from './reference-download.mjs';
1
2
  // ignix-editor — CLI de Ignix Editor. El motor (HyperFrames, en vendor/) corre siempre por
2
3
  // debajo con nuestro entorno: sin telemetría, sin actualizaciones solas, con nuestros binarios.
3
4
  // Los mensajes van en inglés, como el manual y la skill: los lee sobre todo el agente.
@@ -215,6 +216,14 @@ const commands = {
215
216
  process.exit(runEngine(['preview', build, ...rest], env, dir));
216
217
  },
217
218
 
219
+ async download(argv) {
220
+ const { pos, opts } = parseArgs(argv, { output: 'str' });
221
+ if (pos.length !== 1 || !opts.output) fail('Usage: ignix-editor download <Instagram|TikTok|YouTube URL> -o work/ref.mp4');
222
+ const ytdlp = await ensureTool('ytdlp', log);
223
+ const ffmpeg = await ensureTool('ffmpeg', log);
224
+ console.log(downloadReference({ url: pos[0], output: opts.output, ytdlp, ffmpeg }));
225
+ },
226
+
218
227
  async transcribe(argv) {
219
228
  const { pos, opts } = parseArgs(argv, { lang: 'str', model: 'str', script: 'str', output: 'str', threads: 'str' });
220
229
  const input = pos[0] && resolve(pos[0]);
@@ -462,13 +471,14 @@ const commands = {
462
471
  async help() {
463
472
  console.log(`Ignix Editor ${PKG.version} — video from HTML, rendered on your own computer.
464
473
 
465
- ignix-editor doctor checks Node and the encoder, downloads ffmpeg and whisper if missing
474
+ ignix-editor doctor checks Node and the encoder, downloads ffmpeg, whisper and yt-dlp if missing
466
475
  ignix-editor init <name> creates ~/ignix-videos/<name>, ready to work in
467
476
  ignix-editor check [folder] HTML, assets, layout, motion and contrast
468
477
  ignix-editor snapshot [folder] --at 0,2.5,5 single frames to look at (into <folder>/snapshots)
469
478
  ignix-editor render [folder] [-o out.mp4] [--lufs -14] [--no-normalize] [--no-review]
470
479
  ignix-editor review <video.mp4> [--json] is it ready to show? stillness, empty frame, voice gaps, cuts
471
480
  ignix-editor preview [folder] live preview in the browser
481
+ ignix-editor download <URL> -o work/ref.mp4 downloads a public reference with yt-dlp (no cookies)
472
482
  ignix-editor transcribe <file> [--lang es] [--script "text"] [-o words.json]
473
483
  ignix-editor faces <video> [--every 0.2] [-o faces.json] where the face is, smoothed
474
484
  ignix-editor pick-audio [folder] --kind music|sfx --query "inspiring" [--order trending]
package/src/fetch.mjs CHANGED
@@ -11,7 +11,7 @@ export async function fetchAll({ whisper = 'small', analysis = true, json = fals
11
11
  const emit = (e) => json && process.stdout.write(`${JSON.stringify(e)}\n`);
12
12
  if (!WHISPER_MODELS[whisper]) throw Object.assign(new Error(`Unknown model: ${whisper}`), { code: 'invalid_args' });
13
13
  const todo = [];
14
- for (const name of ['ffmpeg', 'ffprobe', 'whisper']) {
14
+ for (const name of ['ffmpeg', 'ffprobe', 'whisper', 'ytdlp']) {
15
15
  if (!findTool(name).path) todo.push({ item: name, size: toolSize(name), run: () => ensureTool(name, log) });
16
16
  }
17
17
  if (!existsSync(whisperModelPath(whisper))) todo.push({ item: `whisper-${whisper}`, size: WHISPER_MODELS[whisper].size, run: () => ensureWhisperModel(whisper, log) });
@@ -0,0 +1,42 @@
1
+ // Reference study only: these files belong in work/, never in render assets.
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync, mkdirSync } from 'node:fs';
4
+ import { dirname, resolve } from 'node:path';
5
+
6
+ export function referenceUrl(raw) {
7
+ let u;
8
+ try { u = new URL(raw); } catch { throw new Error('Expected an Instagram, TikTok or YouTube video URL.'); }
9
+ const host = u.hostname.toLowerCase();
10
+ const allowed = ['instagram.com', 'www.instagram.com', 'youtube.com', 'www.youtube.com', 'm.youtube.com', 'youtu.be', 'tiktok.com', 'www.tiktok.com', 'vm.tiktok.com', 'vt.tiktok.com'];
11
+ if (u.protocol !== 'https:' || !allowed.includes(host) || u.username || u.password || u.port) throw new Error('Expected an HTTPS Instagram, TikTok or YouTube video URL.');
12
+ return u.href;
13
+ }
14
+
15
+ export function downloadArgs({ url, output, ffmpeg, node = process.execPath }) {
16
+ return [
17
+ '--ignore-config', '--no-simulate', '--no-cache-dir', '--no-playlist', '--no-overwrites',
18
+ '--socket-timeout', '20', '--retries', '1', '--fragment-retries', '1',
19
+ '--max-filesize', '500M', '--match-filter', 'duration <=? 1800',
20
+ '--js-runtimes', `node:${node}`, '--ffmpeg-location', dirname(ffmpeg),
21
+ '--format', 'bv[height<=720][ext=mp4][vcodec^=avc1]+ba[ext=m4a]/b[height<=?720][ext=mp4]/b[height<=?720]',
22
+ '--merge-output-format', 'mp4', '--remux-video', 'mp4',
23
+ '--print', 'after_move:filepath', '--output', output.replace(/\.mp4$/i, '') + '.%(ext)s',
24
+ '--', referenceUrl(url),
25
+ ];
26
+ }
27
+
28
+ export function downloadReference({ url, output, ytdlp, ffmpeg, run = spawnSync }) {
29
+ const dest = resolve(output);
30
+ if (!/\.mp4$/i.test(dest)) throw new Error('Reference output must end in .mp4.');
31
+ referenceUrl(url);
32
+ if (existsSync(dest)) return dest;
33
+ mkdirSync(dirname(dest), { recursive: true });
34
+ const r = run(ytdlp, downloadArgs({ url, output: dest, ffmpeg }), {
35
+ encoding: 'utf8', timeout: 600_000, maxBuffer: 4 * 1024 * 1024,
36
+ env: { ...process.env, PATH: `${dirname(ffmpeg)}${process.platform === 'win32' ? ';' : ':'}${process.env.PATH ?? ''}` },
37
+ });
38
+ if (r.error || r.status !== 0 || !existsSync(dest)) {
39
+ throw new Error(`Reference download failed. Use the transcript already supplied by Ignix if available; do not claim to have seen the visuals. Otherwise request the original file. ${r.error?.message ?? (r.stderr ?? '').slice(-1200)}`);
40
+ }
41
+ return dest;
42
+ }