added and fix some of things

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-03 12:03:02 +08:00
parent 6765c1faba
commit d246c09cdd
31 changed files with 724 additions and 510 deletions
+49 -1
View File
@@ -99,4 +99,52 @@ function remuxToFaststartMp4(inputUrl, { timeoutMs = 30 * 60 * 1000 } = {}) {
});
}
module.exports = { needsRemux, remuxToFaststartMp4 };
// Grabs a single frame from a video as a JPEG thumbnail — used when a video
// asset lands with no client-provided thumbnail (see finalizeAssetFromStorage
// in assets.controller.js). Unlike remuxToFaststartMp4 above, this is cheap
// enough (one frame, not a full re-encode/copy) to run inline during the
// upload-finalize request rather than as a background job.
//
// input: presigned GET URL for the video, + its duration (seconds, from
// ffprobe) to pick a safe seek point.
// output: local filesystem path to the extracted .jpg (caller owns cleanup).
function extractFrameThumbnail(inputUrl, duration, { timeoutMs = 30 * 1000 } = {}) {
// Seek 1s in, or the midpoint for clips shorter than ~2s — avoids grabbing
// frame 0, which is often black/blank on screen recordings and slates.
const atSeconds = !duration || duration <= 0 ? 0 : Math.min(1, duration / 2);
const outputPath = path.join(os.tmpdir(), `thumb_${Date.now()}_${crypto.randomBytes(4).toString("hex")}.jpg`);
return new Promise((resolve, reject) => {
let settled = false;
const command = ffmpeg(inputUrl)
.seekInput(atSeconds) // input-side seek — fast, keyframe-based
.outputOptions(["-frames:v 1", "-q:v 2"]);
const timer = setTimeout(() => {
if (settled) return;
settled = true;
command.kill("SIGKILL");
fs.promises.unlink(outputPath).catch(() => {});
reject(new Error(`Thumbnail extraction timed out after ${timeoutMs}ms`));
}, timeoutMs);
command
.on("error", (err) => {
if (settled) return;
settled = true;
clearTimeout(timer);
fs.promises.unlink(outputPath).catch(() => {});
reject(err);
})
.on("end", () => {
if (settled) return;
settled = true;
clearTimeout(timer);
resolve(outputPath);
})
.save(outputPath);
});
}
module.exports = { needsRemux, remuxToFaststartMp4, extractFrameThumbnail };