mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
wuh
This commit is contained in:
@@ -15,7 +15,6 @@ const MAX_BYTES = Number.parseInt(process.env.REPLAY_SEGMENT_MAX_BYTES || '0', 1
|
|||||||
const FFMPEG_BIN = process.env.FFMPEG_BIN || 'ffmpeg';
|
const FFMPEG_BIN = process.env.FFMPEG_BIN || 'ffmpeg';
|
||||||
const ROVER_SNAPSHOT_DIR = process.env.ROVER_SNAPSHOT_DIR || '/var/lib/rover-snapshots';
|
const ROVER_SNAPSHOT_DIR = process.env.ROVER_SNAPSHOT_DIR || '/var/lib/rover-snapshots';
|
||||||
const ROVER_SNAPSHOT_FPS = 3;
|
const ROVER_SNAPSHOT_FPS = 3;
|
||||||
const roverPreviews = new Map(); // key -> proc
|
|
||||||
|
|
||||||
const recorders = new Map(); // key -> { proc, source }
|
const recorders = new Map(); // key -> { proc, source }
|
||||||
let cleanupTimer = null;
|
let cleanupTimer = null;
|
||||||
@@ -55,60 +54,6 @@ function buildInputUrl(source) {
|
|||||||
return `srt://127.0.0.1:9000?streamid=read:${encodeURIComponent(source.id)}`;
|
return `srt://127.0.0.1:9000?streamid=read:${encodeURIComponent(source.id)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnRoverPreview(source) {
|
|
||||||
const key = sourceKey(source);
|
|
||||||
const inputUrl = buildInputUrl(source);
|
|
||||||
if (!inputUrl) return;
|
|
||||||
const outputPath = path.join(ROVER_SNAPSHOT_DIR, `${source.id}.jpg`);
|
|
||||||
ensureDir(ROVER_SNAPSHOT_DIR)
|
|
||||||
.then(() => {
|
|
||||||
const args = [
|
|
||||||
'-hide_banner',
|
|
||||||
'-loglevel',
|
|
||||||
'warning',
|
|
||||||
'-fflags',
|
|
||||||
'nobuffer',
|
|
||||||
'-i',
|
|
||||||
inputUrl,
|
|
||||||
'-vf',
|
|
||||||
`fps=${ROVER_SNAPSHOT_FPS},scale=${SCALE_WIDTH}:-1`,
|
|
||||||
'-q:v',
|
|
||||||
'8',
|
|
||||||
'-an',
|
|
||||||
'-f',
|
|
||||||
'image2',
|
|
||||||
'-update',
|
|
||||||
'1',
|
|
||||||
outputPath,
|
|
||||||
];
|
|
||||||
const proc = spawn(FFMPEG_BIN, args, { stdio: 'ignore' });
|
|
||||||
roverPreviews.set(key, proc);
|
|
||||||
proc.on('exit', (code, signal) => {
|
|
||||||
roverPreviews.delete(key);
|
|
||||||
if (!shouldRecord(source)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const delay = 2000;
|
|
||||||
logger.warn('Rover preview exited; restarting', { key, code, signal });
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!roverPreviews.has(key) && shouldRecord(source)) {
|
|
||||||
spawnRoverPreview(source);
|
|
||||||
}
|
|
||||||
}, delay);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
logger.warn('Rover preview setup failed', { key, err: err.message });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopRoverPreview(key) {
|
|
||||||
const proc = roverPreviews.get(key);
|
|
||||||
if (!proc) return;
|
|
||||||
proc.kill('SIGTERM');
|
|
||||||
roverPreviews.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureDir(dir) {
|
async function ensureDir(dir) {
|
||||||
await fsp.mkdir(dir, { recursive: true });
|
await fsp.mkdir(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
@@ -125,8 +70,14 @@ function spawnRecorder(source) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const outPattern = buildOutputPattern(key);
|
const outPattern = buildOutputPattern(key);
|
||||||
|
const outputSnapshot = source.type === 'rover'
|
||||||
|
? path.join(ROVER_SNAPSHOT_DIR, `${source.id}.jpg`)
|
||||||
|
: null;
|
||||||
ensureDir(path.dirname(outPattern))
|
ensureDir(path.dirname(outPattern))
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
|
if (outputSnapshot) {
|
||||||
|
await ensureDir(ROVER_SNAPSHOT_DIR);
|
||||||
|
}
|
||||||
const args = [
|
const args = [
|
||||||
'-hide_banner',
|
'-hide_banner',
|
||||||
'-loglevel',
|
'-loglevel',
|
||||||
@@ -137,35 +88,81 @@ function spawnRecorder(source) {
|
|||||||
'low_delay',
|
'low_delay',
|
||||||
'-i',
|
'-i',
|
||||||
inputUrl,
|
inputUrl,
|
||||||
'-r',
|
|
||||||
String(FPS),
|
|
||||||
'-vf',
|
|
||||||
`fps=${FPS},scale=${SCALE_WIDTH}:-1`,
|
|
||||||
'-an',
|
|
||||||
'-c:v',
|
|
||||||
'libx264',
|
|
||||||
'-preset',
|
|
||||||
'veryfast',
|
|
||||||
'-tune',
|
|
||||||
'zerolatency',
|
|
||||||
'-g',
|
|
||||||
String(FPS),
|
|
||||||
'-keyint_min',
|
|
||||||
String(FPS),
|
|
||||||
'-sc_threshold',
|
|
||||||
'0',
|
|
||||||
'-f',
|
|
||||||
'segment',
|
|
||||||
'-segment_time',
|
|
||||||
String(SEGMENT_SECONDS),
|
|
||||||
'-segment_format',
|
|
||||||
'mp4',
|
|
||||||
'-reset_timestamps',
|
|
||||||
'1',
|
|
||||||
'-strftime',
|
|
||||||
'1',
|
|
||||||
outPattern,
|
|
||||||
];
|
];
|
||||||
|
if (outputSnapshot) {
|
||||||
|
args.push(
|
||||||
|
'-filter_complex',
|
||||||
|
`[0:v]fps=${FPS},scale=${SCALE_WIDTH}:-1[vseg];` +
|
||||||
|
`[0:v]fps=${ROVER_SNAPSHOT_FPS},scale=${SCALE_WIDTH}:-1[vjpg]`,
|
||||||
|
'-map',
|
||||||
|
'[vseg]',
|
||||||
|
'-an',
|
||||||
|
'-c:v:0',
|
||||||
|
'libx264',
|
||||||
|
'-preset',
|
||||||
|
'veryfast',
|
||||||
|
'-tune',
|
||||||
|
'zerolatency',
|
||||||
|
'-g',
|
||||||
|
String(FPS),
|
||||||
|
'-keyint_min',
|
||||||
|
String(FPS),
|
||||||
|
'-sc_threshold',
|
||||||
|
'0',
|
||||||
|
'-f',
|
||||||
|
'segment',
|
||||||
|
'-segment_time',
|
||||||
|
String(SEGMENT_SECONDS),
|
||||||
|
'-segment_format',
|
||||||
|
'mp4',
|
||||||
|
'-reset_timestamps',
|
||||||
|
'1',
|
||||||
|
'-strftime',
|
||||||
|
'1',
|
||||||
|
outPattern,
|
||||||
|
'-map',
|
||||||
|
'[vjpg]',
|
||||||
|
'-an',
|
||||||
|
'-q:v:1',
|
||||||
|
'8',
|
||||||
|
'-f',
|
||||||
|
'image2',
|
||||||
|
'-update',
|
||||||
|
'1',
|
||||||
|
outputSnapshot,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
args.push(
|
||||||
|
'-r',
|
||||||
|
String(FPS),
|
||||||
|
'-vf',
|
||||||
|
`fps=${FPS},scale=${SCALE_WIDTH}:-1`,
|
||||||
|
'-an',
|
||||||
|
'-c:v',
|
||||||
|
'libx264',
|
||||||
|
'-preset',
|
||||||
|
'veryfast',
|
||||||
|
'-tune',
|
||||||
|
'zerolatency',
|
||||||
|
'-g',
|
||||||
|
String(FPS),
|
||||||
|
'-keyint_min',
|
||||||
|
String(FPS),
|
||||||
|
'-sc_threshold',
|
||||||
|
'0',
|
||||||
|
'-f',
|
||||||
|
'segment',
|
||||||
|
'-segment_time',
|
||||||
|
String(SEGMENT_SECONDS),
|
||||||
|
'-segment_format',
|
||||||
|
'mp4',
|
||||||
|
'-reset_timestamps',
|
||||||
|
'1',
|
||||||
|
'-strftime',
|
||||||
|
'1',
|
||||||
|
outPattern,
|
||||||
|
);
|
||||||
|
}
|
||||||
const proc = spawn(FFMPEG_BIN, args, { stdio: 'ignore' });
|
const proc = spawn(FFMPEG_BIN, args, { stdio: 'ignore' });
|
||||||
recorders.set(key, { proc, source });
|
recorders.set(key, { proc, source });
|
||||||
proc.on('exit', (code, signal) => {
|
proc.on('exit', (code, signal) => {
|
||||||
@@ -211,20 +208,12 @@ function syncRecorders() {
|
|||||||
if (!recorders.has(key)) {
|
if (!recorders.has(key)) {
|
||||||
spawnRecorder(source);
|
spawnRecorder(source);
|
||||||
}
|
}
|
||||||
if (source.type === 'rover' && !roverPreviews.has(key)) {
|
|
||||||
spawnRoverPreview(source);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
Array.from(recorders.keys()).forEach((key) => {
|
Array.from(recorders.keys()).forEach((key) => {
|
||||||
if (!desiredKeys.has(key)) {
|
if (!desiredKeys.has(key)) {
|
||||||
stopRecorder(key);
|
stopRecorder(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Array.from(roverPreviews.keys()).forEach((key) => {
|
|
||||||
if (!desiredKeys.has(key)) {
|
|
||||||
stopRoverPreview(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupSegments() {
|
async function cleanupSegments() {
|
||||||
|
|||||||
Reference in New Issue
Block a user