@bendyline/squisq-cli 1.0.0 → 1.1.0

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.
Files changed (41) hide show
  1. package/README.md +121 -0
  2. package/dist/__tests__/convert.test.d.ts +7 -0
  3. package/dist/__tests__/convert.test.d.ts.map +1 -0
  4. package/dist/__tests__/convert.test.js +127 -0
  5. package/dist/__tests__/convert.test.js.map +1 -0
  6. package/dist/__tests__/readInput.test.d.ts +5 -0
  7. package/dist/__tests__/readInput.test.d.ts.map +1 -0
  8. package/dist/__tests__/readInput.test.js +120 -0
  9. package/dist/__tests__/readInput.test.js.map +1 -0
  10. package/dist/api.d.ts +106 -0
  11. package/dist/api.d.ts.map +1 -0
  12. package/dist/api.js +324 -0
  13. package/dist/api.js.map +1 -0
  14. package/dist/commands/convert.d.ts +15 -0
  15. package/dist/commands/convert.d.ts.map +1 -0
  16. package/dist/commands/convert.js +186 -0
  17. package/dist/commands/convert.js.map +1 -0
  18. package/dist/commands/video.d.ts +12 -0
  19. package/dist/commands/video.d.ts.map +1 -0
  20. package/dist/commands/video.js +122 -0
  21. package/dist/commands/video.js.map +1 -0
  22. package/dist/index.d.ts +12 -2
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +20 -425
  25. package/dist/index.js.map +1 -1
  26. package/dist/nativeEncoder-EXDP2O5B.js +0 -0
  27. package/dist/nativeEncoder-MWHOONST.js +88 -0
  28. package/dist/nativeEncoder-MWHOONST.js.map +1 -0
  29. package/dist/util/detectFfmpeg.d.ts +15 -0
  30. package/dist/util/detectFfmpeg.d.ts.map +1 -0
  31. package/dist/util/detectFfmpeg.js +29 -0
  32. package/dist/util/detectFfmpeg.js.map +1 -0
  33. package/dist/util/nativeEncoder.d.ts +27 -0
  34. package/dist/util/nativeEncoder.d.ts.map +1 -0
  35. package/dist/util/nativeEncoder.js +106 -0
  36. package/dist/util/nativeEncoder.js.map +1 -0
  37. package/dist/util/readInput.d.ts +31 -0
  38. package/dist/util/readInput.d.ts.map +1 -0
  39. package/dist/util/readInput.js +142 -0
  40. package/dist/util/readInput.js.map +1 -0
  41. package/package.json +17 -5
package/dist/api.js ADDED
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Programmatic Video API
3
+ *
4
+ * Provides a library-style entry point for rendering Squisq documents to MP4
5
+ * from Node.js callers (e.g., Qualla's pipeline). This avoids the need to shell
6
+ * out to the `squisq video` CLI and gives callers full control over the Doc,
7
+ * MemoryContentContainer, and encoding options.
8
+ *
9
+ * Orchestrates the full pipeline: Doc → render HTML → Playwright frame capture → FFmpeg encode.
10
+ *
11
+ * Usage:
12
+ * import { renderDocToMp4 } from '@bendyline/squisq-cli/api';
13
+ *
14
+ * await renderDocToMp4(doc, container, {
15
+ * outputPath: '/tmp/output.mp4',
16
+ * fps: 30,
17
+ * quality: 'normal',
18
+ * orientation: 'landscape',
19
+ * });
20
+ */
21
+ import { generateRenderHtml } from '@bendyline/squisq-video';
22
+ import { resolveDimensions } from '@bendyline/squisq-video';
23
+ import { detectFfmpeg } from './util/detectFfmpeg.js';
24
+ export { MemoryContentContainer } from '@bendyline/squisq/storage';
25
+ export { readInput } from './util/readInput.js';
26
+ /**
27
+ * Render a Doc + media container to an MP4 video file.
28
+ *
29
+ * The container should contain audio and image files referenced by the Doc's
30
+ * audio.segments[].src and block image paths. Files are embedded as base64
31
+ * data URIs in a self-contained render HTML page.
32
+ *
33
+ * Requires:
34
+ * - Playwright (chromium) — for headless frame capture
35
+ * - FFmpeg — for video encoding (must be on PATH)
36
+ *
37
+ * @param doc - The Doc structure to render
38
+ * @param container - MemoryContentContainer with audio/image files
39
+ * @param options - Rendering and encoding options
40
+ * @returns Result with duration and frame count
41
+ */
42
+ export async function renderDocToMp4(doc, container, options) {
43
+ const { outputPath, fps = 30, quality = 'normal', orientation = 'landscape', captionStyle, coverPreRoll = 0, onProgress, } = options;
44
+ const dimensions = resolveDimensions({
45
+ orientation,
46
+ width: options.width,
47
+ height: options.height,
48
+ });
49
+ // Detect ffmpeg early — needed for audio concat and video encoding
50
+ const ffmpegPath = await detectFfmpeg();
51
+ if (!ffmpegPath) {
52
+ throw new Error('ffmpeg is required but not found in PATH.\n' +
53
+ 'Install it with:\n' +
54
+ ' macOS: brew install ffmpeg\n' +
55
+ ' Ubuntu: sudo apt install ffmpeg\n' +
56
+ ' Windows: winget install ffmpeg');
57
+ }
58
+ onProgress?.('collecting media', 0);
59
+ // ── Collect images from container ───────────────────────────────
60
+ const { collectImagePaths } = await import('@bendyline/squisq-formats/html');
61
+ const imagePaths = collectImagePaths(doc);
62
+ const images = new Map();
63
+ for (const imgPath of imagePaths) {
64
+ const data = await container.readFile(imgPath);
65
+ if (data) {
66
+ images.set(imgPath, data);
67
+ }
68
+ }
69
+ // ── Collect audio segments ──────────────────────────────────────
70
+ const audio = new Map();
71
+ const audioBuffers = [];
72
+ if (doc.audio?.segments?.length) {
73
+ for (const seg of doc.audio.segments) {
74
+ const data = await container.readFile(seg.src);
75
+ if (data) {
76
+ audio.set(seg.src, data);
77
+ audio.set(seg.name, data);
78
+ audioBuffers.push(data);
79
+ }
80
+ }
81
+ }
82
+ // Concatenate audio for the MP4's audio track
83
+ let concatenatedAudio = null;
84
+ if (audioBuffers.length > 0) {
85
+ concatenatedAudio = await concatenateAudioBuffers(audioBuffers, ffmpegPath);
86
+ }
87
+ onProgress?.('generating render HTML', 10);
88
+ // ── Generate self-contained render HTML ─────────────────────────
89
+ const { PLAYER_BUNDLE } = await import('@bendyline/squisq-react/standalone-source');
90
+ const renderHtml = generateRenderHtml(doc, {
91
+ playerScript: PLAYER_BUNDLE,
92
+ images,
93
+ audio: audio.size > 0 ? audio : undefined,
94
+ width: dimensions.width,
95
+ height: dimensions.height,
96
+ captionStyle,
97
+ });
98
+ onProgress?.('launching browser', 15);
99
+ // ── Playwright frame capture ────────────────────────────────────
100
+ const { chromium } = await import('playwright-core');
101
+ const browser = await chromium.launch({ headless: true });
102
+ const page = await browser.newPage({
103
+ viewport: { width: dimensions.width, height: dimensions.height },
104
+ });
105
+ const pageErrors = [];
106
+ page.on('pageerror', (err) => pageErrors.push(err.message));
107
+ await page.setContent(renderHtml, { waitUntil: 'load' });
108
+ await page.waitForTimeout(500);
109
+ try {
110
+ await page.waitForFunction(() => typeof window.getDuration === 'function', { timeout: 15000 });
111
+ }
112
+ catch {
113
+ await browser.close();
114
+ const errorDetail = pageErrors.length
115
+ ? `\nPage errors:\n ${pageErrors.join('\n ')}`
116
+ : '\nNo page errors captured — the player may have failed to mount.';
117
+ throw new Error(`Render API did not initialize within 15 seconds.${errorDetail}`);
118
+ }
119
+ const docDuration = await page.evaluate(() => {
120
+ return window.getDuration();
121
+ });
122
+ if (docDuration <= 0) {
123
+ await browser.close();
124
+ throw new Error('Document has zero duration — nothing to render');
125
+ }
126
+ const storyFrameCount = Math.ceil(docDuration * fps);
127
+ const preRollFrameCount = Math.ceil(coverPreRoll * fps);
128
+ const totalFrames = preRollFrameCount + storyFrameCount;
129
+ const frames = [];
130
+ onProgress?.('capturing frames', 20);
131
+ // Cover slide pre-roll (if requested)
132
+ if (preRollFrameCount > 0) {
133
+ const hasCover = await page.evaluate(() => {
134
+ const w = window;
135
+ return typeof w.hasCoverBlock === 'function' ? w.hasCoverBlock() : false;
136
+ });
137
+ if (hasCover) {
138
+ await page.evaluate(() => {
139
+ window.showCover();
140
+ });
141
+ await page.waitForTimeout(100);
142
+ const coverFrame = new Uint8Array(await page.screenshot({ type: 'png' }));
143
+ for (let i = 0; i < preRollFrameCount; i++) {
144
+ frames.push(coverFrame);
145
+ }
146
+ await page.evaluate(() => {
147
+ window.hideCover();
148
+ });
149
+ }
150
+ }
151
+ // Story frames via seekTo
152
+ const frameInterval = 1 / fps;
153
+ for (let i = 0; i < storyFrameCount; i++) {
154
+ const time = i * frameInterval;
155
+ await page.evaluate((t) => {
156
+ return window.seekTo(t);
157
+ }, time);
158
+ const screenshot = await page.screenshot({ type: 'png' });
159
+ frames.push(new Uint8Array(screenshot));
160
+ // Report progress: frames phase is 20% to 80%
161
+ if (i % Math.max(1, Math.floor(fps / 2)) === 0 || i === storyFrameCount - 1) {
162
+ const pct = 20 + Math.round((frames.length / totalFrames) * 60);
163
+ onProgress?.('capturing frames', pct);
164
+ }
165
+ }
166
+ await browser.close();
167
+ onProgress?.('encoding video', 80);
168
+ // If there's a pre-roll, delay the audio track to match
169
+ let encodingAudio = concatenatedAudio;
170
+ if (coverPreRoll > 0 && concatenatedAudio) {
171
+ // Use FFmpeg to add silence padding at the start (adelay filter)
172
+ encodingAudio = await addAudioDelay(ffmpegPath, concatenatedAudio, coverPreRoll);
173
+ }
174
+ const { framesToMp4Native } = await import('./util/nativeEncoder.js');
175
+ await framesToMp4Native(ffmpegPath, frames, encodingAudio, outputPath, {
176
+ fps,
177
+ quality,
178
+ orientation,
179
+ width: dimensions.width,
180
+ height: dimensions.height,
181
+ onProgress: (percent, phase) => {
182
+ onProgress?.(`encoding: ${phase}`, 80 + Math.round(percent * 0.2));
183
+ },
184
+ });
185
+ onProgress?.('done', 100);
186
+ const totalDuration = docDuration + coverPreRoll;
187
+ return {
188
+ duration: totalDuration,
189
+ frameCount: frames.length,
190
+ outputPath,
191
+ };
192
+ }
193
+ // ── Audio helpers ─────────────────────────────────────────────────
194
+ /**
195
+ * Concatenate multiple audio buffers into one.
196
+ * Uses native ffmpeg concat when available, falls back to byte concatenation.
197
+ */
198
+ async function concatenateAudioBuffers(buffers, ffmpegPath) {
199
+ if (buffers.length === 0)
200
+ return new Uint8Array(0);
201
+ if (buffers.length === 1)
202
+ return new Uint8Array(buffers[0]);
203
+ if (ffmpegPath) {
204
+ return concatenateAudioNative(ffmpegPath, buffers);
205
+ }
206
+ // Fallback: naive byte concatenation (works for MP3)
207
+ const totalLength = buffers.reduce((sum, b) => sum + b.byteLength, 0);
208
+ const result = new Uint8Array(totalLength);
209
+ let offset = 0;
210
+ for (const buf of buffers) {
211
+ result.set(new Uint8Array(buf), offset);
212
+ offset += buf.byteLength;
213
+ }
214
+ return result;
215
+ }
216
+ async function concatenateAudioNative(ffmpegPath, buffers) {
217
+ const { writeFile, readFile, mkdir, rm } = await import('node:fs/promises');
218
+ const { join } = await import('node:path');
219
+ const { tmpdir } = await import('node:os');
220
+ const { randomBytes } = await import('node:crypto');
221
+ const { execFile } = await import('node:child_process');
222
+ const tmpId = randomBytes(8).toString('hex');
223
+ const workDir = join(tmpdir(), `squisq-audio-concat-${tmpId}`);
224
+ await mkdir(workDir, { recursive: true });
225
+ try {
226
+ const segmentPaths = [];
227
+ for (let i = 0; i < buffers.length; i++) {
228
+ const segPath = join(workDir, `seg-${i}.mp3`);
229
+ await writeFile(segPath, new Uint8Array(buffers[i]));
230
+ segmentPaths.push(segPath);
231
+ }
232
+ const listPath = join(workDir, 'concat-list.txt');
233
+ const listContent = segmentPaths.map((p) => `file '${p.replace(/\\/g, '/')}'`).join('\n');
234
+ await writeFile(listPath, listContent);
235
+ const outputPath = join(workDir, 'combined.mp3');
236
+ await new Promise((resolve, reject) => {
237
+ execFile(ffmpegPath, ['-y', '-f', 'concat', '-safe', '0', '-i', listPath, '-c', 'copy', outputPath], { timeout: 120000 }, (err) => {
238
+ if (err)
239
+ reject(new Error(`ffmpeg audio concat failed: ${err.message}`));
240
+ else
241
+ resolve();
242
+ });
243
+ });
244
+ const data = await readFile(outputPath);
245
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
246
+ }
247
+ finally {
248
+ await rm(workDir, { recursive: true, force: true });
249
+ }
250
+ }
251
+ /**
252
+ * Add silence at the start of an audio track by re-encoding with adelay filter.
253
+ */
254
+ async function addAudioDelay(ffmpegPath, audioData, delaySecs) {
255
+ const { writeFile, readFile, rm } = await import('node:fs/promises');
256
+ const { join } = await import('node:path');
257
+ const { tmpdir } = await import('node:os');
258
+ const { randomBytes } = await import('node:crypto');
259
+ const { execFile } = await import('node:child_process');
260
+ const tmpId = randomBytes(8).toString('hex');
261
+ const inputPath = join(tmpdir(), `squisq-audio-delay-in-${tmpId}.mp3`);
262
+ const outputPath = join(tmpdir(), `squisq-audio-delay-out-${tmpId}.mp3`);
263
+ try {
264
+ await writeFile(inputPath, audioData);
265
+ const delayMs = Math.round(delaySecs * 1000);
266
+ await new Promise((resolve, reject) => {
267
+ execFile(ffmpegPath, [
268
+ '-y',
269
+ '-i',
270
+ inputPath,
271
+ '-af',
272
+ `adelay=${delayMs}|${delayMs}`,
273
+ '-c:a',
274
+ 'libmp3lame',
275
+ '-b:a',
276
+ '128k',
277
+ outputPath,
278
+ ], { timeout: 60000 }, (err) => {
279
+ if (err)
280
+ reject(new Error(`ffmpeg audio delay failed: ${err.message}`));
281
+ else
282
+ resolve();
283
+ });
284
+ });
285
+ const data = await readFile(outputPath);
286
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
287
+ }
288
+ finally {
289
+ await rm(inputPath, { force: true });
290
+ await rm(outputPath, { force: true });
291
+ }
292
+ }
293
+ /**
294
+ * Extract thumbnail images from the first frame of an MP4 video.
295
+ * Produces JPEG files at each specified size using FFmpeg video filters.
296
+ */
297
+ export async function extractThumbnails(options) {
298
+ const { videoPath, outputDir, slug, sizes, force } = options;
299
+ const { existsSync } = await import('node:fs');
300
+ const { join } = await import('node:path');
301
+ const { execFile } = await import('node:child_process');
302
+ const ffmpegPath = await detectFfmpeg();
303
+ if (!ffmpegPath) {
304
+ throw new Error('ffmpeg is required for thumbnail extraction but not found in PATH.\n' +
305
+ 'Install it with:\n' +
306
+ ' macOS: brew install ffmpeg\n' +
307
+ ' Ubuntu: sudo apt install ffmpeg\n' +
308
+ ' Windows: winget install ffmpeg');
309
+ }
310
+ for (const thumb of sizes) {
311
+ const outputPath = join(outputDir, `${slug}-${thumb.width}x${thumb.height}.jpg`);
312
+ if (!force && existsSync(outputPath))
313
+ continue;
314
+ await new Promise((resolve, reject) => {
315
+ execFile(ffmpegPath, ['-y', '-i', videoPath, '-vf', thumb.filter, '-frames:v', '1', '-q:v', '2', outputPath], { timeout: 30000 }, (err) => {
316
+ if (err)
317
+ reject(new Error(`Thumbnail extraction failed (${thumb.name}): ${err.message}`));
318
+ else
319
+ resolve();
320
+ });
321
+ });
322
+ }
323
+ }
324
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * convert command
3
+ *
4
+ * Reads a markdown file, ZIP/DBK container, or folder and exports to
5
+ * all supported formats: DOCX, PPTX, PDF, HTML, and container ZIP (.dbk).
6
+ *
7
+ * Supports optional --theme and --transform flags to apply a squisq theme
8
+ * and/or transform style before exporting.
9
+ *
10
+ * Usage:
11
+ * squisq convert <input> [--output-dir <dir>] [--formats <list>] [--theme <id>] [--transform <style>]
12
+ */
13
+ import type { Command } from 'commander';
14
+ export declare function registerConvertCommand(program: Command): void;
15
+ //# sourceMappingURL=convert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/commands/convert.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwB7D"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * convert command
3
+ *
4
+ * Reads a markdown file, ZIP/DBK container, or folder and exports to
5
+ * all supported formats: DOCX, PPTX, PDF, HTML, and container ZIP (.dbk).
6
+ *
7
+ * Supports optional --theme and --transform flags to apply a squisq theme
8
+ * and/or transform style before exporting.
9
+ *
10
+ * Usage:
11
+ * squisq convert <input> [--output-dir <dir>] [--formats <list>] [--theme <id>] [--transform <style>]
12
+ */
13
+ import { writeFile, mkdir } from 'node:fs/promises';
14
+ import { dirname, basename, extname, join, resolve } from 'node:path';
15
+ import { readInput } from '../util/readInput.js';
16
+ const ALL_FORMATS = ['docx', 'pptx', 'pdf', 'html', 'dbk'];
17
+ function parseFormats(value) {
18
+ const requested = value.split(',').map((s) => s.trim().toLowerCase());
19
+ const valid = [];
20
+ for (const r of requested) {
21
+ if (ALL_FORMATS.includes(r)) {
22
+ valid.push(r);
23
+ }
24
+ else {
25
+ console.warn(`Unknown format "${r}" — skipping. Valid: ${ALL_FORMATS.join(', ')}`);
26
+ }
27
+ }
28
+ if (valid.length === 0) {
29
+ throw new Error(`No valid formats specified. Valid: ${ALL_FORMATS.join(', ')}`);
30
+ }
31
+ return valid;
32
+ }
33
+ export function registerConvertCommand(program) {
34
+ program
35
+ .command('convert')
36
+ .description('Convert a markdown document to DOCX, PPTX, PDF, HTML, and DBK container formats')
37
+ .argument('<input>', 'Path to .md file, .zip/.dbk container, or folder')
38
+ .option('-o, --output-dir <dir>', 'Output directory (default: same as input)')
39
+ .option('-f, --formats <list>', `Comma-separated formats to produce (default: all). Valid: ${ALL_FORMATS.join(', ')}`)
40
+ .option('-t, --theme <id>', 'Squisq theme ID to apply (e.g., documentary, cinematic, bold)')
41
+ .option('--transform <style>', 'Transform style to apply before export (e.g., documentary, magazine, minimal)')
42
+ .action(async (inputPath, opts) => {
43
+ try {
44
+ await runConvert(inputPath, opts);
45
+ }
46
+ catch (err) {
47
+ const message = err instanceof Error ? err.message : String(err);
48
+ console.error(`Error: ${message}`);
49
+ process.exitCode = 1;
50
+ }
51
+ });
52
+ }
53
+ async function runConvert(inputPath, opts) {
54
+ const resolvedInput = resolve(inputPath);
55
+ // Determine which formats to produce
56
+ const formats = opts.formats ? parseFormats(opts.formats) : [...ALL_FORMATS];
57
+ // Determine output directory and base filename
58
+ const outputDir = opts.outputDir ? resolve(opts.outputDir) : dirname(resolvedInput);
59
+ const inputBasename = basename(resolvedInput);
60
+ const inputExt = extname(inputBasename);
61
+ const baseName = inputExt ? inputBasename.slice(0, -inputExt.length) : inputBasename;
62
+ // Ensure output directory exists
63
+ await mkdir(outputDir, { recursive: true });
64
+ // Validate theme and transform IDs
65
+ if (opts.theme) {
66
+ const { getAvailableThemes } = await import('@bendyline/squisq/schemas');
67
+ const themes = getAvailableThemes();
68
+ if (!themes.includes(opts.theme)) {
69
+ throw new Error(`Unknown theme "${opts.theme}". Available: ${themes.join(', ')}`);
70
+ }
71
+ }
72
+ if (opts.transform) {
73
+ const { getTransformStyleIds } = await import('@bendyline/squisq/transform');
74
+ const styles = getTransformStyleIds();
75
+ if (!styles.includes(opts.transform)) {
76
+ throw new Error(`Unknown transform style "${opts.transform}". Available: ${styles.join(', ')}`);
77
+ }
78
+ }
79
+ console.error(`Reading: ${resolvedInput}`);
80
+ const result = await readInput(resolvedInput);
81
+ const { container } = result;
82
+ if (!result.markdownDoc) {
83
+ throw new Error('Convert command requires a markdown document. JSON Doc input is not supported for convert — use the video command instead.');
84
+ }
85
+ // Apply transform if requested
86
+ let exportMarkdownDoc = result.markdownDoc;
87
+ if (opts.transform) {
88
+ exportMarkdownDoc = await applyTransformToMarkdown(result.markdownDoc, container, opts.transform, opts.theme);
89
+ console.error(` Applied transform: ${opts.transform}`);
90
+ }
91
+ const themeId = opts.theme;
92
+ for (const format of formats) {
93
+ const outPath = join(outputDir, `${baseName}.${format}`);
94
+ switch (format) {
95
+ case 'docx': {
96
+ const { markdownDocToDocx } = await import('@bendyline/squisq-formats/docx');
97
+ const buf = await markdownDocToDocx(exportMarkdownDoc, { themeId });
98
+ await writeFile(outPath, Buffer.from(buf));
99
+ break;
100
+ }
101
+ case 'pptx': {
102
+ const { markdownDocToPptx } = await import('@bendyline/squisq-formats/pptx');
103
+ // Collect images from container for PPTX embedding
104
+ const images = await collectContainerImages(container);
105
+ const buf = await markdownDocToPptx(exportMarkdownDoc, { themeId, images });
106
+ await writeFile(outPath, Buffer.from(buf));
107
+ break;
108
+ }
109
+ case 'pdf': {
110
+ const { markdownDocToPdf } = await import('@bendyline/squisq-formats/pdf');
111
+ const buf = await markdownDocToPdf(exportMarkdownDoc, { themeId });
112
+ await writeFile(outPath, Buffer.from(buf));
113
+ break;
114
+ }
115
+ case 'html': {
116
+ const { markdownToDoc } = await import('@bendyline/squisq/doc');
117
+ const { docToHtml, collectImagePaths } = await import('@bendyline/squisq-formats/html');
118
+ const { PLAYER_BUNDLE } = await import('@bendyline/squisq-react/standalone-source');
119
+ const doc = markdownToDoc(exportMarkdownDoc);
120
+ // Gather images referenced by the doc from the container
121
+ const imagePaths = collectImagePaths(doc);
122
+ const images = new Map();
123
+ for (const imgPath of imagePaths) {
124
+ const data = await container.readFile(imgPath);
125
+ if (data) {
126
+ images.set(imgPath, data);
127
+ }
128
+ }
129
+ const html = docToHtml(doc, {
130
+ playerScript: PLAYER_BUNDLE,
131
+ images,
132
+ title: baseName,
133
+ mode: 'static',
134
+ themeId,
135
+ });
136
+ await writeFile(outPath, html, 'utf-8');
137
+ break;
138
+ }
139
+ case 'dbk': {
140
+ const { containerToZip } = await import('@bendyline/squisq-formats/container');
141
+ const blob = await containerToZip(container);
142
+ const buf = await blob.arrayBuffer();
143
+ await writeFile(outPath, Buffer.from(buf));
144
+ break;
145
+ }
146
+ }
147
+ console.error(` ✓ ${outPath}`);
148
+ }
149
+ console.error('Done.');
150
+ }
151
+ // ============================================
152
+ // Transform Pipeline
153
+ // ============================================
154
+ /**
155
+ * Apply a transform style to a MarkdownDocument.
156
+ * Pipeline: MarkdownDocument → Doc → applyTransform → docToMarkdown → MarkdownDocument
157
+ */
158
+ async function applyTransformToMarkdown(markdownDoc, container, transformStyle, themeId) {
159
+ const { markdownToDoc, docToMarkdown } = await import('@bendyline/squisq/doc');
160
+ const { applyTransform, extractDocImages } = await import('@bendyline/squisq/transform');
161
+ const doc = markdownToDoc(markdownDoc);
162
+ // Extract image metadata from the doc for transform interleaving
163
+ const images = extractDocImages(doc.blocks);
164
+ const result = applyTransform(doc, transformStyle, {
165
+ themeId,
166
+ images,
167
+ });
168
+ return docToMarkdown(result.doc);
169
+ }
170
+ /**
171
+ * Collect all image files from a container as a Map<path, ArrayBuffer>.
172
+ */
173
+ async function collectContainerImages(container) {
174
+ const images = new Map();
175
+ const files = await container.listFiles();
176
+ for (const file of files) {
177
+ if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|avif)$/i.test(file.path)) {
178
+ const data = await container.readFile(file.path);
179
+ if (data) {
180
+ images.set(file.path, data);
181
+ }
182
+ }
183
+ }
184
+ return images;
185
+ }
186
+ //# sourceMappingURL=convert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../../src/commands/convert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * video command
3
+ *
4
+ * Renders a squisq document to MP4 video by delegating to the
5
+ * programmatic renderDocToMp4 API.
6
+ *
7
+ * Usage:
8
+ * squisq video <input> [-o output.mp4] [--fps 30] [--quality normal] [--orientation landscape]
9
+ */
10
+ import type { Command } from 'commander';
11
+ export declare function registerVideoCommand(program: Command): void;
12
+ //# sourceMappingURL=video.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../src/commands/video.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsC3D"}