11import path from "node:path" ;
22import fs from "node:fs/promises" ;
33import crypto from "node:crypto" ;
4- import child_process from "node:child_process" ;
54import { Buffer } from "node:buffer" ;
65import sql from "../sql.ts" ;
76import detectScene from "./lib/detect-scene.ts" ;
7+ import generateVideoPreview from "./lib/generate-video-preview.ts" ;
88
99const { VIDEO_PATH , TRACE_API_SALT , MEDIA_QUEUE = Infinity } = process . env ;
1010
11- const generateVideoPreview = async ( filePath , start , end , size = "m" , mute = false ) =>
12- new Promise ( ( resolve ) => {
13- const ffmpeg = child_process . spawn (
14- "ffmpeg" ,
15- [
16- "-hide_banner" ,
17- "-loglevel" ,
18- "error" ,
19- "-nostats" ,
20- "-y" ,
21- "-ss" ,
22- start - 10 ,
23- "-i" ,
24- filePath ,
25- "-ss" ,
26- "10" ,
27- "-t" ,
28- end - start ,
29- mute ? "-an" : "-y" ,
30- "-map" ,
31- "0:v:0" ,
32- "-map" ,
33- "0:a:0?" ,
34- "-vf" ,
35- `scale=${ { l : 640 , m : 320 , s : 160 } [ size ] } :-2` ,
36- "-c:v" ,
37- "libx264" ,
38- "-crf" ,
39- "25" ,
40- "-profile:v" ,
41- "high" ,
42- "-preset" ,
43- "veryfast" ,
44- "-bf" ,
45- "8" ,
46- "-r" ,
47- "24000/1001" ,
48- "-pix_fmt" ,
49- "yuv420p" ,
50- "-c:a" ,
51- "aac" ,
52- "-ac" ,
53- "2" ,
54- "-b:a" ,
55- "64k" ,
56- "-max_muxing_queue_size" ,
57- "1024" ,
58- "-movflags" ,
59- "empty_moov+faststart" ,
60- "-map_metadata" ,
61- "-1" ,
62- "-map_chapters" ,
63- "-1" ,
64- "-f" ,
65- "mp4" ,
66- "-" ,
67- ] ,
68- { timeout : 10000 } ,
69- ) ;
70- ffmpeg . stderr . on ( "data" , ( data ) => {
71- console . log ( data . toString ( ) ) ;
72- } ) ;
73- const chunks = [ ] ;
74- ffmpeg . stdout . on ( "data" , ( data ) => {
75- chunks . push ( data ) ;
76- } ) ;
77- ffmpeg . on ( "close" , ( ) => {
78- resolve ( Buffer . concat ( chunks ) ) ;
79- } ) ;
80- } ) ;
81-
8211export default async ( req , res ) => {
8312 const [ fileId , time , expire , hash ] = req . app . locals . sqids . decode ( req . params . id ) ;
8413 const buf = Buffer . from ( TRACE_API_SALT ) ;
@@ -137,7 +66,6 @@ export default async (req, res) => {
13766 }
13867
13968 const muted = "mute" in req . query ;
140- const video = await generateVideoPreview ( videoFilePath , scene . start , scene . end , size , muted ) ;
14169
14270 res . set ( "Cache-Control" , "max-age=86400" ) ;
14371 res . set ( "Content-Type" , "video/mp4" ) ;
@@ -146,10 +74,22 @@ export default async (req, res) => {
14674 res . set ( "x-video-end" , scene . end ) ;
14775 res . set ( "x-video-duration" , scene . duration ) ;
14876 res . set ( "Access-Control-Expose-Headers" , "x-video-start, x-video-end, x-video-duration" ) ;
149- res . send ( video ) ;
77+
78+ const ffmpeg = generateVideoPreview ( videoFilePath , scene . start , scene . end , size , muted ) ;
79+
80+ ffmpeg . stdout . pipe ( res ) ;
81+
82+ await new Promise < void > ( ( resolve ) => {
83+ ffmpeg . on ( "close" , ( ) => resolve ( ) ) ;
84+ ffmpeg . on ( "error" , ( err ) => {
85+ console . log ( err ) ;
86+ res . end ( ) ;
87+ resolve ( ) ;
88+ } ) ;
89+ } ) ;
15090 } catch ( e ) {
15191 console . log ( e ) ;
152- res . status ( 500 ) . send ( "Internal Server Error" ) ;
92+ if ( ! res . headersSent ) res . status ( 500 ) . send ( "Internal Server Error" ) ;
15393 }
15494 req . app . locals . mediaQueue -- ;
15595} ;
0 commit comments