How to programmatically start/stop FFMPEG stream transcoding - php

I have an ip webcam which provides an MJPEG stream. I can successfully transcode and save that stream with ffmpeg under OSX. The following gives me pretty much what I want:
ffmpeg -f mjpeg -i "http://user:pass#10.0.1.200/nphMotionJpeg?Resolution=640x480&Quality=Standard" -b:v 1500k -vcodec libx264 /tmp/test.mp4
That will start an FFMPEG session and begin saving the live stream to my test.mp4 file. pressing q will quit ffmpeg and save the file.
I would like to programmatically start & stop the recording using a PHP or Bash shell script. I have tried the following:
<?php
$pid = pcntl_fork();
if($pid == -1){
die("could not fork");
}elseif($pid){
// we are the parent...
print $pid.' started recording. waiting 10 seconds...';
sleep(10); // Wait 10 seconds
print_r(shell_exec("kill ".$pid)); // Kill the child recording process
echo 'done';
exit();
}else{
// we are the child process. call ffmpeg.
exec('../lib/ffmpeg -f mjpeg -i "http://user:pass#10.0.1.200/nphMotionJpeg?Resolution=640x480&Quality=Standard" -b:v 1500k -vcodec libx264 /tmp/test.mp4');
}
But there are two problems:
The ffmpeg process does not end/die (probably because its forked again)
When I manually kill the ffmpeg process, the video file is not readable

So I was doing a combination of things wrong.
For starters, I needed to push the output form ffmpeg to a log file and also tell it to overwrite my temp file without prompting using the -y argument.
So instead of
ffmpeg -f mjpeg -i "http://user:pass#10.0.1.200/nphMotionJpeg?Resolution=640x480&Quality=Standard" -b:v 1500k -vcodec libx264 /tmp/test.mp4
I am now using
ffmpeg -y -f mjpeg -i "http://user:pass#10.0.1.200/nphMotionJpeg?Resolution=640x480&Quality=Standard" -b:v 1500k -vcodec libx264 /tmp/test.mp4 </dev/null >/dev/null 2>/tmp/ffmpeg.log &
The second problem was that I wasn't waiting long enough before sending the kill command to ffmpeg, and so a corrupt file was being created.
By adding the -t (for time limit) argument with 1 second, I determined that it takes an average of 15 seconds for ffmpeg to record 1 second of video. Increasing the time limit to 10 seconds made the average increase to 25 seconds, so it seems that on my server at least, theres 14 seconds of overhead. By increasing my sleep command in my php script to 30 seconds, I was able to get a useable video file.
So having PHP kill the ffmpeg process results in an unknown (or approximate at best) recording time length which is completely dependent on CPU power, network bandwidth, etc.
Thats a bit of a bummer because I had hoped to be able to increase the recording length depending on some external variables (I have insteon motion sensors feeding a database, i would like to record until the motion stops).
In any event, here is a working PHP script in case it helps someone in the future:
<?php
print date('H:i:s')."\nStarted recording. waiting 60 seconds...\n";
exec('../lib/ffmpeg -y -f mjpeg -i "http://user:pass#10.0.1.200/nphMotionJpeg?Resolution=640x480&Quality=Standard" -b:v 1500k -vcodec libx264 /tmp/test.mp4 </dev/null >/dev/null 2>/tmp/ffmpeg.log &');
sleep(60); // Wait long enough before killing, otherwise the video will be corrupt!
shell_exec('pkill ffmpeg'); // find and kill
echo "done\n\n";
exit();
?>

Send a SIGQUIT signal to the background process to terminate the ffmpeg command normally.
Just use
kill -s QUIT $PID
and the process will finish with a non-corrupted MP4 video file.

Send the "q" key:
process.StandardInput.WriteLine("q");

Related

Run script after FFMPEG background task completes (via PHP)

I have a PHP script which triggers an FFMPEG file conversion via shell_exec().
shell_exec('ffmpeg -i file.webm -c:v libx264 -c:a aac -strict -2 file.mp4 >/dev/null 2>/dev/null &');
This happens in the background (hence &), i.e. the script completes before conversion has finished.
Is there a way to call and execute a PHP script (to update a DB flag) once the conversion is complete?
I've done plenty of Googling but my knowledge of server commands just isn't up to understanding what I'm reading (e.g. this answer). The best I could manage was to redirect stdout to a file via
shell_exec('ffmpeg -i file.webm -c:v libx264 -c:a aac -strict -2 file.mp4 > MYFILE.txt 2>/dev/null &');
...but obviously that just creates and writes to a file, it doesn't call and execute it via PHP.
I am not that great at server commands either, so I can't really help you there. But I do have this knack for figuring things out.
So I see a few ways you could do this, essentially you need PHP to do something when the command line call finishes. The obvious answer is to remove the & off the end of the command and make it blocking so PHP sticks around tell the job is done. But in doing so you can't return to the end user until that is done.
Option 1
So one way around this is to make a sort of Bootstrap PHP script that you call non-blocking. In this script do your now blocking conversion command and after that returns have PHP do something else.
//bootstrap.php
shell_exec('ffmpeg -i file.webm -c:v libx264 -c:a aac -strict -2 file.mp4 > MYFILE.txt 2>/dev/null'); //blocking
//Update the DB
Then from your Controller or what have you call the bootstrap non-blocking
shell_exec('php {pathto}/bootstrap.php 2>/dev/null &');
This way the call to the bootstrap returns immediately but the conversion call is blocking, which gives you the chance to update the DB afterwords.
Option 2
Because the conversion is outputting a file, you could start a separate background job, that monitors the modified time of the output file. Then if the modified time is like a minute in the past you could assume it's done converting and update the DB. The modified time should continue to update as long as data is being added to the file.
Hope that helps.
PS. I have some code you may fine useful on GitHub
Runs Background processes in both windows & linux
https://github.com/ArtisticPhoenix/MISC/blob/master/BgProcess.php
PHP process locking ( Mutex simulation using files)
https://github.com/ArtisticPhoenix/MISC/blob/master/ProcLock.php
Command line argument mapping for PHP programs:
https://github.com/ArtisticPhoenix/Cli
Your welcome to use them if it helps you out.

Running PHP remotely via SSH

I have a video encoding server set up on Laravel Forge with nginx. I'm trying to run a testing script to encode a video remotely via SSH, using the LaravelCollective SSH package.
This is my testing script (index.php)
<?php
exec("ffmpeg -I input.mpg -c:v libx264 -preset faster -crf 22 -c:a aac -strict experimental -movflags +faststart -vf scale=360:-1 output.mp4 1> output.txt 2>&1");
When I SSH into the server and run the script from the command line it works: the video is encoded; the script is working.
$ php /home/forge/mydomainname.com/public/test/index.php
However, when I run the same command locally in my Laravel app - using the SSH package - it doesn't encode and I receive no output; the browser just returns a white page.
SSH::run('php /home/forge/mydomainname.com/public/test/index.php', function($line) {
echo $line.PHP_EOL;
});
However however, if I open index.php, comment out the FFmpeg command and add some code to check if exec is enabled, it will execute and send output, so I know that the SSH package is actually working and executing the script remotely.
<?php
// exec("ffmpeg -I input.mpg -c:v libx264 -preset faster -crf 22 -c:a aac -strict experimental -movflags +faststart -vf scale=360:-1 output.mp4 1> output.txt 2>&1");
if(function_exists('exec')) {
echo "exec is enabled";
} else {
echo "exec is disabled";
}
It will return "exec is enabled" to my browser.
To sum up:
The script will encode video when running it via the command line on the server.
The script will not encode video when running it remotely.
The script will execute when running it remotely.
WTH?
I figured it out.
From within my Laravel app I decided to try echo exec('whoami'); and see if that returned anything to the browser. It did, so I knew exec() was working and I could trigger it via the SSH package.
Then I realized that my ffmpeg encoding command was suppressing output with 2>&1. I removed that and finally saw what was going on: I was receiving a "file not found" error, which was weird because input.mpg is in the same directory as index.php.
This has worked on three other servers, but not on this one created with Forge.
So I added the full path to the input file and voilà! It works!
exec("ffmpeg -i /home/forge/mydomainname.com/public/test/input.mpg -c:v libx264 -preset faster -crf 22 -c:a aac -strict experimental -movflags +faststart -vf scale=360:-1 /home/forge/mydomainname.com/public/test/output.mp4 1> /home/forge/mydomainname.com/public/test/output.txt ");

ffmpeg-php and shell_exec

I'm using ffmpeg-php for uploading videos and converting videos from
"ogv", "mp4", "avi", "flv", "ogg",
"mpg", "wmv", "asf" and "mov"
formats to
"flv" , "ogg" and "flv" formats.
I'm doing this by:
$command1='ffmpeg -i '.$Source." -ar 44100 -ab 128k -s wvga -aspect 4:3 -f FLV -y ".$dest;
$command2="ffmpeg -i ".$Source." -acodec libfaac -ab 128k -ac 2 -vcodec libx264 -vpre slow -threads 10 -s wvga -aspect 4:3 -f mp4 -y ".$dest;
$command3="ffmpeg -i ".$Source." -acodec libvorbis -ab 128k -ac 2 -vcodec libtheora -threads 10 -s wvga -aspect 4:3 -f ogg -y ".$dest;
#shell_exec($command1.";".$command2.";".$command3);
When applying those commands on .ogv videos it works fine but when applying on .MOV it does not work and no errors displays although when I paste the command into the terminal it works fine.
Hint: Script is running by cron job.
is there any suggestions for this problem?
Most likely the script is running out of time before the conversion takes place.
Increase the execution time of your scripts.
Although its not a recommend solution for production. You might want to run background processes to do the conversion and use the PHP script just to upload the video.
What happens when you run the problem part outside of a crontab?
are you saving the output of your crontab entry to files? i.e.
59 12 1 1 * { myPhpScript ; } > /tmp/myPhpScript.log 2>&1` ...
( The semicolon preceding the closing '}' is critical)
This captures any stdout or stderr messages to a logfile.
I hope this helps.
P.S. as you appear to be a newish user, if you get an answer that helps you please remember to mark it as accepted, and/or give it a + (or -) as a useful answer.

how to convert video from one format to another using php

hi i want to include the vedio download option in my webpage. I am using ffmpeg, but it seems to work very slow. Is there is any other way to do this or how to spead up the ffmpeg.
i am using this code to get the frames from the vedio.
to convert the vedio
$call="ffmpeg -i ".$_SESSION['video_to_convert']." -vcodec libvpx -r 30 -b ".$quality." -acodec libvorbis -ab 128000 -ar ".$audio." -ac 2 -s ".$size." ".$converted_vids.$name.".".$type." -y 2> log/".$name.".txt";
$convert = (popen("start /b ".$call, "r"));
pclose($convert);
to get the frame from the vedio
exec("ffmpeg -vframes 1 -ss ".$time_in_seconds." -i $converted_vids video_images.jpg -y 2>);
but this code does not generate any error its loading continously.
Cache or pre-generate the output format.
Use the ffmpeg-php library. Should boost up some processes rather then manually calling the ffmpeg command line tool using exec.
I'd first of all take PHP out of the equasion and time how long it takes to do what you're after via the command line.
Once you're happy that works the way you'd like it to, make sure you've tweaked your script's execution time (see http://php.net/manual/en/function.set-time-limit.php) to accomodate what's likely to take a while.
Consider an async approach if it's getting in the way of UX.
Ta

How to get PID from PHP function exec() in Windows?

I have always used:
$pid = exec("/usr/local/bin/php file.php $args > /dev/null & echo \$!");
But I am using an XP virtual machine to develop a web app and I have no idea how to get the pid in windows.
I tried this on a cmd:
C:\\wamp\\bin\\php\\php5.2.9-2\\php.exe "file.php args" > NUL & echo $!
And it gets the file executed, but the output is "$!"
How can I get the pid into the var $pid? (using php)
I'm using Pstools which allows you to create a process in the background and capture it's pid:
// use psexec to start in background, pipe stderr to stdout to capture pid
exec("psexec -d $command 2>&1", $output);
// capture pid on the 6th line
preg_match('/ID (\d+)/', $output[5], $matches);
$pid = $matches[1];
It's a little hacky, but it gets the job done
I landed here thanks to google and decided that this ten years old post needs more info based on How to invoke/start a Process in PHP and kill it using Process ID...
Imagine that you want to execute a command (this example uses ffmpeg to stream a file on a windows system to a rtmp server). You could command something like this:
ffmpeg -re -i D:\wicked_video.mkv -c:v libx264 -preset veryfast -b:v 200k -maxrate 400k -bufsize 6000k -pix_fmt yuv420p -g 50 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmp://<IP_OF_RMTP_SERVER>/superawesomestreamkey > d:\demo.txt 2> d:\demoerr.txt
The first part is explained here and the last part of that command outputs to files with that name for logging purposes:
> d:\demo.txt 2> d:\demoerr.txt
So lets assume that that command works. You tested it.
To run that command with php you can execute it with exec but it will take time (another subject, check set_time_limit), its a video handled by ffmpeg via php. Not the way to go but it is happening in this case.
You can run the command in background but what is the pid of that Process?
We want to kill it for some reason and psexec gives only the 'process ID" runned by a user. And there is only one user in this case.
We want multiple processes on the same user.
Here is a example to get the pid of a runned process in php:
// the command could be anything:
// $cmd = 'whoami';
// This is a f* one, The point is: exec is nasty.
// $cmd = 'shutdown -r -t 0'; //
// but this is the ffmpeg example that outputs seperate files for sake
$cmd = 'ffmpeg -re -i D:\wicked_video.mkv -c:v libx264 -preset veryfast -b:v 200k -maxrate 400k -bufsize 6000k -pix_fmt yuv420p -g 50 -c:a aac -b:a 160k -ac 2 -ar 44100 -f flv rtmp://10.237.1.8/show/streamkey1 > d:\demo.txt 2> d:\demoerr.txt';
// we assume the os is windows, pipe read and write
$descriptorspec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
];
// start task in background, when its a recource, you can get Parent process id
if ( $prog = is_resource( proc_open("start /b " . $cmd, $descriptorspec, $pipes ) ) )
{
// Get Parent process Id
$ppid = proc_get_status($prog);
// this is the 'child' pid
$pid = $ppid['pid'];
// use wmic to get the PID
$output = array_filter( explode(" ", shell_exec("wmic process get parentprocessid,processid | find \"$pid\"" ) ) );
array_pop($output);
// if pid exitst this will not be empty
$pid = end($output);
// outputs the PID of the process
echo $pid;
}
The code above should echo the pid of the 'inBackground' runned process.
Note that you need to save the pid to kill it later if it is still running.
Now you can do this to kill the process: (imagine the pid is 1234)
//'F' to Force kill a process
exec("taskkill /pid 1234 /F");
Here is my first post ever here on stackoverflow,
I hope this will help someone. Have a awesome and not lonely christmas ♪♪
You will have to install an extra extension, but found the solution located at Uniformserver's Wiki.
UPDATE
After some searching you might look into tasklist which coincidently, you may be able to use with the PHP exec command to get what you are after.
Here's a somewhat less "hacky" version of SeanDowney's answer.
PsExec returns the PID of the spawned process as its integer exit code. So all you need is this:
<?php
function spawn($script)
{
#exec('psexec -accepteula -d php.exe ' . $script . ' 2>&1', $output, $pid);
return $pid;
} // spawn
echo spawn('phpinfo.php');
?>
The -accepteula argument is needed only the first time you run PsExec, but if you're distributing your program, each user will be running it for the first time, and it doesn't hurt anything to leave it in for each subsequent execution.
PSTools is a quick and easy install (just unzip PSTools somewhere and add its folder to your path), so there's no good reason not to use this method.

Categories