From 96f4cf2f72a4d56cbb0b0f35a24fba90a01eea2f Mon Sep 17 00:00:00 2001 From: Brooke Vibber Date: Wed, 1 Oct 2025 12:44:09 -0700 Subject: [PATCH] retooling a bit * allow setting vcodec * remove the min/max bitrate caps * allow a non-square-output with --stretch * cleanup bitrate setup * don't force audio channels * allow setting audio sample rate * don't force preset --- pack-vid | 226 ++++++++++++++++++++++++++----------------------------- 1 file changed, 106 insertions(+), 120 deletions(-) diff --git a/pack-vid b/pack-vid index 2ad6b7d..7ca7fa8 100755 --- a/pack-vid +++ b/pack-vid @@ -21,10 +21,11 @@ $options = [ 'letterbox' => false, 'no-audio' => false, 'audio-bitrate' => 96000, - 'audio-channels' => 2, + 'audio-rate' => false, + 'audio-channels' => false, 'exposure' => '0', // stops 'peak' => '1000', // '10000' is max - 'preset' => 'medium', + 'preset' => false, 'fps' => '60', 'size' => $maxBytes, 'quality' => 1.0, @@ -41,6 +42,8 @@ $options = [ 'crop-top' => false, 'tonemap' => 'hable', 'color-temperature' => false, + 'vcodec' => 'libx264', + 'stretch' => false, ]; while ( count( $args ) > 0 && substr( $args[0], 0, 2 ) == '--' ) { @@ -153,8 +156,6 @@ function extractTracks( $streams, $type ) { } function convert( $src, $dest, $options ) { - $maxBits = 8 * sizify( $options['size'] ); - $probe = ffprobe( $src ); $videoTracks = extractTracks( $probe->streams, 'video' ); @@ -198,9 +199,14 @@ function convert( $src, $dest, $options ) { $keyframeInt = intval( ceil( $duration * 60 ) ); } - $bitrate = floor( $maxBits / $duration ); if ( $options['bitrate'] ) { $bitrate = sizify( $options['bitrate'] ); + } else if ( $options[ 'size' ] ) { + $maxBits = 8 * sizify( $options['size'] ); + $bitrate = floor( $maxBits / $duration ); + } else { + // Calculate a target bitrate from the size later + $bitrate = null; } @@ -208,127 +214,93 @@ function convert( $src, $dest, $options ) { $audio = [ '-an' ]; } else { $audioBitrate = $options[ 'audio-bitrate' ]; - $audioChannels = $options[ 'audio-channels' ]; $audio = [ - '-ac', $audioChannels, '-b:a', $audioBitrate, ]; + if ( $options['audio-channels'] ) { + $audio[] = '-ac'; + $audio[] = $options[ 'audio-channels' ]; + } + if ( $options[ 'audio-rate' ] ) { + $audio[] = '-ar'; + $audio[] = $options[ 'audio-rate' ]; + } $bitrate -= $audioBitrate; } - $bitrate = max( $bitrate, 16000 ); - $mbits = 1000 * 1000; - $base = intval( $mbits * floatval( $options['quality'] ) ); - if ( $bitrate < 0.125 * $base || $height < 144 ) { - $frameWidth = 256; - $frameHeight = 144; - $bitrate = min( $bitrate, $base * 0.25 ); - } elseif ( $bitrate < 0.25 * $base || $height < 180 ) { - $frameWidth = 320; - $frameHeight = 180; - $bitrate = min( $bitrate, $base * 0.5 ); - } elseif ( $bitrate < 0.5 * $base || $height < 288 ) { - $frameWidth = 512; - $frameHeight = 288; - $bitrate = min( $bitrate, $base * 0.5 ); - } elseif ( $bitrate < 1 * $base || $height < 480 ) { - $frameWidth = 640; - $frameHeight = 360; - $bitrate = min( $bitrate, $base ); - } elseif ( $bitrate < 2 * $base || $height < 540) { - $frameWidth = 854; - $frameHeight = 480; - $bitrate = min( $bitrate, $base * 2 ); - } elseif ( $bitrate < 2.5 * $base || $height < 720) { - $frameWidth = 960; - $frameHeight = 540; - $bitrate = min( $bitrate, $base * 2.5 ); - } elseif ( $bitrate < 4 * $base || $height < 1080) { - $frameWidth = 1280; - $frameHeight = 720; - $bitrate = min( $bitrate, $base * 4 ); - } elseif ( $bitrate < 8 * $base || $height < 1440) { - $frameWidth = 1920; - $frameHeight = 1080; - $bitrate = min( $bitrate, $base * 8 ); - } elseif ( $bitrate < 16 * $base || $height < 2160) { - $frameWidth = 2560; - $frameHeight = 1440; - $bitrate = min( $bitrate, $base * 16 ); + if ( $options[ 'width' ] && $options[ 'height' ] ) { + // Use exact given dimensions. + $frameWidth = intval( $options[ 'width' ] ); + $frameHeight = intval( $options[ 'height' ] ); } else { - $frameWidth = 3840; - $frameHeight = 2160; - $bitrate = min( $bitrate, $base * 32 ); + // Select target resolution from the target bitrate... + if ( !$bitrate ) { + // If we didn't get given one, default to 5Mbits 1080HD + $bitrate = 5000000; + } + $mbits = 1000 * 1000; + $base = intval( $mbits * floatval( $options['quality'] ) ); + if ( $bitrate < 0.125 * $base || $height < 144 ) { + $frameWidth = 256; + $frameHeight = 144; + } elseif ( $bitrate < 0.25 * $base || $height < 180 ) { + $frameWidth = 320; + $frameHeight = 180; + } elseif ( $bitrate < 0.5 * $base || $height < 288 ) { + $frameWidth = 512; + $frameHeight = 288; + } elseif ( $bitrate < 1 * $base || $height < 480 ) { + $frameWidth = 640; + $frameHeight = 360; + } elseif ( $bitrate < 2 * $base || $height < 540) { + $frameWidth = 854; + $frameHeight = 480; + } elseif ( $bitrate < 2.5 * $base || $height < 720) { + $frameWidth = 960; + $frameHeight = 540; + } elseif ( $bitrate < 4 * $base || $height < 1080) { + $frameWidth = 1280; + $frameHeight = 720; + } elseif ( $bitrate < 8 * $base || $height < 1440) { + $frameWidth = 1920; + $frameHeight = 1080; + } elseif ( $bitrate < 16 * $base || $height < 2160) { + $frameWidth = 2560; + $frameHeight = 1440; + } else { + $frameWidth = 3840; + $frameHeight = 2160; + } } $aspect = $width / $height; $pixels = $width * $height; - // canonical min rate is 0.125 megabit at 144p - $bitrate = max( $bitrate, 0.125 * $base ); - - /* - $minWidth = 640; - $minHeight = 360; - - $baseWidth = 854; - $baseHeight = 480; - $pixelsPerBit = ( $baseWidth * $baseHeight ) / $base; - - $maxWidth = 1920; - $maxHeight = 1080; - $maxrate = $base * ( $maxWidth * $maxHeight ) / ( $baseWidth * $baseHeight ); - - $pixels = $bitrate * $pixelsPerBit; - $frameHeight = evenize( sqrt( $pixels / $aspect ) ); - $frameWidth = evenize( $frameHeight * $aspect ); - - if ( $aspect > 16 / 9 ) { - if ( $frameWidth < $minWidth ) { - $frameWidth = $minWidth; - $frameHeight = evenize( $frameWidth / $aspect ); - } elseif ( $frameWidth > $maxWidth ) { - $frameWidth = $maxWidth; - $frameHeight = evenize( $frameWidth / $aspect ); - $bitrate = min( $bitrate, $maxrate ); - } + if ( $options[ 'stretch' ] ) { + // Use this option when making non-square output + $scaleWidth = $frameWidth; + $scaleHeight = $frameHeight; } else { - if ( $frameHeight < $minHeight ) { - $frameHeight = $minHeight; - $frameWidth = evenize( $frameHeight * $aspect ); - } elseif ( $frameWidth > $maxWidth ) { - $frameHeight = $maxHeight; - $frameWidth = evenize( $frameHeight * $aspect ); - $bitrate = min( $bitrate, $maxrate ); - } - } - */ - - if ( $options['width'] ) { - $frameWidth = intval( $options['width'] ); - } - if ( $options['height'] ) { - $frameHeight = intval( $options['height'] ); - } - - $wide = $aspect > ( $frameWidth / $frameHeight ); - $crop = boolval( $options['crop'] ); - $letterbox = boolval( $options['letterbox'] ); - if ( $crop ) { - if ( $wide ) { - $scaleHeight = $frameHeight; - $scaleWidth = evenize( $frameHeight * $aspect ); + // Assumes square pixels + $wide = $aspect > ( $frameWidth / $frameHeight ); + $crop = boolval( $options['crop'] ); + $letterbox = boolval( $options['letterbox'] ); + if ( $crop ) { + if ( $wide ) { + $scaleHeight = $frameHeight; + $scaleWidth = evenize( $frameHeight * $aspect ); + } else { + $scaleWidth = $frameWidth; + $scaleHeight = evenize( $frameWidth / $aspect ); + } } else { - $scaleWidth = $frameWidth; - $scaleHeight = evenize( $frameWidth / $aspect ); - } - } else { - if ( $wide ) { - $scaleWidth = $frameWidth; - $scaleHeight = evenize( $frameWidth / $aspect ); - } else { - $scaleHeight = $frameHeight; - $scaleWidth = evenize( $frameHeight * $aspect ); + if ( $wide ) { + $scaleWidth = $frameWidth; + $scaleHeight = evenize( $frameWidth / $aspect ); + } else { + $scaleHeight = $frameHeight; + $scaleWidth = evenize( $frameHeight * $aspect ); + } } } @@ -376,7 +348,22 @@ function convert( $src, $dest, $options ) { $fps = $options['fps']; - $preset = $options['preset']; + if ( $options['preset'] ) { + $preset = [ '-preset', $options[ 'preset' ] ]; + } else { + $preset = []; + } + + $extension = pathinfo( $dest )[ 'extension' ]; + if ( $extension === 'mp4' || $extension === 'mov' ) { + $format = [ '-movflags', '+faststart' ]; + } else if ( $extension === 'webm' || $extension === 'mkv' ) { + $format = [ '-cues_to_front', 1 ]; + } else { + $format = []; + } + + $vcodec = $options['vcodec']; $tempPrefix = 'pack-vid-passlog' . rand(0,1 << 31); $passlog = tempnam( '.', $tempPrefix ); @@ -386,13 +373,13 @@ function convert( $src, $dest, $options ) { '-f', 'mp4', '-fpsmax', $fps, '-vf', $vf, - '-c:v', 'libx264', + '-c:v', $vcodec, '-b:v', $bitrate, - '-preset', $preset, + ], $preset, [ '-pass', '1', '-passlogfile', $passlog, '-g', $keyframeInt, - ], $audio, [ + '-an', '-y', '/dev/null' ] ) ); @@ -401,14 +388,13 @@ function convert( $src, $dest, $options ) { '-i', $src, '-vf', $vf, '-fpsmax', $fps, - '-c:v', 'libx264', + '-c:v', $vcodec, '-b:v', $bitrate, - '-preset', $preset, + ], $preset, [ '-pass', '2', '-passlogfile', $passlog, '-g', $keyframeInt, - ], $audio, [ - '-movflags', '+faststart', + ], $audio, $format, [ '-y', $dest ] ) );