@y11i-3d/chrome-recording 1.0.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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11 -0
- package/dist/concat.d.ts +2 -0
- package/dist/concat.js +31 -0
- package/dist/get_chrome_info.ps1 +62 -0
- package/dist/postinstall.d.ts +2 -0
- package/dist/postinstall.js +19 -0
- package/dist/record.d.ts +2 -0
- package/dist/record.js +142 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yuichiroh Arai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# chrome-recording
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@y11i-3d/chrome-recording)
|
|
4
|
+
[](https://github.com/y11i-3d/chrome-recording/actions/workflows/build.yml)
|
|
5
|
+
|
|
6
|
+
CLI for recording Windows Chrome content from WSL, using ffmpeg's ddagrab.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
- WSL (Windows Subsystem for Linux)
|
|
11
|
+
- Chrome running on Windows
|
|
12
|
+
- `ffmpeg.exe` available in PATH (Windows-side)
|
|
13
|
+
- `powershell.exe` available in PATH (Windows-side)
|
|
14
|
+
- `wslpath` available in PATH
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @y11i-3d/chrome-recording
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or run directly with `npx`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @y11i-3d/chrome-recording <subcommand> [options]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
After installation, a PowerShell script (`get_chrome_info.ps1`) is automatically copied to `%LOCALAPPDATA%\y11i-3d\chrome-recording\` on Windows. This script is required by the `record` command.
|
|
29
|
+
|
|
30
|
+
## Subcommands
|
|
31
|
+
|
|
32
|
+
| Subcommand | Description |
|
|
33
|
+
| ---------- | ------------------------------------------------------- |
|
|
34
|
+
| `record` | Capture Chrome browser content as a video or screenshot |
|
|
35
|
+
| `concat` | Concatenate recorded mp4 files into one |
|
|
36
|
+
|
|
37
|
+
## record
|
|
38
|
+
|
|
39
|
+
Capture the Chrome content area using ffmpeg's ddagrab.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
chrome-recording record [options]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
By default, recording starts immediately and stops when you press Enter. The output is saved to `./recordings/<timestamp>.mp4`.
|
|
46
|
+
|
|
47
|
+
| Option | Description |
|
|
48
|
+
| ------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
49
|
+
| `-s, --screenshot` | Take a screenshot instead of recording a video |
|
|
50
|
+
| `-o, --output <file>` | Output file path |
|
|
51
|
+
| `-c, --crop <top:right:bottom:left>` | Crop in CSS shorthand order (px) |
|
|
52
|
+
| `-m, --manual` | Wait for Enter before starting capture |
|
|
53
|
+
| `-d, --duration <seconds>` | Stop recording automatically after N seconds |
|
|
54
|
+
| `-q, --quality <value>` | Quality: plain number for CRF (e.g. `23`), number+unit for bitrate (e.g. `4000k`, `4M`) |
|
|
55
|
+
| `-f, --fps <fps>` | Frame rate |
|
|
56
|
+
| `-v, --verbose` | Show ffmpeg output |
|
|
57
|
+
|
|
58
|
+
### Examples
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Record until Enter is pressed
|
|
62
|
+
chrome-recording record
|
|
63
|
+
|
|
64
|
+
# Take a screenshot
|
|
65
|
+
chrome-recording record -s
|
|
66
|
+
|
|
67
|
+
# Record for 10 seconds at CRF 18
|
|
68
|
+
chrome-recording record -d 10 -q 18
|
|
69
|
+
|
|
70
|
+
# Record with manual start and crop
|
|
71
|
+
chrome-recording record -m -c 40:0:0:0
|
|
72
|
+
|
|
73
|
+
# Record to a specific file at 4 Mbps
|
|
74
|
+
chrome-recording record -o output.mp4 -q 4M
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## concat
|
|
78
|
+
|
|
79
|
+
Concatenate all mp4 files in a directory into a single file (stream copy, no re-encode).
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
chrome-recording concat [options]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Files named `concat_*.mp4` are excluded. Input files are sorted alphabetically.
|
|
86
|
+
|
|
87
|
+
| Option | Description |
|
|
88
|
+
| --------------------- | ------------------------------------------------------------ |
|
|
89
|
+
| `-i, --input <dir>` | Input directory (default: `./recordings`) |
|
|
90
|
+
| `-o, --output <file>` | Output file path (default: `<input>/concat_<timestamp>.mp4`) |
|
|
91
|
+
| `-v, --verbose` | Show ffmpeg output |
|
|
92
|
+
|
|
93
|
+
### Examples
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Concatenate all recordings in ./recordings
|
|
97
|
+
chrome-recording concat
|
|
98
|
+
|
|
99
|
+
# Concatenate from a specific directory
|
|
100
|
+
chrome-recording concat -i ./clips -o final.mp4
|
|
101
|
+
```
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerConcatCommand } from "./concat.js";
|
|
4
|
+
import { registerRecordCommand } from "./record.js";
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program
|
|
7
|
+
.name("chrome-recording")
|
|
8
|
+
.description("Record Chrome browser content in WSL");
|
|
9
|
+
registerRecordCommand(program);
|
|
10
|
+
registerConcatCommand(program);
|
|
11
|
+
program.parse();
|
package/dist/concat.d.ts
ADDED
package/dist/concat.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
export function registerConcatCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("concat")
|
|
8
|
+
.description("Concatenate recorded mp4 files")
|
|
9
|
+
.option("-i, --input <dir>", "Input directory", resolve(process.cwd(), "recordings"))
|
|
10
|
+
.option("-o, --output <file>", "Output file path")
|
|
11
|
+
.option("-v, --verbose", "Show ffmpeg output")
|
|
12
|
+
.action((options) => {
|
|
13
|
+
const files = readdirSync(options.input)
|
|
14
|
+
.filter((f) => f.endsWith(".mp4") && !f.startsWith("concat_"))
|
|
15
|
+
.sort()
|
|
16
|
+
.map((f) => resolve(options.input, f));
|
|
17
|
+
if (files.length === 0)
|
|
18
|
+
throw new Error("No mp4 files to concatenate");
|
|
19
|
+
const outputFile = options.output ?? resolve(options.input, `concat_${Date.now()}.mp4`);
|
|
20
|
+
const concatList = resolve(tmpdir(), `concat_list_${Date.now()}.txt`);
|
|
21
|
+
writeFileSync(concatList, files.map((f) => `file '${f}'`).join("\n"));
|
|
22
|
+
const stdio = options.verbose ? "inherit" : "ignore";
|
|
23
|
+
try {
|
|
24
|
+
execSync(`ffmpeg.exe -f concat -safe 0 -i "${concatList}" -c copy -y "${outputFile}"`, { stdio: ["ignore", stdio, stdio] });
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
unlinkSync(concatList);
|
|
28
|
+
}
|
|
29
|
+
console.log(`Done: ${outputFile}`);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
2
|
+
|
|
3
|
+
Add-Type -TypeDefinition @"
|
|
4
|
+
using System;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.Runtime.InteropServices;
|
|
7
|
+
using System.Text;
|
|
8
|
+
public class Win32 {
|
|
9
|
+
public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
|
|
10
|
+
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
|
|
11
|
+
[DllImport("user32.dll")] public static extern int GetClassName(IntPtr hwnd, StringBuilder buf, int maxCount);
|
|
12
|
+
[DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr parent, EnumWindowsProc cb, IntPtr lParam);
|
|
13
|
+
public struct RECT { public int Left, Top, Right, Bottom; }
|
|
14
|
+
|
|
15
|
+
public static List<RECT> FindContentRects(IntPtr chromeHwnd) {
|
|
16
|
+
RECT mainRect;
|
|
17
|
+
GetWindowRect(chromeHwnd, out mainRect);
|
|
18
|
+
var results = new List<RECT>();
|
|
19
|
+
EnumChildWindows(chromeHwnd, (h, _) => {
|
|
20
|
+
var buf = new StringBuilder(256);
|
|
21
|
+
GetClassName(h, buf, 256);
|
|
22
|
+
if (buf.ToString() == "Chrome_RenderWidgetHostHWND") {
|
|
23
|
+
RECT r;
|
|
24
|
+
GetWindowRect(h, out r);
|
|
25
|
+
if (r.Top >= mainRect.Top && r.Top < mainRect.Bottom) {
|
|
26
|
+
results.Add(r);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}, IntPtr.Zero);
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
"@
|
|
35
|
+
|
|
36
|
+
$proc = Get-Process chrome -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowTitle -ne "" } | Select-Object -First 1
|
|
37
|
+
if (-not $proc) {
|
|
38
|
+
Write-Error "Chrome is not running or has no visible window."
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$rects = [Win32]::FindContentRects($proc.MainWindowHandle)
|
|
43
|
+
$best = $rects | Sort-Object { $_.Left }, { $_.Top } | Select-Object -First 1
|
|
44
|
+
|
|
45
|
+
if (-not $best) {
|
|
46
|
+
Write-Error "Chrome content area (Chrome_RenderWidgetHostHWND) not found."
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$x = $best.Left
|
|
51
|
+
$y = $best.Top
|
|
52
|
+
$w = $best.Right - $best.Left
|
|
53
|
+
$h = $best.Bottom - $best.Top
|
|
54
|
+
|
|
55
|
+
Write-Output "content $x $y $w $h"
|
|
56
|
+
|
|
57
|
+
$i = 0
|
|
58
|
+
foreach ($s in [System.Windows.Forms.Screen]::AllScreens) {
|
|
59
|
+
$b = $s.Bounds
|
|
60
|
+
Write-Output "monitor $i $($b.Left) $($b.Top) $($b.Right) $($b.Bottom)"
|
|
61
|
+
$i++
|
|
62
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { copyFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import pkg from "../package.json" with { type: "json" };
|
|
7
|
+
const [scope, pkgName] = pkg.name.replace(/^@/, "").split("/");
|
|
8
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const ps1Src = resolve(scriptDir, "get_chrome_info.ps1");
|
|
10
|
+
const localAppDataWin = execSync("powershell.exe -Command '$env:LOCALAPPDATA'")
|
|
11
|
+
.toString()
|
|
12
|
+
.trim();
|
|
13
|
+
const localAppDataLinux = execSync(`wslpath -u "${localAppDataWin}"`)
|
|
14
|
+
.toString()
|
|
15
|
+
.trim();
|
|
16
|
+
const destDir = resolve(localAppDataLinux, scope ?? pkg.name, pkgName ?? "");
|
|
17
|
+
mkdirSync(destDir, { recursive: true });
|
|
18
|
+
copyFileSync(ps1Src, resolve(destDir, "get_chrome_info.ps1"));
|
|
19
|
+
console.log(`Installed get_chrome_info.ps1 to ${destDir}`);
|
package/dist/record.d.ts
ADDED
package/dist/record.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { execSync, spawn } from "child_process";
|
|
2
|
+
import { mkdirSync } from "fs";
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
export function registerRecordCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("record")
|
|
8
|
+
.description("Record Chrome browser content")
|
|
9
|
+
.option("-s, --screenshot", "Take a screenshot", false)
|
|
10
|
+
.option("-o, --output <file>", "Output file path")
|
|
11
|
+
.option("-c, --crop <top:right:bottom:left>", "Crop (CSS order)")
|
|
12
|
+
.option("-m, --manual", "Start recording manually")
|
|
13
|
+
.option("-d, --duration <seconds>", "Recording duration in seconds")
|
|
14
|
+
.option("-q, --quality <value>", "Quality: number for CRF, number+unit for bitrate (e.g. 23, 4000k, 4M)")
|
|
15
|
+
.option("-f, --fps <fps>", "Frame rate")
|
|
16
|
+
.option("-v, --verbose", "Show ffmpeg output")
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
const missing = ["powershell.exe", "ffmpeg.exe", "wslpath"].filter((cmd) => {
|
|
19
|
+
try {
|
|
20
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (missing.length > 0) {
|
|
28
|
+
throw new Error(`Command(s) not found: ${missing.join(", ")}\nWSL + Windows environment required.`);
|
|
29
|
+
}
|
|
30
|
+
const localAppDataWin = execSync("powershell.exe -Command '$env:LOCALAPPDATA'")
|
|
31
|
+
.toString()
|
|
32
|
+
.trim();
|
|
33
|
+
const ps1Win = `${localAppDataWin}\\y11i-3d\\chrome-recording\\get_chrome_info.ps1`;
|
|
34
|
+
const info = execSync(`powershell.exe -ExecutionPolicy Bypass -File "${ps1Win}"`)
|
|
35
|
+
.toString()
|
|
36
|
+
.trim()
|
|
37
|
+
.split("\n")
|
|
38
|
+
.map((l) => l.trim().replace(/\r/, ""));
|
|
39
|
+
const contentLine = info.find((l) => l.startsWith("content"));
|
|
40
|
+
if (!contentLine) {
|
|
41
|
+
throw new Error("Failed to get Chrome content area info");
|
|
42
|
+
}
|
|
43
|
+
const contentParts = contentLine.split(/\s+/).slice(1).map(Number);
|
|
44
|
+
const contentX = contentParts[0] ?? 0;
|
|
45
|
+
const contentY = contentParts[1] ?? 0;
|
|
46
|
+
const contentW = contentParts[2] ?? 0;
|
|
47
|
+
const contentH = contentParts[3] ?? 0;
|
|
48
|
+
const monitors = info
|
|
49
|
+
.filter((l) => l.startsWith("monitor"))
|
|
50
|
+
.map((l) => {
|
|
51
|
+
const parts = l.split(/\s+/).map(Number);
|
|
52
|
+
const idx = parts[1] ?? 0;
|
|
53
|
+
const left = parts[2] ?? 0;
|
|
54
|
+
const top = parts[3] ?? 0;
|
|
55
|
+
const right = parts[4] ?? 0;
|
|
56
|
+
const bottom = parts[5] ?? 0;
|
|
57
|
+
return { idx, left, top, right, bottom };
|
|
58
|
+
});
|
|
59
|
+
const monitor = monitors.find((m) => contentX >= m.left &&
|
|
60
|
+
contentX < m.right &&
|
|
61
|
+
contentY >= m.top &&
|
|
62
|
+
contentY < m.bottom);
|
|
63
|
+
if (!monitor)
|
|
64
|
+
throw new Error("Chrome content area not found");
|
|
65
|
+
const offsetX = contentX - monitor.left;
|
|
66
|
+
const offsetY = contentY - monitor.top;
|
|
67
|
+
console.log(`Capturing monitor ${monitor.idx + 1} at (${offsetX}, ${offsetY}), size ${contentW}x${contentH}`);
|
|
68
|
+
const rl = createInterface({
|
|
69
|
+
input: process.stdin,
|
|
70
|
+
output: process.stdout,
|
|
71
|
+
});
|
|
72
|
+
const waitEnter = (msg) => new Promise((res) => {
|
|
73
|
+
process.stdout.write(msg);
|
|
74
|
+
rl.once("line", () => res());
|
|
75
|
+
});
|
|
76
|
+
const [cropT, cropR, cropB, cropL] = options.crop
|
|
77
|
+
? options.crop.split(":").map(Number)
|
|
78
|
+
: [0, 0, 0, 0];
|
|
79
|
+
const cropW = `floor((iw-${(cropL ?? 0) + (cropR ?? 0)})/2)*2`;
|
|
80
|
+
const cropH = `floor((ih-${(cropT ?? 0) + (cropB ?? 0)})/2)*2`;
|
|
81
|
+
const cropFilter = `crop=${cropW}:${cropH}:${cropL ?? 0}:${cropT ?? 0}`;
|
|
82
|
+
const screenshot = options.screenshot;
|
|
83
|
+
const duration = options.duration !== undefined ? Number(options.duration) : null;
|
|
84
|
+
const qualityArgs = options.quality === undefined
|
|
85
|
+
? []
|
|
86
|
+
: /[a-zA-Z]$/.test(options.quality)
|
|
87
|
+
? ["-b:v", options.quality]
|
|
88
|
+
: ["-crf", options.quality];
|
|
89
|
+
const ext = screenshot ? "png" : "mp4";
|
|
90
|
+
const outputLinux = options.output ??
|
|
91
|
+
resolve(process.cwd(), "recordings", `${Date.now()}.${ext}`);
|
|
92
|
+
mkdirSync(dirname(outputLinux), { recursive: true });
|
|
93
|
+
const outputWin = execSync(`wslpath -w "${outputLinux}"`)
|
|
94
|
+
.toString()
|
|
95
|
+
.trim();
|
|
96
|
+
if (options.manual) {
|
|
97
|
+
await waitEnter(`Press Enter to ${screenshot ? "capture" : "start recording"}...`);
|
|
98
|
+
}
|
|
99
|
+
const ddagrabParams = [
|
|
100
|
+
`ddagrab=output_idx=${monitor.idx}`,
|
|
101
|
+
`offset_x=${offsetX}`,
|
|
102
|
+
`offset_y=${offsetY}`,
|
|
103
|
+
`video_size=${contentW}x${contentH}`,
|
|
104
|
+
...(options.fps !== undefined ? [`framerate=${options.fps}`] : []),
|
|
105
|
+
];
|
|
106
|
+
const ddagrab = ddagrabParams.join(":");
|
|
107
|
+
const ffmpegArgs = screenshot
|
|
108
|
+
? [
|
|
109
|
+
"-filter_complex",
|
|
110
|
+
`${ddagrab},hwdownload,format=bgra,${cropFilter}`,
|
|
111
|
+
"-frames:v",
|
|
112
|
+
"1",
|
|
113
|
+
"-y",
|
|
114
|
+
outputWin,
|
|
115
|
+
]
|
|
116
|
+
: [
|
|
117
|
+
"-filter_complex",
|
|
118
|
+
`${ddagrab},hwdownload,format=bgra,${cropFilter}`,
|
|
119
|
+
...(duration !== null ? ["-t", String(duration)] : []),
|
|
120
|
+
"-c:v",
|
|
121
|
+
"libx264",
|
|
122
|
+
...qualityArgs,
|
|
123
|
+
"-y",
|
|
124
|
+
outputWin,
|
|
125
|
+
];
|
|
126
|
+
const io = options.verbose ? "inherit" : "ignore";
|
|
127
|
+
const ffmpeg = spawn("ffmpeg.exe", ffmpegArgs, {
|
|
128
|
+
stdio: ["pipe", io, io],
|
|
129
|
+
});
|
|
130
|
+
ffmpeg.on("close", () => {
|
|
131
|
+
console.log(`\nDone: ${outputLinux}`);
|
|
132
|
+
});
|
|
133
|
+
if (screenshot || duration !== null) {
|
|
134
|
+
rl.close();
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
await waitEnter("Press Enter to stop recording...");
|
|
138
|
+
rl.close();
|
|
139
|
+
ffmpeg.stdin?.end("q");
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@y11i-3d/chrome-recording",
|
|
3
|
+
"description": "CLI for recording Windows Chrome content from WSL, using ffmpeg's ddagrab.",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"recording",
|
|
6
|
+
"chrome",
|
|
7
|
+
"ffmpeg",
|
|
8
|
+
"windows",
|
|
9
|
+
"wsl"
|
|
10
|
+
],
|
|
11
|
+
"version": "1.0.0",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"bin": {
|
|
17
|
+
"chrome-recording": "dist/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rm -rf dist && tsc && cp src/get_chrome_info.ps1 dist/",
|
|
21
|
+
"postinstall": "node dist/postinstall.js"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@eslint/js": "^10.0.1",
|
|
25
|
+
"@types/node": "^25.6.0",
|
|
26
|
+
"eslint": "^10.2.0",
|
|
27
|
+
"globals": "^17.4.0",
|
|
28
|
+
"typescript": "^6.0.2",
|
|
29
|
+
"typescript-eslint": "^8.58.1"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "Yuichiroh Arai",
|
|
34
|
+
"url": "https://yuichiroharai.com/"
|
|
35
|
+
},
|
|
36
|
+
"repository": "github:y11i-3d/chrome-recording",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"commander": "^14.0.3"
|
|
39
|
+
}
|
|
40
|
+
}
|