66 * Supports CPU (libx264) and GPU encoding.
77 */
88
9- import { spawn } from "child_process" ;
109import { copyFileSync , existsSync , mkdirSync , readdirSync , statSync , writeFileSync } from "fs" ;
1110import { join , dirname , extname } from "path" ;
12- import { trackChildProcess } from "../utils/processTracker.js" ;
1311import { DEFAULT_CONFIG , type EngineConfig } from "../config.js" ;
1412import {
1513 type GpuEncoder ,
@@ -20,7 +18,6 @@ import {
2018import { type HdrTransfer , getHdrEncoderColorParams } from "../utils/hdr.js" ;
2119import { withEvenDimensionPad } from "../utils/evenDimensions.js" ;
2220import { formatFfmpegError , runFfmpeg } from "../utils/runFfmpeg.js" ;
23- import { getFfmpegBinary } from "../utils/ffmpegBinaries.js" ;
2421import { extractAudioMetadata } from "../utils/ffprobe.js" ;
2522import { type Fps , fpsToFfmpegArg } from "@hyperframes/core" ;
2623import type { EncoderOptions , EncodeResult , MuxResult } from "./chunkEncoder.types.js" ;
@@ -484,82 +481,40 @@ export async function encodeFramesFromDir(
484481 const inputPath = join ( framesDir , framePattern ) ;
485482 const inputArgs = [ "-framerate" , fpsToFfmpegArg ( options . fps ) , "-i" , inputPath ] ;
486483 const args = buildEncoderArgs ( options , inputArgs , outputPath , gpuEncoder ) ;
487-
488- return new Promise ( ( resolve ) => {
489- const ffmpeg = spawn ( getFfmpegBinary ( ) , args ) ;
490- trackChildProcess ( ffmpeg ) ;
491- let stderr = "" ;
492- const onAbort = ( ) => {
493- ffmpeg . kill ( "SIGTERM" ) ;
484+ const encodeTimeout = config ?. ffmpegEncodeTimeout ?? DEFAULT_CONFIG . ffmpegEncodeTimeout ;
485+ const result = await runFfmpeg ( args , { signal, timeout : encodeTimeout } ) ;
486+ if ( result . terminationReason === "abort" ) {
487+ return {
488+ success : false ,
489+ outputPath,
490+ durationMs : result . durationMs ,
491+ framesEncoded : 0 ,
492+ fileSize : 0 ,
493+ error : "FFmpeg encode cancelled" ,
494494 } ;
495- if ( signal ) {
496- if ( signal . aborted ) {
497- ffmpeg . kill ( "SIGTERM" ) ;
498- } else {
499- signal . addEventListener ( "abort" , onAbort , { once : true } ) ;
500- }
501- }
502-
503- const encodeTimeout = config ?. ffmpegEncodeTimeout ?? DEFAULT_CONFIG . ffmpegEncodeTimeout ;
504- let timedOut = false ;
505- const timer = setTimeout ( ( ) => {
506- timedOut = true ;
507- ffmpeg . kill ( "SIGTERM" ) ;
508- } , encodeTimeout ) ;
509-
510- ffmpeg . stderr . on ( "data" , ( data ) => {
511- stderr += data . toString ( ) ;
512- } ) ;
513-
514- ffmpeg . on ( "close" , ( code ) => {
515- clearTimeout ( timer ) ;
516- if ( signal ) signal . removeEventListener ( "abort" , onAbort ) ;
517- const durationMs = Date . now ( ) - startTime ;
518- if ( signal ?. aborted && ! timedOut ) {
519- resolve ( {
520- success : false ,
521- outputPath,
522- durationMs,
523- framesEncoded : 0 ,
524- fileSize : 0 ,
525- error : "FFmpeg encode cancelled" ,
526- } ) ;
527- return ;
528- }
529-
530- if ( code !== 0 || timedOut ) {
531- resolve ( {
532- success : false ,
533- outputPath,
534- durationMs,
535- framesEncoded : 0 ,
536- fileSize : 0 ,
537- error : appendEncodeTimeoutMessage (
538- formatFfmpegError ( code , stderr ) ,
539- timedOut ,
540- encodeTimeout ,
541- ) ,
542- } ) ;
543- return ;
544- }
545-
546- const fileSize = existsSync ( outputPath ) ? statSync ( outputPath ) . size : 0 ;
547- resolve ( { success : true , outputPath, durationMs, framesEncoded : frameCount , fileSize } ) ;
548- } ) ;
549-
550- ffmpeg . on ( "error" , ( err ) => {
551- clearTimeout ( timer ) ;
552- if ( signal ) signal . removeEventListener ( "abort" , onAbort ) ;
553- resolve ( {
554- success : false ,
555- outputPath,
556- durationMs : Date . now ( ) - startTime ,
557- framesEncoded : 0 ,
558- fileSize : 0 ,
559- error : appendEncodeTimeoutMessage ( `[FFmpeg] ${ err . message } ` , timedOut , encodeTimeout ) ,
560- } ) ;
561- } ) ;
562- } ) ;
495+ }
496+ if ( ! result . success ) {
497+ return {
498+ success : false ,
499+ outputPath,
500+ durationMs : result . durationMs ,
501+ framesEncoded : 0 ,
502+ fileSize : 0 ,
503+ error : appendEncodeTimeoutMessage (
504+ formatFfmpegError ( result . exitCode , result . stderr ) ,
505+ result . terminationReason === "deadline" ,
506+ encodeTimeout ,
507+ ) ,
508+ } ;
509+ }
510+ const fileSize = existsSync ( outputPath ) ? statSync ( outputPath ) . size : 0 ;
511+ return {
512+ success : true ,
513+ outputPath,
514+ durationMs : Date . now ( ) - startTime ,
515+ framesEncoded : frameCount ,
516+ fileSize,
517+ } ;
563518}
564519
565520export async function encodeFramesChunkedConcat (
@@ -624,45 +579,18 @@ export async function encodeFramesChunkedConcat(
624579 let gpuEncoder : GpuEncoder = null ;
625580 if ( options . useGpu ) gpuEncoder = await getCachedGpuEncoder ( ) ;
626581 const args = buildEncoderArgs ( options , inputArgs , chunkPath , gpuEncoder ) ;
627- const chunkResult = await new Promise < { success : boolean ; error ?: string } > ( ( resolve ) => {
628- const ffmpeg = spawn ( getFfmpegBinary ( ) , args ) ;
629- trackChildProcess ( ffmpeg ) ;
630- let stderr = "" ;
631- const encodeTimeout = config ?. ffmpegEncodeTimeout ?? DEFAULT_CONFIG . ffmpegEncodeTimeout ;
632- let timedOut = false ;
633- const timer = setTimeout ( ( ) => {
634- timedOut = true ;
635- ffmpeg . kill ( "SIGTERM" ) ;
636- } , encodeTimeout ) ;
637- ffmpeg . stderr . on ( "data" , ( d ) => {
638- stderr += d . toString ( ) ;
639- } ) ;
640- ffmpeg . on ( "close" , ( code ) => {
641- clearTimeout ( timer ) ;
642- if ( code === 0 && ! timedOut ) resolve ( { success : true } ) ;
643- else {
644- resolve ( {
645- success : false ,
646- error : appendEncodeTimeoutMessage (
647- `Chunk ${ i } encode failed: ${ stderr . slice ( - 400 ) } ` ,
648- timedOut ,
649- encodeTimeout ,
650- ) ,
651- } ) ;
652- }
653- } ) ;
654- ffmpeg . on ( "error" , ( err ) => {
655- clearTimeout ( timer ) ;
656- resolve ( {
657- success : false ,
658- error : appendEncodeTimeoutMessage (
659- `Chunk ${ i } encode error: ${ err . message } ` ,
660- timedOut ,
582+ const encodeTimeout = config ?. ffmpegEncodeTimeout ?? DEFAULT_CONFIG . ffmpegEncodeTimeout ;
583+ const processResult = await runFfmpeg ( args , { signal, timeout : encodeTimeout } ) ;
584+ const chunkResult = {
585+ success : processResult . success ,
586+ error : processResult . success
587+ ? undefined
588+ : appendEncodeTimeoutMessage (
589+ `Chunk ${ i } encode failed: ${ processResult . stderr . slice ( - 400 ) } ` ,
590+ processResult . terminationReason === "deadline" ,
661591 encodeTimeout ,
662592 ) ,
663- } ) ;
664- } ) ;
665- } ) ;
593+ } ;
666594 if ( ! chunkResult . success ) {
667595 return {
668596 success : false ,
@@ -692,45 +620,18 @@ export async function encodeFramesChunkedConcat(
692620 "-y" ,
693621 outputPath ,
694622 ] ;
695- const concatResult = await new Promise < { success : boolean ; error ?: string } > ( ( resolve ) => {
696- const ffmpeg = spawn ( getFfmpegBinary ( ) , concatArgs ) ;
697- trackChildProcess ( ffmpeg ) ;
698- let stderr = "" ;
699- const encodeTimeout = config ?. ffmpegEncodeTimeout ?? DEFAULT_CONFIG . ffmpegEncodeTimeout ;
700- let timedOut = false ;
701- const timer = setTimeout ( ( ) => {
702- timedOut = true ;
703- ffmpeg . kill ( "SIGTERM" ) ;
704- } , encodeTimeout ) ;
705- ffmpeg . stderr . on ( "data" , ( d ) => {
706- stderr += d . toString ( ) ;
707- } ) ;
708- ffmpeg . on ( "close" , ( code ) => {
709- clearTimeout ( timer ) ;
710- if ( code === 0 && ! timedOut ) resolve ( { success : true } ) ;
711- else {
712- resolve ( {
713- success : false ,
714- error : appendEncodeTimeoutMessage (
715- `Chunk concat failed: ${ stderr . slice ( - 400 ) } ` ,
716- timedOut ,
717- encodeTimeout ,
718- ) ,
719- } ) ;
720- }
721- } ) ;
722- ffmpeg . on ( "error" , ( err ) => {
723- clearTimeout ( timer ) ;
724- resolve ( {
725- success : false ,
726- error : appendEncodeTimeoutMessage (
727- `Chunk concat error: ${ err . message } ` ,
728- timedOut ,
623+ const encodeTimeout = config ?. ffmpegEncodeTimeout ?? DEFAULT_CONFIG . ffmpegEncodeTimeout ;
624+ const concatProcessResult = await runFfmpeg ( concatArgs , { signal, timeout : encodeTimeout } ) ;
625+ const concatResult = {
626+ success : concatProcessResult . success ,
627+ error : concatProcessResult . success
628+ ? undefined
629+ : appendEncodeTimeoutMessage (
630+ `Chunk concat failed: ${ concatProcessResult . stderr . slice ( - 400 ) } ` ,
631+ concatProcessResult . terminationReason === "deadline" ,
729632 encodeTimeout ,
730633 ) ,
731- } ) ;
732- } ) ;
733- } ) ;
634+ } ;
734635
735636 if ( ! concatResult . success ) {
736637 return {
0 commit comments