Skip to content

Commit eb4c1e4

Browse files
committed
Add --mp4 option to transform webm to mp4
1 parent 9155eb9 commit eb4c1e4

4 files changed

Lines changed: 42 additions & 8 deletions

File tree

bin/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ program
4848
.option('-d, --delay <delay>', 'Delay after the image is rendered, e.g, `2s`')
4949
.option('-t, --time <time>', 'Record screen for a specific time, e.g, `10s')
5050
.option('-ws, --window-size <size>', 'The size of the rendered window, e.g, `800x600`, defaults to `1600x900`')
51+
.option('--mp4', 'Generate an mp4 video')
5152
.action(handleRender);
5253

5354
program

lib/handler.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export async function handleRender(source, options) {
2828
options.windowHeight = Number(h);
2929
}
3030

31+
if (/\.(mp4|webm)$/.test(options.output) && !options.time) {
32+
options.time = 5 * 1000;
33+
}
34+
3135
let title = options.time ? 'record' : 'image';
3236
if (source) {
3337
const basename = path.basename(source);

lib/render/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,20 @@ export async function render(code, options = {}) {
6060
tag = await page.$eval('css-doodle', el => el.seed);
6161
}
6262
let ext = options.time ? 'webm' : 'png';
63+
if (ext === 'webm' && options.mp4) {
64+
ext = 'mp4';
65+
}
6366
options.output = `${options.title}-${tag}.${ext}`;
6467
}
6568

69+
if (/\.(mp4|webm)$/.test(options.output) && !options.time) {
70+
options.time = 5 * 1000;
71+
}
72+
73+
if (options.output.endsWith('.mp4')) {
74+
options.mp4 = true;
75+
}
76+
6677
if (options.delay) {
6778
await setTimeout(options.delay);
6879
}

lib/render/screencast.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { setTimeout } from 'node:timers/promises';
2+
import { exec } from 'node:child_process';
23

34
export async function screencast(page, options) {
45
const { scale, output, selector, windowWidth, windowHeight } = options;
56

67
await page.setViewport({
78
width: windowWidth,
89
height: windowHeight,
10+
defaultScaleFactor: scale
911
});
1012

11-
const clip = await page.evaluate(selector => {
13+
const crop = await page.evaluate(selector => {
1214
const element = document.querySelector(selector);
1315
if (element) {
1416
const { width, height, x, y } = element.getBoundingClientRect();
@@ -27,18 +29,34 @@ export async function screencast(page, options) {
2729
}
2830
}, selector);
2931

30-
await page.setViewport({
31-
width: Math.ceil(clip.width) || windowWidth,
32-
height: Math.ceil(clip.height) || windowHeight,
33-
});
34-
3532
const recorder = await page.screencast({
3633
path: output,
37-
scale,
38-
clip,
34+
crop,
3935
});
4036

4137
await setTimeout(options.time);
4238
await recorder.stop();
39+
40+
if (options.mp4) {
41+
try {
42+
return await webmToMp4(output);
43+
} catch (e) {
44+
throw e;
45+
}
46+
}
47+
4348
return output;
4449
}
50+
51+
function webmToMp4(input) {
52+
const output = input.replace('.webm', '.mp4');
53+
return new Promise((resolve, reject) => {
54+
exec(`echo -y | ffmpeg -i ${input} -c:v copy ${output}`, (err, stdout, stderr) => {
55+
if (err) {
56+
reject(err);
57+
} else {
58+
resolve(output);
59+
}
60+
});
61+
});
62+
}

0 commit comments

Comments
 (0)