#!/usr/bin/env php \n" ); } [ $src, $dest ] = $args; convert( $src, $dest ); exit(0); // function run( $cmd, $args ) { $commandLine = implode( ' ', array_merge( [ escapeshellcmd( $cmd ) ], array_map( 'escapeshellarg', $args ) ) ); echo "$commandLine\n"; $output = shell_exec( $commandLine ); if ( $output === false ) { throw new Error( "Failed to run $cmd" ); } return $output; } function ffprobe( $path ) { $output = run( 'ffprobe', [ '-hide_banner', '-show_format', '-show_streams', '-print_format', 'json', '--', $path ] ); $data = json_decode( $output ); if ( $data === null ) { throw new Error( "Failed to read JSON from ffprobe: $output" ); } return $data; } function evenize( $n ) { $n = ceil( $n ); if ( $n & 1 ) { $n++; } return $n; } function convert( $src, $dest ) { $maxBits = 4000 * 1000 * 8; // fit in 4Mb $maxBits = $maxBits * 7 / 8; // leave some headroom $probe = ffprobe( $src ); $videoTracks = array_filter( $probe->streams, function ( $stream ) { return $stream->codec_type === 'video'; } ); $track = $videoTracks[0]; $duration = floatval( $track->duration ); $width = $track->width; $height = $track->height; $hdr = $track->color_primaries === 'bt2020'; $keyframeInt = ceil( $duration * 60 ); $bitrate = floor( $maxBits / $duration ); $mbits = 1000 * 1000; if ( $bitrate < 2 * $mbits ) { $cropWidth = 854; $scaleHeight = 480; } else if ( $bitrate <= 4 * $mbits ) { $cropWidth = 1280; $scaleHeight = 720; } else { $cropWidth = 1920; $scaleHeight = 1080; } $scaleWidth = evenize( $width * $scaleHeight / $height ); $filters = [ "scale=w=$scaleWidth:h=$scaleHeight" ]; if ( $hdr ) { $filters[] = "zscale=t=linear:p=bt709"; $filters[] = "tonemap=hable"; $filters[] = "zscale=t=bt709:m=bt709:r=full"; } $filters[] = "format=yuv420p"; $filters[] = "crop=w=$cropWidth"; $vf = implode( ',', $filters ); run( 'ffmpeg', [ '-i', $src, '-f', 'null', '-vf', $vf, '-c:v', 'libx264', '-b:v', $bitrate, '-preset', 'veryslow', '-pass', '1', '-g', $keyframeInt, '-an', '-y', '/dev/null' ] ); run( 'ffmpeg', [ '-i', $src, '-vf', $vf, '-c:v', 'libx264', '-b:v', $bitrate, '-preset', 'veryslow', '-pass', '2', '-g', $keyframeInt, '-an', '-y', $dest ] ); } /* # <18s ffmpeg \ -i "yikes.mp4" \ -f null \ -vf "zscale=t=linear:p=bt709,\ tonemap=hable,\ zscale=w=1146:h=480:t=bt709:m=bt709:r=full,format=yuv420p,\ crop=w=854" \ -vcodec libx264 \ -b:v 1250k \ -preset veryslow \ -pass 1 \ -g 1080 \ -an \ -y /dev/null && \ ffmpeg \ -i "yikes.mp4" \ -vf "zscale=t=linear:p=bt709,\ tonemap=hable,\ zscale=w=1146:h=480:t=bt709:m=bt709:r=full,format=yuv420p,\ crop=w=854" \ -vcodec libx264 \ -b:v 1250k \ -pass 2 \ -preset veryslow \ -g 1080 \ -ab 96k \ -movflags +faststart \ -y yikes-small-compat.mp4 */