Initial commit
This commit is contained in:
commit
e6836a8ab3
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules/
|
||||
/lib/
|
1370
package-lock.json
generated
Normal file
1370
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "record",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "lib/record.js",
|
||||
"scripts": {
|
||||
"start": "npm run build && node lib/record.js",
|
||||
"build": "tsc"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.8.2",
|
||||
"typescript": "^4.7.4",
|
||||
"zx": "^7.0.7"
|
||||
}
|
||||
}
|
125
record.ts
Executable file
125
record.ts
Executable file
@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env -S ts-node-esm --compilerOptions='{"module": "es2022"}'
|
||||
|
||||
$.verbose = false;
|
||||
|
||||
import { $ } from "zx";
|
||||
// Or
|
||||
import "zx/globals";
|
||||
import { spawn } from "child_process";
|
||||
import { PassThrough } from "stream";
|
||||
|
||||
function sleep(time: number) {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), time);
|
||||
});
|
||||
}
|
||||
|
||||
void (async function () {
|
||||
const source = spawn("ffmpeg", [
|
||||
"-f",
|
||||
"video4linux2",
|
||||
"-input_format",
|
||||
"nv12",
|
||||
"-thread_queue_size",
|
||||
"512",
|
||||
"-video_size",
|
||||
"3840x2160",
|
||||
"-i",
|
||||
"/dev/video2",
|
||||
"-f",
|
||||
"nut",
|
||||
"-c",
|
||||
"copy",
|
||||
"pipe:1",
|
||||
]);
|
||||
const preview = spawn("ffplay", [
|
||||
"-f",
|
||||
"nut",
|
||||
"-use_wallclock_as_timestamps",
|
||||
"1",
|
||||
"-",
|
||||
]);
|
||||
const convert_and_add_audio = spawn("ffmpeg", [
|
||||
"-y",
|
||||
"-vaapi_device",
|
||||
"/dev/dri/renderD128",
|
||||
"-f",
|
||||
"nut",
|
||||
"-i",
|
||||
"-",
|
||||
"-f",
|
||||
"pulse",
|
||||
"-thread_queue_size",
|
||||
"512",
|
||||
"-i",
|
||||
"alsa_input.usb-Elgato_Cam_Link_4K_0123456789000-03.analog-stereo",
|
||||
"-vf",
|
||||
"format=nv12,hwupload",
|
||||
"-c:v",
|
||||
"h264_vaapi",
|
||||
"-b:v",
|
||||
"12M",
|
||||
"-f",
|
||||
"mp4",
|
||||
"output.mp4",
|
||||
]);
|
||||
console.log("started ffmpeg...");
|
||||
|
||||
source.stderr.on("data", (data) => {
|
||||
// console.error("source", data.toString());
|
||||
});
|
||||
convert_and_add_audio.stderr.on("data", (data) => {
|
||||
// console.error("convert", data.toString());
|
||||
});
|
||||
preview.stderr.on("data", (data) => {
|
||||
// console.error("preview", data.toString());
|
||||
});
|
||||
|
||||
// preview.stderr.on("data", (data) => console.error(data.toString()));
|
||||
|
||||
const source_stream_for_convert = new PassThrough();
|
||||
const source_stream_for_preview = new PassThrough();
|
||||
// const source_stream_for_dump = new PassThrough();
|
||||
|
||||
// source.stdout.pipe(source_stream_for_convert);
|
||||
// // source.stdout.pipe(source_stream_for_preview);
|
||||
// source.stdout.pipe(source_stream_for_dump);
|
||||
//
|
||||
source_stream_for_convert.pipe(convert_and_add_audio.stdin);
|
||||
// source_stream_for_preview.pipe(preview.stdin);
|
||||
|
||||
let preview_ok = true;
|
||||
const stats = {};
|
||||
let count = 0;
|
||||
let preview_stopped = false;
|
||||
let preview_can_resume = false;
|
||||
let preview_should_stop_asap = false;
|
||||
source.stdout.on("data", (data) => {
|
||||
// this implements framedropping. we detect when a frame starts and emit it only when the stdin is drained
|
||||
count++;
|
||||
// console.log("got data!", a++);
|
||||
const beginning = data.slice(0, 4).join("");
|
||||
if (beginning == "7875228173") {
|
||||
//means the beginning of the frame (counted the most frequent start of data and that was the most frequent)
|
||||
if (preview_can_resume) {
|
||||
preview_stopped = false;
|
||||
}
|
||||
if (preview_should_stop_asap) {
|
||||
preview_should_stop_asap = false;
|
||||
preview_stopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!preview_stopped) {
|
||||
let preview_result = preview.stdin.write(data);
|
||||
if (!preview_result && !preview_should_stop_asap) {
|
||||
preview_should_stop_asap = true;
|
||||
preview_can_resume = false;
|
||||
preview.stdin.once("drain", () => {
|
||||
preview_can_resume = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
convert_and_add_audio.stdin.write(data);
|
||||
});
|
||||
})();
|
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"files": ["record.ts"],
|
||||
"exclude": ["node_modules/**/*"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user