@@ -34,6 +34,8 @@ use crate::{
3434const OUTPUT_LOG_LIMIT_BYTES : usize = 8 * 1024 ;
3535const HLS_X264_PRESET : & str = "superfast" ;
3636const HLS_AUDIO_BITRATE : & str = "96k" ;
37+ const OPENER_MAX_DIMENSION : i32 = 640 ;
38+ const OPENER_VIDEO_CRF : & str = "27" ;
3739const HLS_TARGET_SEGMENT_SECONDS : u32 = 1 ;
3840const HLS_FFMPEG_INIT_FILENAME : & str = "init.mp4" ;
3941const HLS_DEFAULT_KEYFRAME_INTERVAL_FRAMES : u32 = 30 ;
@@ -631,44 +633,7 @@ async fn generate_and_upload_opener(
631633 source_path : & Path ,
632634 source_probe : & SourceProbe ,
633635) -> Result < UploadedArtifact > {
634- let compatible = source_probe. video_codec . as_deref ( ) == Some ( "h264" )
635- && ( !source_probe. has_audio || source_probe. audio_codec . as_deref ( ) == Some ( "aac" ) ) ;
636- let mut args = vec ! [
637- os( "-y" ) ,
638- os( "-i" ) ,
639- source_path. as_os_str( ) . to_owned( ) ,
640- os( "-map" ) ,
641- os( "0:v:0" ) ,
642- ] ;
643- if source_probe. has_audio {
644- args. extend ( [ os ( "-map" ) , os ( "0:a:0" ) ] ) ;
645- }
646- if compatible {
647- args. extend ( [ os ( "-c" ) , os ( "copy" ) ] ) ;
648- } else {
649- args. extend ( [
650- os ( "-vf" ) ,
651- os ( "scale='min(640,iw)':-2" ) ,
652- os ( "-c:v" ) ,
653- os ( "libx264" ) ,
654- os ( "-preset" ) ,
655- os ( HLS_X264_PRESET ) ,
656- os ( "-crf" ) ,
657- os ( "27" ) ,
658- os ( "-pix_fmt" ) ,
659- os ( "yuv420p" ) ,
660- ] ) ;
661- if source_probe. has_audio {
662- args. extend ( [ os ( "-c:a" ) , os ( "aac" ) , os ( "-b:a" ) , os ( HLS_AUDIO_BITRATE ) ] ) ;
663- }
664- }
665- args. extend ( [
666- os ( "-movflags" ) ,
667- os ( "+frag_keyframe+empty_moov+default_base_moof" ) ,
668- os ( "-f" ) ,
669- os ( "mp4" ) ,
670- os ( "pipe:1" ) ,
671- ] ) ;
636+ let args = opener_ffmpeg_args ( source_path, source_probe) ;
672637 let object_key = opener_object_key ( & request. asset_id ) ;
673638 let uploaded_object_key = uploaded_artifact_object_key ( request, & object_key) ;
674639 let source_size = request
@@ -706,10 +671,65 @@ async fn generate_and_upload_opener(
706671 content_type : "video/mp4" ,
707672 byte_size,
708673 duration_ms : Some ( source_probe. duration_ms ) ,
709- resolution_tier : Some ( source_probe. resolution_tier . to_owned ( ) ) ,
674+ resolution_tier : Some (
675+ if source_probe. width . max ( source_probe. height ) <= OPENER_MAX_DIMENSION {
676+ source_probe. resolution_tier
677+ } else {
678+ "720p"
679+ }
680+ . to_owned ( ) ,
681+ ) ,
710682 } )
711683}
712684
685+ fn opener_ffmpeg_args ( source_path : & Path , source_probe : & SourceProbe ) -> Vec < OsString > {
686+ let copy_video = source_probe. video_codec . as_deref ( ) == Some ( "h264" )
687+ && source_probe. width . max ( source_probe. height ) <= OPENER_MAX_DIMENSION ;
688+ let mut args = vec ! [
689+ os( "-y" ) ,
690+ os( "-i" ) ,
691+ source_path. as_os_str( ) . to_owned( ) ,
692+ os( "-map" ) ,
693+ os( "0:v:0" ) ,
694+ ] ;
695+ if source_probe. has_audio {
696+ args. extend ( [ os ( "-map" ) , os ( "0:a:0" ) ] ) ;
697+ }
698+ if copy_video {
699+ args. extend ( [ os ( "-c:v" ) , os ( "copy" ) ] ) ;
700+ } else {
701+ args. extend ( [
702+ os ( "-vf" ) ,
703+ os ( & format ! (
704+ "scale=w='if(gte(iw,ih),min({OPENER_MAX_DIMENSION},iw),-2)':h='if(gte(iw,ih),-2,min({OPENER_MAX_DIMENSION},ih))'"
705+ ) ) ,
706+ os ( "-c:v" ) ,
707+ os ( "libx264" ) ,
708+ os ( "-preset" ) ,
709+ os ( HLS_X264_PRESET ) ,
710+ os ( "-crf" ) ,
711+ os ( OPENER_VIDEO_CRF ) ,
712+ os ( "-pix_fmt" ) ,
713+ os ( "yuv420p" ) ,
714+ ] ) ;
715+ }
716+ if source_probe. has_audio {
717+ if copy_video && source_probe. audio_codec . as_deref ( ) == Some ( "aac" ) {
718+ args. extend ( [ os ( "-c:a" ) , os ( "copy" ) ] ) ;
719+ } else {
720+ args. extend ( [ os ( "-c:a" ) , os ( "aac" ) , os ( "-b:a" ) , os ( HLS_AUDIO_BITRATE ) ] ) ;
721+ }
722+ }
723+ args. extend ( [
724+ os ( "-movflags" ) ,
725+ os ( "+frag_keyframe+empty_moov+default_base_moof" ) ,
726+ os ( "-f" ) ,
727+ os ( "mp4" ) ,
728+ os ( "pipe:1" ) ,
729+ ] ) ;
730+ args
731+ }
732+
713733fn multipart_output_part_size ( estimated_bytes : u64 ) -> usize {
714734 const MIN_PART_SIZE : u64 = 16 * 1024 * 1024 ;
715735 const TARGET_PART_COUNT : u64 = 9_500 ;
@@ -2946,6 +2966,58 @@ mod tests {
29462966 ) ;
29472967 }
29482968
2969+ #[ test]
2970+ fn opener_downscales_large_compatible_video_and_reduces_audio ( ) {
2971+ let source_probe = SourceProbe {
2972+ duration_ms : 75_000 ,
2973+ width : 1920 ,
2974+ height : 1080 ,
2975+ resolution_tier : "1080p" ,
2976+ has_audio : true ,
2977+ video_codec : Some ( "h264" . to_owned ( ) ) ,
2978+ audio_codec : Some ( "aac" . to_owned ( ) ) ,
2979+ frame_rate : Some ( 30.0 ) ,
2980+ } ;
2981+
2982+ let args = opener_ffmpeg_args ( Path :: new ( "source.mp4" ) , & source_probe)
2983+ . into_iter ( )
2984+ . map ( |value| value. to_string_lossy ( ) . into_owned ( ) )
2985+ . collect :: < Vec < _ > > ( ) ;
2986+
2987+ assert ! ( args. windows( 2 ) . any( |pair| pair == [ "-c:v" , "libx264" ] ) ) ;
2988+ assert ! ( args. windows( 2 ) . any( |pair| pair == [ "-c:a" , "aac" ] ) ) ;
2989+ assert ! ( args. windows( 2 ) . any( |pair| pair == [ "-b:a" , "96k" ] ) ) ;
2990+ assert ! ( args. iter( ) . any( |value| value. contains( "min(640,iw)" ) ) ) ;
2991+ assert ! ( args. iter( ) . any( |value| value. contains( "min(640,ih)" ) ) ) ;
2992+ assert ! (
2993+ args. iter( )
2994+ . any( |value| value == "+frag_keyframe+empty_moov+default_base_moof" )
2995+ ) ;
2996+ }
2997+
2998+ #[ test]
2999+ fn opener_keeps_zero_copy_for_small_compatible_video ( ) {
3000+ let source_probe = SourceProbe {
3001+ duration_ms : 12_000 ,
3002+ width : 640 ,
3003+ height : 360 ,
3004+ resolution_tier : "720p" ,
3005+ has_audio : true ,
3006+ video_codec : Some ( "h264" . to_owned ( ) ) ,
3007+ audio_codec : Some ( "aac" . to_owned ( ) ) ,
3008+ frame_rate : Some ( 30.0 ) ,
3009+ } ;
3010+
3011+ let args = opener_ffmpeg_args ( Path :: new ( "source.mp4" ) , & source_probe)
3012+ . into_iter ( )
3013+ . map ( |value| value. to_string_lossy ( ) . into_owned ( ) )
3014+ . collect :: < Vec < _ > > ( ) ;
3015+
3016+ assert ! ( args. windows( 2 ) . any( |pair| pair == [ "-c:v" , "copy" ] ) ) ;
3017+ assert ! ( args. windows( 2 ) . any( |pair| pair == [ "-c:a" , "copy" ] ) ) ;
3018+ assert ! ( !args. iter( ) . any( |value| value == "-vf" ) ) ;
3019+ }
3020+
29493021 #[ test]
29503022 fn frame_rate_parser_handles_ffprobe_ratios ( ) {
29513023 assert_eq ! ( parse_frame_rate( "30/1" ) , Some ( 30.0 ) ) ;
0 commit comments