(req, res, url)
| 721 | } |
| 722 | |
| 723 | async function handleFrame(req, res, url) { |
| 724 | const id = url.searchParams.get("id"); |
| 725 | const clip = url.searchParams.get("clip") === "2" ? "video2" : "video1"; |
| 726 | const frame = Math.max(0, Number(url.searchParams.get("frame") || 0)); |
| 727 | const session = sessions.get(id); |
| 728 | if (!session) return text(res, 404, "Session not found"); |
| 729 | |
| 730 | try { |
| 731 | const args = [ |
| 732 | "-v", "error", |
| 733 | "-i", session[clip], |
| 734 | "-vf", `select=eq(n\\,${frame}),scale=180:-1`, |
| 735 | "-frames:v", "1", |
| 736 | "-f", "image2pipe", |
| 737 | "-vcodec", "mjpeg", |
| 738 | "-" |
| 739 | ]; |
| 740 | const { stdout } = await run(FFMPEG, args, { encoding: "buffer" }); |
| 741 | res.writeHead(200, { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=86400" }); |
| 742 | res.end(stdout); |
| 743 | } catch (error) { |
| 744 | json(res, 500, { error: error.message, detail: error.stderr || "" }); |
| 745 | } |
| 746 | } |
| 747 | |
| 748 | function clampNumber(value, min, max) { |
| 749 | const n = Number(value); |
no test coverage detected