@@ -72,6 +72,10 @@ class TranscodeSession extends EventEmitter {
7272 hwEncoder : options . hwEncoder || 'software' ,
7373 maxResolution : options . maxResolution || '1080p' ,
7474 quality : options . quality || 'medium' ,
75+ // Upscaling options
76+ upscaleEnabled : options . upscaleEnabled || false ,
77+ upscaleMethod : options . upscaleMethod || 'hardware' , // 'hardware' or 'software'
78+ upscaleTarget : options . upscaleTarget || '1080p' ,
7579 ...options
7680 } ;
7781 }
@@ -355,7 +359,9 @@ class TranscodeSession extends EventEmitter {
355359 }
356360
357361 /**
358- * Get target height based on maxResolution setting
362+ * Get target height based on maxResolution or upscaleTarget setting
363+ * When upscaling is enabled, uses the upscaleTarget resolution.
364+ * Otherwise, uses maxResolution to cap the output.
359365 */
360366 getTargetHeight ( ) {
361367 const resolutionMap = {
@@ -364,15 +370,61 @@ class TranscodeSession extends EventEmitter {
364370 '720p' : 720 ,
365371 '480p' : 480
366372 } ;
373+
374+ // When upscaling is enabled, use the upscale target resolution
375+ if ( this . options . upscaleEnabled ) {
376+ const target = resolutionMap [ this . options . upscaleTarget ] || 1080 ;
377+ console . log ( `[TranscodeSession ${ this . id } ] Upscale target height: ${ target } p` ) ;
378+ return target ;
379+ }
380+
381+ // Otherwise, use max resolution as the cap
367382 return resolutionMap [ this . options . maxResolution ] || 1080 ;
368383 }
369384
385+ /**
386+ * Build scale filter string based on encoder and upscaling settings
387+ * @param {string } encoder - The encoder being used
388+ * @param {number } height - Target height
389+ */
390+ buildScaleFilter ( encoder , height ) {
391+ const useUpscale = this . options . upscaleEnabled ;
392+ const upscaleMethod = this . options . upscaleMethod || 'hardware' ;
393+
394+ // Log upscaling status
395+ if ( useUpscale ) {
396+ console . log ( `[TranscodeSession ${ this . id } ] Upscaling: ${ upscaleMethod } method to ${ height } p` ) ;
397+ }
398+
399+ // Hardware scaling filters (for both upscale and downscale)
400+ if ( upscaleMethod === 'hardware' || ! useUpscale ) {
401+ switch ( encoder ) {
402+ case 'nvenc' :
403+ // NVIDIA CUDA scaling with Lanczos for upscaling
404+ return `scale_cuda=-2:${ height } :interp_algo=lanczos` ;
405+ case 'vaapi' :
406+ return `scale_vaapi=w=-2:h=${ height } :format=nv12` ;
407+ case 'qsv' :
408+ return `scale_qsv=w=-2:h=${ height } ` ;
409+ case 'amf' :
410+ // AMF uses CPU decode, so use software scale
411+ return useUpscale ? `scale=-2:${ height } :flags=lanczos` : `scale=-2:${ height } ` ;
412+ case 'software' :
413+ default :
414+ return useUpscale ? `scale=-2:${ height } :flags=lanczos` : `scale=-2:${ height } ` ;
415+ }
416+ }
417+
418+ // Software Lanczos scaling (high quality, slower)
419+ return `scale=-2:${ height } :flags=lanczos` ;
420+ }
421+
370422 /**
371423 * NVIDIA NVENC encoder arguments
372424 */
373425 addNvencEncoderArgs ( args , height , qp ) {
374426 // Video filter for scaling on GPU
375- args . push ( '-vf' , `scale_cuda=-2: ${ height } :interp_algo=lanczos` ) ;
427+ args . push ( '-vf' , this . buildScaleFilter ( 'nvenc' , height ) ) ;
376428
377429 // NVENC encoder with quality settings
378430 // Using portable options that work across FFmpeg builds
@@ -381,7 +433,8 @@ class TranscodeSession extends EventEmitter {
381433 '-preset' , 'p4' , // Balanced preset (p1=fastest, p7=best)
382434 '-rc' , 'constqp' , // Constant QP mode
383435 '-qp' , String ( qp ) ,
384- '-bf' , '3' // B-frames for better compression
436+ '-bf' , '3' , // B-frames for better compression
437+ '-pix_fmt' , 'yuv420p' // Force 8-bit output for compatibility
385438 ) ;
386439 }
387440
@@ -390,15 +443,16 @@ class TranscodeSession extends EventEmitter {
390443 */
391444 addAmfEncoderArgs ( args , height , qp ) {
392445 // CPU decoding + software scale + AMF encode
393- args . push ( '-vf' , `scale=-2: ${ height } ` ) ;
446+ args . push ( '-vf' , this . buildScaleFilter ( 'amf' , height ) ) ;
394447
395448 args . push (
396449 '-c:v' , 'h264_amf' ,
397450 '-quality' , 'quality' , // Quality preset
398451 '-rc' , 'cqp' , // Constant QP
399452 '-qp_i' , String ( qp ) ,
400453 '-qp_p' , String ( qp + 2 ) ,
401- '-qp_b' , String ( qp + 4 )
454+ '-qp_b' , String ( qp + 4 ) ,
455+ '-pix_fmt' , 'yuv420p' // Force 8-bit output for compatibility
402456 ) ;
403457 }
404458
@@ -410,15 +464,16 @@ class TranscodeSession extends EventEmitter {
410464 // 1. scale_vaapi to resize on GPU
411465 // 2. Ensure output format is nv12 for maximum encoder compatibility
412466 // The format is handled automatically when using -hwaccel_output_format vaapi
413- args . push ( '-vf' , `scale_vaapi=w=-2:h= ${ height } :format=nv12` ) ;
467+ args . push ( '-vf' , this . buildScaleFilter ( 'vaapi' , height ) ) ;
414468
415469 // VAAPI encoder with quality setting
416470 // Note: -global_quality is the portable way to set quality for VAAPI
417471 args . push (
418472 '-c:v' , 'h264_vaapi' ,
419473 '-profile:v' , 'main' , // Use main profile for compatibility
420474 '-global_quality' , String ( qp ) ,
421- '-bf' , '3'
475+ '-bf' , '3' ,
476+ '-pix_fmt' , 'yuv420p' // Force 8-bit output for compatibility
422477 ) ;
423478 }
424479
@@ -427,23 +482,24 @@ class TranscodeSession extends EventEmitter {
427482 */
428483 addQsvEncoderArgs ( args , height , qp ) {
429484 // Scale on QSV
430- args . push ( '-vf' , `scale_qsv=w=-2:h= ${ height } ` ) ;
485+ args . push ( '-vf' , this . buildScaleFilter ( 'qsv' , height ) ) ;
431486
432487 args . push (
433488 '-c:v' , 'h264_qsv' ,
434489 '-preset' , 'medium' ,
435490 '-global_quality' , String ( qp ) ,
436491 '-look_ahead' , '1' ,
437- '-look_ahead_depth' , '40'
492+ '-look_ahead_depth' , '40' ,
493+ '-pix_fmt' , 'yuv420p' // Force 8-bit output for compatibility
438494 ) ;
439495 }
440496
441497 /**
442498 * Software encoder arguments (fallback)
443499 */
444500 addSoftwareEncoderArgs ( args , height , crf ) {
445- // Software scaling
446- args . push ( '-vf' , `scale=-2: ${ height } ` ) ;
501+ // Software scaling (use Lanczos for upscaling if enabled)
502+ args . push ( '-vf' , this . buildScaleFilter ( 'software' , height ) ) ;
447503
448504 args . push (
449505 '-c:v' , 'libx264' ,
0 commit comments