Skip to content

Instantly share code, notes, and snippets.

@divinity76
Last active May 17, 2019 15:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save divinity76/3bafc6516ecc54237d610ded03a8065d to your computer and use it in GitHub Desktop.
Save divinity76/3bafc6516ecc54237d610ded03a8065d to your computer and use it in GitHub Desktop.
video cutter
<?php
declare (strict_types = 1);
stream_set_blocking(STDIN, true);
if ($argc >= 2) {
$in_file = $argv;
unset($in_file[0]);
$in_file = implode("", $in_file);
} else {
echo "choose path to media file: ";
$in_file = fgets(STDIN);
}
$in_file = trim(strtr($in_file, array('"' => '', "'" => '')));
if (!is_file($in_file)) {
echo "error: \"{$in_file}\" is not a valid file!\n";
pause();
return 1;
}
$in_file = _cygwinify_filepath($in_file);
$in_file = realpath($in_file);
//var_dump($in_file);
echo "filepath parsed: \"{$in_file}\"\n";
$dir = dirname($in_file);
if (!chdir($dir)) {
die("error: failed to chdir \"{$dir}\"");
}
$in_file_name = basename($in_file);
$total_secs = get_seconds($in_file);
$key_frames = get_key_frames($in_file);
echo "media length is \"{$total_secs}\" seconds (aka \"" . number_format($total_secs / 60, 1) . "\" minutes)\n";
echo "number of keyframes: " . count($key_frames) . "\r\n";
echo "write how many seconds you want each clip to be, and press enter: ";
$clip_sec = fgets(STDIN);
$clip_sec = trim($clip_sec);
if (false === ($clip_sec = filter_var($clip_sec, FILTER_VALIDATE_INT))) {
die("ERROR: could not parse input as number!");
}
$secs = 0;
$i = 0;
for (;;) {
++$i;
$remaining = $total_secs - $secs;
if ($remaining <= 0) {
break;
}
$next = $secs + $clip_sec;
if($next<=0){
break;
}
$next=get_next_keyframe($key_frames, $next, $total_secs);
$cmd = implode(" ", array(
"ffmpeg",
"-y",
"-noaccurate_seek",
"-i " . escapeshellarg(_uncygwinify_filepath($in_file)),
"-c:v copy",
"-c:a copy",
"-ss " . escapeshellarg((string)($secs-0.1)), // -0.1 is to work around keyframe issues >.<
"-t " . escapeshellarg((string)($next-$secs)),
escapeshellarg("clip." . $i . "." . $in_file_name)
));
$secs=$next;
echo "executing: ";
var_dump($cmd);
passthru($cmd, $ret);
if ($ret !== 0) {
die("FFMPEG ERROR: FFMPEG DID NOT RETURN 0! returned {$ret}.......\n");
}
if($next===$total_secs){
break;
}
}
function get_next_keyframe(array &$keyframes, float $secs, float $total_secs)
{
if (0) {
if (empty($keyframes)) {
throw new \LogicException("keyframes empty!");
}
}
foreach ($keyframes as $key => $frame) {
if ($frame >= $secs) {
return $frame;
} else {
unset($keyframes[$key]);
}
}
//..
return $total_secs;
}
function get_seconds(string $file): float
{
// -noaccurate_seek
// ffprobe -print_format json -show_format
my_exec("ffprobe " . escapeshellarg(_uncygwinify_filepath($file)) . " -print_format json -show_format", "", $stdout, $stderr);
$parsed = json_decode($stdout, true);
return ((float)$parsed['format']['duration']);
}
function get_key_frames(string $file): array
{
// ffprobe -select_streams v -skip_frame nokey -show_frames -show_entries frame=pkt_pts_time -print_format json "file"
my_exec("ffprobe -select_streams v -skip_frame nokey -show_frames -show_entries frame=pkt_pts_time -print_format json " . escapeshellarg(_uncygwinify_filepath($file)), "", $stdout, $stderr);
$parsed = json_decode($stdout, true);
$ret = array();
foreach ($parsed['frames'] as $keyframe) {
$ret[] = (double)$keyframe['pkt_pts_time'];
}
sort($ret, SORT_NUMERIC);
return $ret;
}
function pause()
{
echo "press enter to continue..";
fgets(STDIN);
return;
}
function _uncygwinify_filepath(string $path): string
{
static $is_cygwin_cache = null;
if ($is_cygwin_cache === null) {
$is_cygwin_cache = (false !== stripos(PHP_OS, "cygwin"));
}
if ($is_cygwin_cache) {
return trim(shell_exec("cygpath -aw " . escapeshellarg($path)));
} else {
return $path;
}
}
function _cygwinify_filepath(string $path): string
{
static $is_cygwin_cache = null;
if ($is_cygwin_cache === null) {
$is_cygwin_cache = (false !== stripos(PHP_OS, "cygwin"));
}
if ($is_cygwin_cache) {
return trim(shell_exec("cygpath -a " . escapeshellarg($path)));
//return "/cygdrive/" . strtr($path, array(':' => '', '\\' => '/'));
} else {
return $path;
}
}
function my_exec(string $cmd, string $stdin = "", string &$stdout = null, string &$stderr = null): int
{
$stdouth = tmpfile();
$stderrh = tmpfile();
$descriptorspec = array(
0 => array("pipe", "rb"), // stdin
1 => array("file", stream_get_meta_data($stdouth)['uri'], 'ab'),
2 => array("file", stream_get_meta_data($stderrh)['uri'], 'ab')
);
$pipes = array();
$proc = proc_open($cmd, $descriptorspec, $pipes);
while (strlen($stdin) > 0) {
$written_now = fwrite($pipes[0], $stdin);
if ($written_now <= 0) {
//... can add more error checking here, but probably means that target program
// exited or crashed before consuming all of stdin.
// (could also have explicitly closed stdin before consuming all)
break;
}
$stdin = substr($stdin, $written_now);
}
fclose($pipes[0]);
unset($stdin, $pipes[0]);
$proc_ret = proc_close($proc); // this line will stall until the process has exited.
$stdout = stream_get_contents($stdouth);
$stderr = stream_get_contents($stderrh);
fclose($stdouth);
fclose($stderrh);
return $proc_ret;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment