initial commit
This commit is contained in:
commit
2a55fc989f
1 changed files with 163 additions and 0 deletions
163
pack-vid
Executable file
163
pack-vid
Executable file
|
@ -0,0 +1,163 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Squishes given video input into 4000kb .mp4
|
||||||
|
// Crops to 16:9
|
||||||
|
// Picks bitrate to match
|
||||||
|
// Picks resolution based on bitrate target
|
||||||
|
// 2-pass encoding with libx264 veryslow
|
||||||
|
|
||||||
|
$args = $_SERVER['argv'];
|
||||||
|
$self = array_shift( $args );
|
||||||
|
|
||||||
|
if ( count ( $args ) < 2 ) {
|
||||||
|
die( "Usage: pack-vid.php <srcfile.mp4> <destfile.mp4>\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
|
||||||
|
*/
|
Loading…
Reference in a new issue