105 lines
2.5 KiB
TypeScript
105 lines
2.5 KiB
TypeScript
import schedule from "node-schedule";
|
|
import { spawn } from "child_process";
|
|
import { existsSync, mkdirSync } from "fs";
|
|
import { join } from "path";
|
|
import { getAllStreams, getStreamProcess } from "$lib/server/streaming.js";
|
|
|
|
const TIMELAPSE_OUTPUT_PATH =
|
|
process.env.TIMELAPSE_OUTPUT_PATH || "./timelapse";
|
|
const CAPTURE_INTERVAL = "0 * * * *"; // Every hour at minute 0
|
|
|
|
let captureJob: any = null;
|
|
let initialized = false;
|
|
|
|
function ensureOutputDirectory() {
|
|
if (!existsSync(TIMELAPSE_OUTPUT_PATH)) {
|
|
mkdirSync(TIMELAPSE_OUTPUT_PATH, { recursive: true });
|
|
}
|
|
}
|
|
|
|
function captureTimelapseFrame() {
|
|
const streams = getAllStreams();
|
|
|
|
streams.forEach((streamInfo) => {
|
|
const stream = getStreamProcess(streamInfo.streamId);
|
|
if (!stream || !stream.process || !stream.process.stdout) {
|
|
console.error(
|
|
`[Timelapse] Stream ${streamInfo.streamId} not available for capture`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
const filename = `${streamInfo.streamId}_${timestamp}.jpg`;
|
|
const outputPath = join(TIMELAPSE_OUTPUT_PATH, filename);
|
|
|
|
const ffmpegArgs = [
|
|
"-rtsp_transport",
|
|
"tcp",
|
|
"-i",
|
|
streamInfo.rtspUrl,
|
|
"-vframes",
|
|
"1",
|
|
"-q:v",
|
|
"2",
|
|
outputPath,
|
|
];
|
|
|
|
const ffmpegProcess = spawn("ffmpeg", ffmpegArgs);
|
|
|
|
ffmpegProcess.on("error", (err: Error) => {
|
|
console.error(
|
|
`[Timelapse] FFmpeg error for ${streamInfo.streamId}:`,
|
|
err,
|
|
);
|
|
});
|
|
|
|
ffmpegProcess.on("close", (code: number | null) => {
|
|
if (code === 0) {
|
|
console.log(
|
|
`[Timelapse] Captured frame for ${streamInfo.streamId}: ${filename}`,
|
|
);
|
|
} else {
|
|
console.error(
|
|
`[Timelapse] FFmpeg exited with code ${code} for ${streamInfo.streamId}`,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function initializeTimelapse() {
|
|
if (initialized) {
|
|
return;
|
|
}
|
|
|
|
ensureOutputDirectory();
|
|
console.log(`[Timelapse] Output directory: ${TIMELAPSE_OUTPUT_PATH}`);
|
|
console.log("[Timelapse] Starting hourly capture job...");
|
|
|
|
captureJob = schedule.scheduleJob(CAPTURE_INTERVAL, () => {
|
|
console.log("[Timelapse] Running hourly capture job...");
|
|
captureTimelapseFrame();
|
|
});
|
|
|
|
initialized = true;
|
|
console.log("[Timelapse] Hourly capture job initialized");
|
|
}
|
|
|
|
export async function handle({ event, resolve }) {
|
|
// Initialize timelapse on first request
|
|
if (!initialized) {
|
|
initializeTimelapse();
|
|
}
|
|
|
|
return resolve(event);
|
|
}
|
|
|
|
// Cleanup on shutdown
|
|
process.on("exit", () => {
|
|
if (captureJob) {
|
|
captureJob.cancel();
|
|
console.log("[Timelapse] Capture job cancelled");
|
|
}
|
|
});
|