Skip to content

Commit 9f6bfb8

Browse files
Start an electron-converter basis for the video to SVG feature
1 parent 15ebddc commit 9f6bfb8

10 files changed

Lines changed: 139 additions & 3 deletions

File tree

electron-converter/index.html

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-9" />
5+
<script>
6+
window.addEventListener('load', async () => {
7+
let seek;
8+
const fps = 24;
9+
10+
const video = document.querySelector('video');
11+
const canvas = document.querySelector('canvas');
12+
13+
video.addEventListener('seeked', () => {
14+
const context = canvas.getContext('2d');
15+
context.drawImage(video, 0, 0);
16+
seek?.resolve();
17+
});
18+
19+
canvas.width = video.videoWidth;
20+
canvas.height = video.videoHeight;
21+
22+
const frames = ~~(video.duration * fps);
23+
for (let index = 0; index < frames; index++) {
24+
seek = defer();
25+
video.currentTime = index / fps;
26+
await seek.promise;
27+
const blobDeferred = defer();
28+
canvas.toBlob(blob => blobDeferred.resolve(blob));
29+
window.postMessage(await (await blobDeferred.promise).arrayBuffer());
30+
}
31+
});
32+
33+
function defer() {
34+
let resolve;
35+
let reject;
36+
const promise = new Promise((_resolve, _reject) => {
37+
resolve = _resolve;
38+
reject = _reject;
39+
});
40+
41+
return { resolve, reject, promise };
42+
}
43+
</script>
44+
</head>
45+
<body>
46+
<video src="sample.mp4"></video>
47+
<canvas></canvas>
48+
</body>
49+
</html>

electron-converter/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const electron = require('electron');
2+
3+
process.on('unhandledRejection', error => { throw error; });
4+
process.on('uncaughtException', error => { throw error; });
5+
6+
void async function () {
7+
await electron.app.whenReady();
8+
9+
const window = new electron.BrowserWindow({ width: 600, height: 400, webPreferences: { preload: electron.app.getAppPath() + '/preload.js' } });
10+
window.loadFile('index.html');
11+
await new Promise(resolve => window.webContents.once('dom-ready', resolve));
12+
13+
// Delay the closure of the window to allow the processing to finish first
14+
let done = false;
15+
window.once('close', event => {
16+
done = true;
17+
event.preventDefault();
18+
});
19+
20+
// TODO: Replace with a buffer cache
21+
const screenshotCache = [];
22+
electron.ipcMain.on('screenshot', (_event, arg) => {
23+
screenshotCache.push({ arrayBuffer: arg, stamp: new Date() });
24+
});
25+
26+
async function* screenshots() {
27+
while (!done) {
28+
// TODO: Replace with a buffer cache
29+
if (screenshotCache.length === 0) {
30+
await new Promise(resolve => setTimeout(resolve, 100));
31+
continue;
32+
}
33+
34+
const { arrayBuffer, stamp } = screenshotCache.shift();
35+
const buffer = Buffer.from(arrayBuffer);
36+
const nativeImage = electron.nativeImage.createFromBuffer(buffer);
37+
const { width, height } = await nativeImage.getSize();
38+
const crop = (/** @type {Patch} */ patch) => (patch ? nativeImage.crop(patch) : nativeImage).toPNG();
39+
yield { stamp, buffer, width, height, format: 'image/png', crop };
40+
}
41+
}
42+
43+
// TODO: Change this module to ESM while preserving preload functionality and
44+
// hook up the rest
45+
for await (const screenshot of screenshots()) {
46+
console.log(screenshot.stamp, screenshot.width, screenshot.height);
47+
}
48+
49+
// const marker = '<image class="_';
50+
// const stream = fs.createWriteStream('../screencast-converted.svg');
51+
// const _cache = {};
52+
// for await (const buffer of screencast(cache(screenshots, _cache))) {
53+
// stream.write(buffer);
54+
// if (buffer.startsWith(marker)) {
55+
// console.log(buffer.slice(marker.length, buffer.indexOf('"', marker.length)), '|'.repeat(_cache.overhead));
56+
// }
57+
// }
58+
59+
// stream.close();
60+
// process.exit();
61+
}()

electron-converter/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "commonjs"
3+
}

electron-converter/preload.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const electron = require('electron');
2+
3+
window.addEventListener('message', event => {
4+
electron.ipcRenderer.send('screenshot', event.data);
5+
});

electron-converter/sample.mp4

1.5 MB
Binary file not shown.

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<title>SVG Screencast</title>
66
</head>
77
<body>
8-
<a href="browser-generator">Generator</a>
98
<a href="inspector">Inspector</a>
10-
<a href="converter">Converter</a>
9+
<a href="browser-generator">Generator</a>
10+
<a href="browser-converter">Converter</a>
1111
</body>
1212
</html>

readme.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ The generated screencast is written into [`screencast-worker.svg`](screencast-wo
100100

101101
This generator runs the `screencast.js` code in a Node worker thread.
102102

103+
### Electron Converter
104+
105+
```sh
106+
cd electron-converter
107+
npx electron .
108+
```
109+
110+
The generated screencast is written into [`screencast-converted.svg`](screencast-converted.svg).
111+
103112
### Browser Generator / Converter / Inspector
104113

105114
Web-based tools can be accessed by serving this repository with a web server and
@@ -122,9 +131,18 @@ Manual testing is used to ensure no regressions in areas not covered by tests.
122131

123132
### To-Do
124133

134+
#### Figure out ESM without breaking preload and finish `electron-converter`
135+
136+
I had to revert it to CJS to test out the IPC communication sending the frames
137+
to the main process, but in it, I now can't use screencast to produce the file.
138+
139+
Changing to ESM will break the preload script (it will complain that it needs to
140+
be loaded using CJS in the browser window developer tools). I need to find a way
141+
to resolve this before I can get to generating the screencast file.
142+
125143
#### Make runnable through a CLI for video to screencast conversion feature
126144

127-
Reuse the `electron-generator` code to build a feature where when called using
145+
Use `electron-converter` to build a feature where when called using
128146
`npm tomashubelbauer/svg-screencast screencast.mp4`, a `screencast.svg` file
129147
would get generated in the same directory. Maybe also generate `screencast.html`
130148
which would be the `inspector` application with the SVG pre-loaded or hard-coded

0 commit comments

Comments
 (0)