@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.
- package/README.md +121 -0
- package/dist/__tests__/convert.test.d.ts +7 -0
- package/dist/__tests__/convert.test.d.ts.map +1 -0
- package/dist/__tests__/convert.test.js +127 -0
- package/dist/__tests__/convert.test.js.map +1 -0
- package/dist/__tests__/readInput.test.d.ts +5 -0
- package/dist/__tests__/readInput.test.d.ts.map +1 -0
- package/dist/__tests__/readInput.test.js +120 -0
- package/dist/__tests__/readInput.test.js.map +1 -0
- package/dist/api.d.ts +106 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +324 -0
- package/dist/api.js.map +1 -0
- package/dist/commands/convert.d.ts +15 -0
- package/dist/commands/convert.d.ts.map +1 -0
- package/dist/commands/convert.js +186 -0
- package/dist/commands/convert.js.map +1 -0
- package/dist/commands/video.d.ts +12 -0
- package/dist/commands/video.d.ts.map +1 -0
- package/dist/commands/video.js +122 -0
- package/dist/commands/video.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -425
- package/dist/index.js.map +1 -1
- package/dist/nativeEncoder-EXDP2O5B.js +0 -0
- package/dist/nativeEncoder-MWHOONST.js +88 -0
- package/dist/nativeEncoder-MWHOONST.js.map +1 -0
- package/dist/util/detectFfmpeg.d.ts +15 -0
- package/dist/util/detectFfmpeg.d.ts.map +1 -0
- package/dist/util/detectFfmpeg.js +29 -0
- package/dist/util/detectFfmpeg.js.map +1 -0
- package/dist/util/nativeEncoder.d.ts +27 -0
- package/dist/util/nativeEncoder.d.ts.map +1 -0
- package/dist/util/nativeEncoder.js +106 -0
- package/dist/util/nativeEncoder.js.map +1 -0
- package/dist/util/readInput.d.ts +31 -0
- package/dist/util/readInput.d.ts.map +1 -0
- package/dist/util/readInput.js +142 -0
- package/dist/util/readInput.js.map +1 -0
- package/package.json +17 -5
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @bendyline/squisq-cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for converting Squisq documents to multiple formats and rendering them to video. Reads Markdown files, ZIP/DBK containers, or folders as input.
|
|
4
|
+
|
|
5
|
+
Part of the [Squisq](https://github.com/bendyline/squisq) monorepo.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@bendyline/squisq-cli)
|
|
8
|
+
[](https://github.com/bendyline/squisq/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @bendyline/squisq-cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Commands
|
|
17
|
+
|
|
18
|
+
### `squisq convert`
|
|
19
|
+
|
|
20
|
+
Convert a Squisq document to DOCX, PPTX, PDF, HTML, or DBK (container ZIP):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
squisq convert input.md --formats docx,pptx,pdf
|
|
24
|
+
squisq convert project.dbk --output-dir ./out --formats html,docx
|
|
25
|
+
squisq convert ./my-folder --formats pdf --theme documentary
|
|
26
|
+
squisq convert input.md --theme cinematic --transform magazine --formats pptx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Option | Description | Default |
|
|
30
|
+
| -------------- | ------------------------------------------------ | ----------- |
|
|
31
|
+
| `--output-dir` | Output directory | current dir |
|
|
32
|
+
| `--formats` | Comma-separated list: docx, pptx, pdf, html, dbk | all |
|
|
33
|
+
| `--theme` | Squisq theme ID (e.g., documentary, cinematic) | none |
|
|
34
|
+
| `--transform` | Transform style (e.g., documentary, magazine) | none |
|
|
35
|
+
|
|
36
|
+
### `squisq video`
|
|
37
|
+
|
|
38
|
+
Render a Squisq document to MP4 video using Playwright for frame capture and native ffmpeg for encoding:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
squisq video input.md output.mp4
|
|
42
|
+
squisq video project.dbk --quality high --fps 30
|
|
43
|
+
squisq video ./my-folder --orientation portrait --captions social
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Option | Description | Default |
|
|
47
|
+
| --------------- | ------------------------- | --------- |
|
|
48
|
+
| `--fps` | Frames per second (1–120) | 30 |
|
|
49
|
+
| `--quality` | draft, normal, or high | normal |
|
|
50
|
+
| `--orientation` | landscape or portrait | landscape |
|
|
51
|
+
| `--captions` | off, standard, or social | off |
|
|
52
|
+
| `--width` | Override width in pixels | auto |
|
|
53
|
+
| `--height` | Override height in pixels | auto |
|
|
54
|
+
|
|
55
|
+
**Requires:** [ffmpeg](https://ffmpeg.org/) installed and available on your PATH.
|
|
56
|
+
|
|
57
|
+
## Programmatic API
|
|
58
|
+
|
|
59
|
+
Use the CLI as a library from Node.js — no shell-out required:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { renderDocToMp4, MemoryContentContainer, readInput } from '@bendyline/squisq-cli/api';
|
|
63
|
+
|
|
64
|
+
// Load a document from disk
|
|
65
|
+
const { doc, container } = await readInput('./my-article.md');
|
|
66
|
+
|
|
67
|
+
// Render to MP4
|
|
68
|
+
const result = await renderDocToMp4(doc, container, {
|
|
69
|
+
outputPath: './output.mp4',
|
|
70
|
+
fps: 30,
|
|
71
|
+
quality: 'high',
|
|
72
|
+
orientation: 'landscape',
|
|
73
|
+
captionStyle: 'social',
|
|
74
|
+
onProgress: (phase, pct) => console.log(`${phase}: ${pct}%`),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
console.log(`Rendered ${result.frameCount} frames (${result.duration}s)`);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `extractThumbnails`
|
|
81
|
+
|
|
82
|
+
Extract JPEG thumbnails from the first frame of a rendered video:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { extractThumbnails } from '@bendyline/squisq-cli/api';
|
|
86
|
+
|
|
87
|
+
await extractThumbnails({
|
|
88
|
+
videoPath: './output.mp4',
|
|
89
|
+
outputDir: './thumbs',
|
|
90
|
+
slug: 'my-article',
|
|
91
|
+
sizes: [
|
|
92
|
+
{ name: 'og', width: 1200, height: 630, filter: 'scale=1200:630' },
|
|
93
|
+
{ name: 'thumb', width: 480, height: 270, filter: 'scale=480:270' },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See the full [API Reference](../../docs/API.md#bendylinesquisq-cli) for all types and options.
|
|
99
|
+
|
|
100
|
+
## Input Formats
|
|
101
|
+
|
|
102
|
+
The CLI accepts three types of input:
|
|
103
|
+
|
|
104
|
+
| Input | Description |
|
|
105
|
+
| ------------- | --------------------------------------------------------- |
|
|
106
|
+
| `.md` file | Plain Markdown file |
|
|
107
|
+
| `.zip`/`.dbk` | Container archive with document + embedded media |
|
|
108
|
+
| Folder | Directory with a Markdown file and associated media files |
|
|
109
|
+
|
|
110
|
+
## Related Packages
|
|
111
|
+
|
|
112
|
+
| Package | Description |
|
|
113
|
+
| ------------------------------------------------------------------------------------ | ----------------------------------------------- |
|
|
114
|
+
| [@bendyline/squisq](https://www.npmjs.com/package/@bendyline/squisq) | Headless core — schemas, templates, markdown |
|
|
115
|
+
| [@bendyline/squisq-formats](https://www.npmjs.com/package/@bendyline/squisq-formats) | DOCX, PDF, HTML import/export (used by convert) |
|
|
116
|
+
| [@bendyline/squisq-video](https://www.npmjs.com/package/@bendyline/squisq-video) | Headless video rendering (used by video) |
|
|
117
|
+
| [@bendyline/squisq-react](https://www.npmjs.com/package/@bendyline/squisq-react) | React components for rendering docs |
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
[MIT](https://github.com/bendyline/squisq/blob/main/LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/convert.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* convert command integration tests
|
|
3
|
+
*
|
|
4
|
+
* Exercises the full convert pipeline: input → all formats → output validation.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, before, after, beforeEach, afterEach } from 'mocha';
|
|
7
|
+
import { expect } from 'chai';
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
import { readFile, mkdir, rm, stat, writeFile as fsWriteFile } from 'node:fs/promises';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { containerToZip } from '@bendyline/squisq-formats/container';
|
|
14
|
+
import { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
15
|
+
import { zipToContainer } from '@bendyline/squisq-formats/container';
|
|
16
|
+
const exec = promisify(execFile);
|
|
17
|
+
const CLI_PATH = join(import.meta.dirname, '..', '..', 'dist', 'index.js');
|
|
18
|
+
const FIXTURE_MD = join(import.meta.dirname, 'fixtures', 'test.md');
|
|
19
|
+
/**
|
|
20
|
+
* Run the CLI with the given args and return stdout/stderr.
|
|
21
|
+
*/
|
|
22
|
+
async function runCli(...args) {
|
|
23
|
+
return exec('node', [CLI_PATH, ...args], { timeout: 30000 });
|
|
24
|
+
}
|
|
25
|
+
describe('convert command', () => {
|
|
26
|
+
let tempDir;
|
|
27
|
+
beforeEach(async () => {
|
|
28
|
+
tempDir = join(tmpdir(), `squisq-convert-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
29
|
+
await mkdir(tempDir, { recursive: true });
|
|
30
|
+
});
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
it('converts a .md file to all formats', async () => {
|
|
35
|
+
const { stderr } = await runCli('convert', FIXTURE_MD, '-o', tempDir);
|
|
36
|
+
expect(stderr).to.include('Done.');
|
|
37
|
+
// Verify all four output files exist and are non-empty
|
|
38
|
+
for (const ext of ['docx', 'pdf', 'html', 'dbk']) {
|
|
39
|
+
const outPath = join(tempDir, `test.${ext}`);
|
|
40
|
+
const info = await stat(outPath);
|
|
41
|
+
expect(info.size).to.be.greaterThan(0);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
it('respects --formats to produce only selected formats', async () => {
|
|
45
|
+
await runCli('convert', FIXTURE_MD, '-o', tempDir, '-f', 'docx,pdf');
|
|
46
|
+
// These should exist
|
|
47
|
+
const docxStat = await stat(join(tempDir, 'test.docx'));
|
|
48
|
+
expect(docxStat.size).to.be.greaterThan(0);
|
|
49
|
+
const pdfStat = await stat(join(tempDir, 'test.pdf'));
|
|
50
|
+
expect(pdfStat.size).to.be.greaterThan(0);
|
|
51
|
+
// These should NOT exist
|
|
52
|
+
for (const ext of ['html', 'dbk']) {
|
|
53
|
+
try {
|
|
54
|
+
await stat(join(tempDir, `test.${ext}`));
|
|
55
|
+
expect.fail(`test.${ext} should not exist`);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
expect(err.code).to.equal('ENOENT');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
it('converts a .dbk container to all formats', async () => {
|
|
63
|
+
// Create a minimal .dbk container
|
|
64
|
+
const container = new MemoryContentContainer();
|
|
65
|
+
await container.writeDocument('# Container Doc\n\nSome content here.');
|
|
66
|
+
const blob = await containerToZip(container);
|
|
67
|
+
const buf = Buffer.from(await blob.arrayBuffer());
|
|
68
|
+
const dbkPath = join(tempDir, 'input.dbk');
|
|
69
|
+
await fsWriteFile(dbkPath, buf);
|
|
70
|
+
// Convert to a separate output dir to avoid overwriting the input .dbk
|
|
71
|
+
const outDir = join(tempDir, 'out');
|
|
72
|
+
const { stderr } = await runCli('convert', dbkPath, '-o', outDir);
|
|
73
|
+
expect(stderr).to.include('Done.');
|
|
74
|
+
for (const ext of ['docx', 'pdf', 'html', 'dbk']) {
|
|
75
|
+
const info = await stat(join(outDir, `input.${ext}`));
|
|
76
|
+
expect(info.size).to.be.greaterThan(0);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
it('exits with error for nonexistent input', async () => {
|
|
80
|
+
try {
|
|
81
|
+
await runCli('convert', '/nonexistent/file.md', '-o', tempDir);
|
|
82
|
+
expect.fail('Expected a non-zero exit');
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
// execFile rejects on non-zero exit
|
|
86
|
+
expect(err.stderr).to.include('Error');
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
describe('output validation', () => {
|
|
90
|
+
let validationDir;
|
|
91
|
+
before(async () => {
|
|
92
|
+
validationDir = join(tmpdir(), `squisq-validate-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
93
|
+
await mkdir(validationDir, { recursive: true });
|
|
94
|
+
await runCli('convert', FIXTURE_MD, '-o', validationDir);
|
|
95
|
+
});
|
|
96
|
+
after(async () => {
|
|
97
|
+
await rm(validationDir, { recursive: true, force: true });
|
|
98
|
+
});
|
|
99
|
+
it('DOCX is a valid ZIP with WordprocessingML content', async () => {
|
|
100
|
+
const data = await readFile(join(validationDir, 'test.docx'));
|
|
101
|
+
// ZIP files start with PK (0x50 0x4B)
|
|
102
|
+
expect(data[0]).to.equal(0x50);
|
|
103
|
+
expect(data[1]).to.equal(0x4b);
|
|
104
|
+
// Should contain Content_Types and document.xml inside
|
|
105
|
+
const asString = data.toString('latin1');
|
|
106
|
+
expect(asString).to.include('[Content_Types].xml');
|
|
107
|
+
expect(asString).to.include('word/document.xml');
|
|
108
|
+
});
|
|
109
|
+
it('PDF starts with %PDF- magic bytes', async () => {
|
|
110
|
+
const data = await readFile(join(validationDir, 'test.pdf'));
|
|
111
|
+
const header = data.subarray(0, 5).toString('ascii');
|
|
112
|
+
expect(header).to.equal('%PDF-');
|
|
113
|
+
});
|
|
114
|
+
it('HTML contains SquisqPlayer and root element', async () => {
|
|
115
|
+
const html = await readFile(join(validationDir, 'test.html'), 'utf-8');
|
|
116
|
+
expect(html).to.include('SquisqPlayer');
|
|
117
|
+
expect(html).to.include('squisq-root');
|
|
118
|
+
});
|
|
119
|
+
it('DBK container round-trips through zipToContainer', async () => {
|
|
120
|
+
const data = await readFile(join(validationDir, 'test.dbk'));
|
|
121
|
+
const container = await zipToContainer(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
|
|
122
|
+
const md = await container.readDocument();
|
|
123
|
+
expect(md).to.be.a('string').that.includes('# Test Document');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=convert.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.test.js","sourceRoot":"","sources":["../../src/__tests__/convert.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readInput.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/readInput.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* readInput unit tests
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, beforeEach, afterEach } from 'mocha';
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import { readInput } from '../util/readInput.js';
|
|
7
|
+
import { writeFile, mkdir, rm } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { containerToZip } from '@bendyline/squisq-formats/container';
|
|
11
|
+
import { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
12
|
+
const FIXTURE_MD = join(import.meta.dirname, 'fixtures', 'test.md');
|
|
13
|
+
describe('readInput', () => {
|
|
14
|
+
let tempDir;
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
tempDir = join(tmpdir(), `squisq-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
17
|
+
await mkdir(tempDir, { recursive: true });
|
|
18
|
+
});
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
it('reads a .md file into a container and MarkdownDocument', async () => {
|
|
23
|
+
const result = await readInput(FIXTURE_MD);
|
|
24
|
+
expect(result.markdownDoc).to.not.equal(null);
|
|
25
|
+
expect(result.markdownDoc).to.have.property('type', 'document');
|
|
26
|
+
expect(result.markdownDoc.children).to.be.an('array');
|
|
27
|
+
expect(result.markdownDoc.children.length).to.be.greaterThan(0);
|
|
28
|
+
const docContent = await result.container.readDocument();
|
|
29
|
+
expect(docContent).to.be.a('string').that.includes('# Test Document');
|
|
30
|
+
});
|
|
31
|
+
it('reads a .zip container', async () => {
|
|
32
|
+
// Build a minimal container ZIP
|
|
33
|
+
const container = new MemoryContentContainer();
|
|
34
|
+
await container.writeDocument('# From ZIP\n\nHello from a ZIP container.');
|
|
35
|
+
const blob = await containerToZip(container);
|
|
36
|
+
const buf = Buffer.from(await blob.arrayBuffer());
|
|
37
|
+
const zipPath = join(tempDir, 'test.zip');
|
|
38
|
+
await writeFile(zipPath, buf);
|
|
39
|
+
const result = await readInput(zipPath);
|
|
40
|
+
expect(result.markdownDoc).to.not.equal(null);
|
|
41
|
+
expect(result.markdownDoc.type).to.equal('document');
|
|
42
|
+
const doc = await result.container.readDocument();
|
|
43
|
+
expect(doc).to.include('# From ZIP');
|
|
44
|
+
});
|
|
45
|
+
it('reads a .dbk container (same as .zip)', async () => {
|
|
46
|
+
const container = new MemoryContentContainer();
|
|
47
|
+
await container.writeDocument('# From DBK\n\nHello from a DBK container.');
|
|
48
|
+
const blob = await containerToZip(container);
|
|
49
|
+
const buf = Buffer.from(await blob.arrayBuffer());
|
|
50
|
+
const dbkPath = join(tempDir, 'test.dbk');
|
|
51
|
+
await writeFile(dbkPath, buf);
|
|
52
|
+
const result = await readInput(dbkPath);
|
|
53
|
+
expect(result.markdownDoc).to.not.equal(null);
|
|
54
|
+
expect(result.markdownDoc.type).to.equal('document');
|
|
55
|
+
const doc = await result.container.readDocument();
|
|
56
|
+
expect(doc).to.include('# From DBK');
|
|
57
|
+
});
|
|
58
|
+
it('reads a folder with markdown and media', async () => {
|
|
59
|
+
const folderPath = join(tempDir, 'mydoc');
|
|
60
|
+
await mkdir(folderPath, { recursive: true });
|
|
61
|
+
await writeFile(join(folderPath, 'index.md'), '# Folder Doc\n\nContent here.');
|
|
62
|
+
await writeFile(join(folderPath, 'image.txt'), 'placeholder');
|
|
63
|
+
const result = await readInput(folderPath);
|
|
64
|
+
expect(result.markdownDoc).to.not.equal(null);
|
|
65
|
+
expect(result.markdownDoc.type).to.equal('document');
|
|
66
|
+
const doc = await result.container.readDocument();
|
|
67
|
+
expect(doc).to.include('# Folder Doc');
|
|
68
|
+
// Media files are also in the container
|
|
69
|
+
const files = await result.container.listFiles();
|
|
70
|
+
const paths = files.map((f) => f.path);
|
|
71
|
+
expect(paths).to.include('image.txt');
|
|
72
|
+
});
|
|
73
|
+
it('throws on folder with no markdown file', async () => {
|
|
74
|
+
const folderPath = join(tempDir, 'empty');
|
|
75
|
+
await mkdir(folderPath, { recursive: true });
|
|
76
|
+
await writeFile(join(folderPath, 'image.png'), Buffer.from([0x89, 0x50]));
|
|
77
|
+
try {
|
|
78
|
+
await readInput(folderPath);
|
|
79
|
+
expect.fail('Expected an error');
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
expect(err).to.be.instanceOf(Error);
|
|
83
|
+
expect(err.message).to.include('No markdown document');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
it('reads a .json file as Doc JSON', async () => {
|
|
87
|
+
const doc = {
|
|
88
|
+
articleId: 'test-article',
|
|
89
|
+
duration: 30,
|
|
90
|
+
blocks: [{ id: 'b1', startTime: 0, duration: 30, audioSegment: 0 }],
|
|
91
|
+
audio: { segments: [] },
|
|
92
|
+
};
|
|
93
|
+
const jsonPath = join(tempDir, 'doc.json');
|
|
94
|
+
await writeFile(jsonPath, JSON.stringify(doc));
|
|
95
|
+
const result = await readInput(jsonPath);
|
|
96
|
+
expect(result.markdownDoc).to.equal(null);
|
|
97
|
+
expect(result.doc).to.deep.include({ articleId: 'test-article', duration: 30 });
|
|
98
|
+
expect(result.doc.blocks).to.have.length(1);
|
|
99
|
+
});
|
|
100
|
+
it('reads a folder with doc.json instead of markdown', async () => {
|
|
101
|
+
const folderPath = join(tempDir, 'jsondoc');
|
|
102
|
+
await mkdir(folderPath, { recursive: true });
|
|
103
|
+
const doc = {
|
|
104
|
+
articleId: 'folder-doc',
|
|
105
|
+
duration: 20,
|
|
106
|
+
blocks: [],
|
|
107
|
+
audio: { segments: [] },
|
|
108
|
+
};
|
|
109
|
+
await writeFile(join(folderPath, 'doc.json'), JSON.stringify(doc));
|
|
110
|
+
await writeFile(join(folderPath, 'image.txt'), 'placeholder');
|
|
111
|
+
const result = await readInput(folderPath);
|
|
112
|
+
expect(result.markdownDoc).to.equal(null);
|
|
113
|
+
expect(result.doc).to.deep.include({ articleId: 'folder-doc' });
|
|
114
|
+
// Media files are still in the container
|
|
115
|
+
const files = await result.container.listFiles();
|
|
116
|
+
const paths = files.map((f) => f.path);
|
|
117
|
+
expect(paths).to.include('image.txt');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=readInput.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readInput.test.js","sourceRoot":"","sources":["../../src/__tests__/readInput.test.ts"],"names":[],"mappings":"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"}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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 type { Doc } from '@bendyline/squisq/schemas';
|
|
22
|
+
import type { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
23
|
+
import type { VideoQuality, VideoOrientation } from '@bendyline/squisq-video';
|
|
24
|
+
export type { VideoQuality, VideoOrientation } from '@bendyline/squisq-video';
|
|
25
|
+
export { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
26
|
+
export { readInput } from './util/readInput.js';
|
|
27
|
+
export type { ReadInputResult } from './util/readInput.js';
|
|
28
|
+
/** Options for renderDocToMp4. */
|
|
29
|
+
export interface RenderDocToMp4Options {
|
|
30
|
+
/** Output file path for the MP4. */
|
|
31
|
+
outputPath: string;
|
|
32
|
+
/** Frames per second (default: 30). */
|
|
33
|
+
fps?: number;
|
|
34
|
+
/** Encoding quality preset (default: 'normal'). */
|
|
35
|
+
quality?: VideoQuality;
|
|
36
|
+
/** Video orientation (default: 'landscape'). */
|
|
37
|
+
orientation?: VideoOrientation;
|
|
38
|
+
/** Override video width in pixels. */
|
|
39
|
+
width?: number;
|
|
40
|
+
/** Override video height in pixels. */
|
|
41
|
+
height?: number;
|
|
42
|
+
/** Caption style to bake into the video (default: none). */
|
|
43
|
+
captionStyle?: 'standard' | 'social';
|
|
44
|
+
/** Seconds of cover-slide pre-roll before the story starts (default: 0). */
|
|
45
|
+
coverPreRoll?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Progress callback. Called with a phase name and 0-100 percentage.
|
|
48
|
+
*/
|
|
49
|
+
onProgress?: (phase: string, percent: number) => void;
|
|
50
|
+
}
|
|
51
|
+
/** Result returned by renderDocToMp4. */
|
|
52
|
+
export interface RenderDocToMp4Result {
|
|
53
|
+
/** Duration of the rendered video in seconds (including pre-roll). */
|
|
54
|
+
duration: number;
|
|
55
|
+
/** Number of frames captured. */
|
|
56
|
+
frameCount: number;
|
|
57
|
+
/** Output file path. */
|
|
58
|
+
outputPath: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Render a Doc + media container to an MP4 video file.
|
|
62
|
+
*
|
|
63
|
+
* The container should contain audio and image files referenced by the Doc's
|
|
64
|
+
* audio.segments[].src and block image paths. Files are embedded as base64
|
|
65
|
+
* data URIs in a self-contained render HTML page.
|
|
66
|
+
*
|
|
67
|
+
* Requires:
|
|
68
|
+
* - Playwright (chromium) — for headless frame capture
|
|
69
|
+
* - FFmpeg — for video encoding (must be on PATH)
|
|
70
|
+
*
|
|
71
|
+
* @param doc - The Doc structure to render
|
|
72
|
+
* @param container - MemoryContentContainer with audio/image files
|
|
73
|
+
* @param options - Rendering and encoding options
|
|
74
|
+
* @returns Result with duration and frame count
|
|
75
|
+
*/
|
|
76
|
+
export declare function renderDocToMp4(doc: Doc, container: MemoryContentContainer, options: RenderDocToMp4Options): Promise<RenderDocToMp4Result>;
|
|
77
|
+
/** A thumbnail size specification. */
|
|
78
|
+
export interface ThumbnailSpec {
|
|
79
|
+
/** Label for the thumbnail (used in filename: `{slug}-{width}x{height}.jpg`). */
|
|
80
|
+
name: string;
|
|
81
|
+
/** Output width in pixels. */
|
|
82
|
+
width: number;
|
|
83
|
+
/** Output height in pixels. */
|
|
84
|
+
height: number;
|
|
85
|
+
/** FFmpeg video filter string (e.g., 'scale=1280:720'). */
|
|
86
|
+
filter: string;
|
|
87
|
+
}
|
|
88
|
+
/** Options for extractThumbnails. */
|
|
89
|
+
export interface ExtractThumbnailsOptions {
|
|
90
|
+
/** Path to the source MP4 video. */
|
|
91
|
+
videoPath: string;
|
|
92
|
+
/** Directory to write thumbnails into. */
|
|
93
|
+
outputDir: string;
|
|
94
|
+
/** Base slug for filenames (produces `{slug}-{width}x{height}.jpg`). */
|
|
95
|
+
slug: string;
|
|
96
|
+
/** Thumbnail sizes to generate. */
|
|
97
|
+
sizes: ThumbnailSpec[];
|
|
98
|
+
/** Overwrite existing thumbnails (default: false). */
|
|
99
|
+
force?: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Extract thumbnail images from the first frame of an MP4 video.
|
|
103
|
+
* Produces JPEG files at each specified size using FFmpeg video filters.
|
|
104
|
+
*/
|
|
105
|
+
export declare function extractThumbnails(options: ExtractThumbnailsOptions): Promise<void>;
|
|
106
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAM9E,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,kCAAkC;AAClC,MAAM,WAAW,qBAAqB;IACpC,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IAEnB,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,mDAAmD;IACnD,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,gDAAgD;IAChD,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;IAErC,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IAEjB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IAEnB,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA8L/B;AAmID,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qCAAqC;AACrC,MAAM,WAAW,wBAAwB;IACvC,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCxF"}
|