init
This commit is contained in:
104
src/hooks.server.ts
Normal file
104
src/hooks.server.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
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");
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user