From 2a55fc989f07e4ddd26f0f1d34c43e6febaa4cb7 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sat, 14 Jan 2023 17:20:35 -0800 Subject: [PATCH] initial commit --- pack-vid | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100755 pack-vid diff --git a/pack-vid b/pack-vid new file mode 100755 index 0000000..cb2907c --- /dev/null +++ b/pack-vid @@ -0,0 +1,163 @@ +#!/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 +*/