PHP - FFMPEG - Unable to apply multiple regular expressions on media output - php

I am writing a php script (I have to use php) to cycle through a dir of media files and displaying media information in a chart.
Right now, I'm trying to do it on 1 file.
I can successfully do so. I currently can parse the output (using regular expressions) to obtain information such as file name, duration, resolution, etc
I was told that I can combine the regular expressions to make it more efficient.
When I do however, I am unable to parse the ffmpeg output correctly.
Consequently, I've tried copying the output to a string and parsing that using multiple expressions and it works just fine.
Any help is appreciated.
Thanks.
Working code
$media_info = "'test.mkv': Metadata: creation_time : 2011-03-12 09:04:18 Duration: 00:21:44.10, start: 0.000000, bitrate: 500 kb/s Chapter #0.0: start 0.097000, end 1304.107000 Metadata: title : 00:00:00.097 Stream #0:0: Video: h264 (High), yuv420p, 720x400 [SAR 80:81 DAR 16:9], 23.98 fps, 23.98 tbr, 1k tbn, 47.95 tbc (default) Stream #0:1: Audio: aac, 48000 Hz, stereo, s16 (default) At least one output file must be specified";
$file = array();
$file_test = preg_match_all("/'([a-zA-Z0-9\._]*\.[a-zA-Z0-9]*)'.* Duration: ([0-9]{2,}:[0-9]{2}:[0-9]{2}\.[0-9]*)/", $media_info, $file);;
var_dump($file)
Not Working Code
ob_start();
passthru("C:\\wamp\\www\\ffmpeg\\bin\\ffmpeg.exe -i \"{$videofile}\" 2>&1");
$raw_data = ob_get_contents();
ob_end_clean();
$ffmpeg_info = explode("from", $raw_data);
$media_info = $ffmpeg_info[1];
$file = array();
$file_test = preg_match_all("/'([a-zA-Z0-9\._]*\.[a-zA-Z0-9]*)'.* Duration: ([0-9]{2,}:[0-9]{2}:[0-9]{2}\.[0-9]*)/", $media_info, $file);;
var_dump($file)
New Code
I tried using the ffmpeg-php extension and the following works (except for printing the bit rate)
$movie = new ffmpeg_movie($video_file);
echo $movie->getFilename();
echo "<br/>" ;
echo $movie->getDuration();
echo "<br/>" ;
echo $movie->getFrameWidth();
echo "x";
echo $movie->getFrameHeight();
echo "<br/>" ;
echo $movie->getBitRate();
echo "<br/>" ;
echo $movie->getVideoBitRate();
echo "<br/>" ;
echo $movie->getAudioBitRate();
echo "<br/>" ;
echo $movie->getVideoCodec();
echo "<br/>" ;
echo $movie->getAudioCodec();
?>

Are you able to use a library to extract the information instead of a regex on the output of a command line tool? For example, ffmpeg-php?

Related

Retrieving information from ffmpeg shell_exec command

i am doing the following
$output = shell_exec('/usr/local/bin/ffmpeg -i intro.mp3 2>&1');
echo "<pre>$output</pre>";
This outputs
ffmpeg version 0.8.5, Copyright (c) 2000-2011 the FFmpeg developers
built on Aug 20 2012 09:28:43 with clang 3.1 (tags/Apple/clang-318.0.61)
configuration: --enable-nonfree --enable-gpl --enable-version3 --enable-postproc --enable-swscale --enable-avfilter --enable-libmp3lame --enable-libvorbis --enable-libtheora --enable-libfaac --enable-libxvid --enable-libx264 --enable-libvpx --enable-hardcoded-tables --enable-shared --enable-pthreads --disable-indevs --cc=clang
libavutil 51. 9. 1 / 51. 9. 1
libavcodec 53. 7. 0 / 53. 7. 0
libavformat 53. 4. 0 / 53. 4. 0
libavdevice 53. 1. 1 / 53. 1. 1
libavfilter 2. 23. 0 / 2. 23. 0
libswscale 2. 0. 0 / 2. 0. 0
libpostproc 51. 2. 0 / 51. 2. 0
[mp3 # 0x7f9481807c00] max_analyze_duration 5000000 reached at 5015510
Input #0, mp3, from 'intro.mp3':
Metadata:
album : Frank
artist : Amy Winehouse
genre : R&B
title : [Intro] Stronger Than Me
track : 01
date : 2008
Duration: 00:03:54.71, start: 0.000000, bitrate: 230 kb/s
Stream #0.0: Audio: mp3, 44100 Hz, stereo, s16, 160 kb/s
At least one output file must be specified
i want to get the information to insert into a database album,artist,genre,title etc
But all seperately
i can get them by doing the following
$output = shell_exec('/usr/local/bin/ffmpeg -i intro.mp3 2>&1');
$edit = explode(' ', $output);
$edit = implode("#", $edit);
$edit = explode(':', $output);
echo "<pre>";
print_r($edit);
echo "</pre>";
then running
<?php echo $edit[9]; ?>
etc
but this seems like a really bad way and annoying way to get values and sometimes the output is slightly different which messes with the outputs.
Whats the best way to do this or am i stuck doing this?
There is a better way to retrieve metadata using ffmpeg.
1. Create a new ffmpeg_movie object e.g
$movie = new ffmpeg_movie(String path_to_media, boolean persistent);
2. Start getting metada like so:
$movie->getAuthor();
$movie->getGenre();
$movie->getAlbum();
List of what's available at ffmpeg-php API documentation.
3. Celebrate good times.

ffmpeg Progress Bar - Encoding Percentage in PHP

I've written a whole system in PHP and bash on the server to convert and stream videos in HTML5 on my VPS. The conversion is done by ffmpeg in the background and the contents is output to block.txt.
Having looked at the following posts:
Can ffmpeg show a progress bar?
and
ffmpeg video encoding progress bar
amongst others, I can't find a working example.
I need to grab the currently encoded progress as a percentage.
The first post I linked above gives:
$log = #file_get_contents('block.txt');
preg_match("/Duration:([^,]+)/", $log, $matches);
list($hours,$minutes,$seconds,$mili) = split(":",$matches[1]);
$seconds = (($hours * 3600) + ($minutes * 60) + $seconds);
$seconds = round($seconds);
$page = join("",file("$txt"));
$kw = explode("time=", $page);
$last = array_pop($kw);
$values = explode(' ', $last);
$curTime = round($values[0]);
$percent_extracted = round((($curTime * 100)/($seconds)));
echo $percent_extracted;
The $percent_extracted variable echoes zero, and as maths is not my strong point, I really don't know how to progress here.
Here's one line from the ffmpeg output from block.txt (if it's helpful)
time=00:19:25.16 bitrate= 823.0kbits/s frame=27963 fps= 7 q=0.0 size=
117085kB time=00:19:25.33 bitrate= 823.1kbits/s frame=27967 fps= 7
q=0.0 size= 117085kB time=00:19:25.49 bitrate= 823.0kbits/s
frame=27971 fps= 7 q=0.0 size= 117126kB
Please help me output this percentage, once done I can create my own progress bar. Thanks.
Okay, I've found what I needed - and hopefully this helps someone else as well!
First and foremost, you want to output the ffmpeg data to a text file on the server.
ffmpeg -i path/to/input.mov -vcodec videocodec -acodec audiocodec path/to/output.flv 1> block.txt 2>&1
So, the ffmpeg output is block.txt. Now in PHP, let's do this!
$content = #file_get_contents('../block.txt');
if($content){
//get duration of source
preg_match("/Duration: (.*?), start:/", $content, $matches);
$rawDuration = $matches[1];
//rawDuration is in 00:00:00.00 format. This converts it to seconds.
$ar = array_reverse(explode(":", $rawDuration));
$duration = floatval($ar[0]);
if (!empty($ar[1])) $duration += intval($ar[1]) * 60;
if (!empty($ar[2])) $duration += intval($ar[2]) * 60 * 60;
//get the time in the file that is already encoded
preg_match_all("/time=(.*?) bitrate/", $content, $matches);
$rawTime = array_pop($matches);
//this is needed if there is more than one match
if (is_array($rawTime)){$rawTime = array_pop($rawTime);}
//rawTime is in 00:00:00.00 format. This converts it to seconds.
$ar = array_reverse(explode(":", $rawTime));
$time = floatval($ar[0]);
if (!empty($ar[1])) $time += intval($ar[1]) * 60;
if (!empty($ar[2])) $time += intval($ar[2]) * 60 * 60;
//calculate the progress
$progress = round(($time/$duration) * 100);
echo "Duration: " . $duration . "<br>";
echo "Current Time: " . $time . "<br>";
echo "Progress: " . $progress . "%";
}
This outputs the percentage of time left.
You can have this as the only piece of text echoed out to a page, and from another page you can perform an AJAX request using jQuery to grab this piece of text and output it into a div, for example, to update on your page every 10 seconds. :)
ffmpeg now has a progress option, which gives output more easily parsed.
ffmpeg -progress block.txt -i path/to/input.mov -vcodec videocodec -acodec audiocodec path/to/output.flv 2>&1
Before you start encoding you can get the total frames, and a lot of other info with this (this is what would be done with bash. I'm a Perl programmer so I don't know how you'd get the info into your PHP script).
eval $(ffprobe -of flat=s=_ -show_entries stream=height,width,nb_frames,duration,codec_name path/to/input.mov);
width=${streams_stream_0_width};
height=${streams_stream_0_height};
frames=${streams_stream_0_nb_frames};
videoduration=${streams_stream_0_duration};
audioduration=${streams_stream_1_duration};
codec=${streams_stream_0_codec_name};
echo $width,$height,$frames,$videoduration,$audioduration,$codec;
-of flate=s=_ says to put each name=value on a separate line. -show_entries tells it to show the entries from what follows (stream for -show_streams, format for -show_format, etc.) stream=... says to show those items from the -show_streams output. Try the following to see what is available:
ffprobe -show_streams path/to/input.mov
The output to the progress file is added to approximately once a second. Content, after the encoding is finished, looks like the following. In my script, once a second I am putting the file into an array, and traversing the array in reverse order, using only what is between the first [last before reversal] two "progress" lines I find, so that I am using the most recent info from the end of the file. There may be better ways. This is from an mp4 with no audio so there is only one stream.
frame=86
fps=0.0
stream_0_0_q=23.0
total_size=103173
out_time_ms=1120000
out_time=00:00:01.120000
dup_frames=0
drop_frames=0
progress=continue
frame=142
fps=140.9
stream_0_0_q=23.0
total_size=415861
out_time_ms=3360000
out_time=00:00:03.360000
dup_frames=0
drop_frames=0
progress=continue
frame=185
fps=121.1
stream_0_0_q=23.0
total_size=1268982
out_time_ms=5080000
out_time=00:00:05.080000
dup_frames=0
drop_frames=0
progress=continue
frame=225
fps=110.9
stream_0_0_q=23.0
total_size=2366000
out_time_ms=6680000
out_time=00:00:06.680000
dup_frames=0
drop_frames=0
progress=continue
frame=262
fps=103.4
stream_0_0_q=23.0
total_size=3810570
out_time_ms=8160000
out_time=00:00:08.160000
dup_frames=0
drop_frames=0
progress=continue
frame=299
fps=84.9
stream_0_0_q=-1.0
total_size=6710373
out_time_ms=11880000
out_time=00:00:11.880000
dup_frames=0
drop_frames=0
progress=end
if javascript updates your progress bar, javascript could perform step 2 "directly" :
[this example requires dojo ]
1 php: start conversion and write status to a textfile - example syntax:
exec("ffmpeg -i path/to/input.mov path/to/output.flv 1>path/to/output.txt 2>&1");
For the second part we need just javascript to read the file.
The following example uses dojo.request for AJAX, but you could use jQuery or vanilla or whatever as well :
[2] js: grab the progress from the file:
var _progress = function(i){
i++;
// THIS MUST BE THE PATH OF THE .txt FILE SPECIFIED IN [1] :
var logfile = 'path/to/output.txt';
/* (example requires dojo) */
request.post(logfile).then( function(content){
// AJAX success
var duration = 0, time = 0, progress = 0;
var result = {};
// get duration of source
var matches = (content) ? content.match(/Duration: (.*?), start:/) : [];
if( matches.length>0 ){
var rawDuration = matches[1];
// convert rawDuration from 00:00:00.00 to seconds.
var ar = rawDuration.split(":").reverse();
duration = parseFloat(ar[0]);
if (ar[1]) duration += parseInt(ar[1]) * 60;
if (ar[2]) duration += parseInt(ar[2]) * 60 * 60;
// get the time
matches = content.match(/time=(.*?) bitrate/g);
console.log( matches );
if( matches.length>0 ){
var rawTime = matches.pop();
// needed if there is more than one match
if (lang.isArray(rawTime)){
rawTime = rawTime.pop().replace('time=','').replace(' bitrate','');
} else {
rawTime = rawTime.replace('time=','').replace(' bitrate','');
}
// convert rawTime from 00:00:00.00 to seconds.
ar = rawTime.split(":").reverse();
time = parseFloat(ar[0]);
if (ar[1]) time += parseInt(ar[1]) * 60;
if (ar[2]) time += parseInt(ar[2]) * 60 * 60;
//calculate the progress
progress = Math.round((time/duration) * 100);
}
result.status = 200;
result.duration = duration;
result.current = time;
result.progress = progress;
console.log(result);
/* UPDATE YOUR PROGRESSBAR HERE with above values ... */
if(progress==0 && i>20){
// TODO err - giving up after 8 sec. no progress - handle progress errors here
console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }');
return;
} else if(progress<100){
setTimeout(function(){ _progress(i); }, 400);
}
} else if( content.indexOf('Permission denied') > -1) {
// TODO - err - ffmpeg is not executable ...
console.log('{"status":-400, "error":"ffmpeg : Permission denied, either for ffmpeg or upload location ..." }');
}
},
function(err){
// AJAX error
if(i<20){
// retry
setTimeout(function(){ _progress(0); }, 400);
} else {
console.log('{"status":-400, "error":"there is no progress while we tried to encode the video" }');
console.log( err );
}
return;
});
}
setTimeout(function(){ _progress(0); }, 800);

How can I detect video file duration in minutes with PHP?

I am trying to detect the duration of any video file before it is uploaded with PHP
so if it is less than one minute for example I will refuse uploading it.
if it is not possible , how can I do it after uploading the video ??
You can get video duration with ffmpeg or getID3
Example
$getID3 = new getID3;
$file = $getID3->analyze($filename);
echo("Duration: ".$file['playtime_string'].
" / Dimensions: ".$file['video']['resolution_x']." wide by ".$file['video']['resolution_y']." tall".
" / Filesize: ".$file['filesize']." bytes<br />");
Or
ob_start();
passthru("ffmpeg -i working_copy.flv 2>&1");
$duration = ob_get_contents();
$full = ob_get_contents();
ob_end_clean();
$search = "/duration.*?([0-9]{1,})/";
print_r($duration);
$duration = preg_match($search, $duration, $matches, PREG_OFFSET_CAPTURE, 3);
print_r('<pre>');
print_r($matches[1][0]);
print_r($full);
Please see
http://getid3.sourceforge.net/
http://ffmpeg.org/
How to get video duration, dimension and size in PHP?
get flv video length
Thanks
:D
for people coming to check this out this is a solution a little more advanced for the year 2017
first download the latest version from the first link above in the accepted answer the (green checkmark)
require_once('getid/getid3/getid3.php'); //path to your get id file if you do not include this in your file it will result in an error meaning the code will not work
$file = "sade.mp4"; //what you want extract info from maybe mp3,or mp4 file anything you want to get file size in bytes or time duration
$uuuuu = "videos/"; //path to your video file it could also be music not only video
$getID3 = new getID3; //initialize engine
$ThisFileInfo = $getID3->analyze($uuuuu.$file);
getid3_lib::CopyTagsToComments($ThisFileInfo);
echo '<pre>'.htmlentities(print_r($ThisFileInfo, true)).'</pre>'; //use this to print array to pick whatever you like that you want value like playduration, filesize

parse output 2>&1 with php script

I have an exec command I am using to make ffmpeg work.
$process = exec("/usr/local/bin/ffmpeg -i /home/g/Desktop/cave.wmv -deinterlace
-acodec libfaac -ab 96k -ar 44100 -vcodec libx264 -s 480x320 -f flv /home/g/Desktop
/file.flv 2>&1 | php testing123.php") ;
In testing123.php I use
$h = fopen('php://stdin', 'r'); $str = fgets($h); fclose($h);
//topic removed
$sql = 'INSERT INTO table
(id,status) VALUES(?,?)';
$stmt3 = $conn->prepare($sql);
$result=$stmt3->execute(array(rand(),$str));
However, as you may have guessed I get one database entry, only when the file initially executes from the exec command in the other file. Is there a way to keep executing the file or something else so that the output fed to the file can be inserted into my database every couple seconds?
You need to use a while loop. There are some examples in the fgets documentation.
$h = fopen('php://stdin', 'r');
while ( ($str = fgets($h)) !== false )
{
$sql = 'INSERT INTO ...';
mysql_query($sql);
}
fclose($h);
One of the comments in the documentation suggests using stream_get_line instead of fgets. In case you're interested, here's the direct link: http://www.php.net/manual/en/function.fgets.php#86319.

How to extract frame size and length from ffmpeg, in php?

I usually use midentify, which spits out a nicely formatted string, that is easy to preg_match
It however, fails sometimes, so I wanna do a fall-back method via ffmpeg. ffmpeg -i hello.avi spits this out:
Input #0, avi, from 'hello.avi':
Metadata:
encoder : Nandub v1.0rc2
Duration: 01:11:16.56, start: 0.000000, bitrate: 1202 kb/s
Stream #0.0: Video: mpeg4, yuv420p, 640x336 [PAR 1:1 DAR 40:21], 25 tbr, 25 tbn, 25 tbc
Stream #0.1: Audio: mp3, 48000 Hz, 2 channels, s16, 117 kb/s
At least one output file must be specified
I need the width and height of the actual frame size, as well as the duration.
What would be the best way to extract this from here? Im not that familiar with regex.
Duration is easy:
preg_match('/Duration: (\d{2}:\d{2}:\d{2}\.\d{2})/', $source, $matches);
$matches[1] == '01:11:16.56';
The size is harder. I'm going to guess that any set of numbers separated by nothing except an x character will be the dimensions:
preg_match('/(\d+)x(\d+)/', $source, $matches);
$matches[1] == '640';
$matches[2] == '336';
I would use the ffmpeg extension to PHP. Download and install from http://ffmpeg-php.sourceforge.net/ then:
extension_loaded('ffmpeg') or die('Error in loading ffmpeg');
$ffmpegInstance = new ffmpeg_movie('hello.avi');
echo "getDuration: " . $ffmpegInstance->getDuration() .
"getFrameCount: " . $ffmpegInstance->getFrameCount() .
"getFrameRate: " . $ffmpegInstance->getFrameRate() .
"getFilename: " . $ffmpegInstance->getFilename() .
"getComment: " . $ffmpegInstance->getComment() .
"getTitle: " . $ffmpegInstance->getTitle() .
"getAuthor: " . $ffmpegInstance->getAuthor() .
"getCopyright: " . $ffmpegInstance->getCopyright() .
"getArtist: " . $ffmpegInstance->getArtist() .
"getGenre: " . $ffmpegInstance->getGenre() .
"getTrackNumber: " . $ffmpegInstance->getTrackNumber() .
"getYear: " . $ffmpegInstance->getYear() .
"getFrameHeight: " . $ffmpegInstance->getFrameHeight() .
"getFrameWidth: " . $ffmpegInstance->getFrameWidth() .
"getPixelFormat: " . $ffmpegInstance->getPixelFormat() .
"getBitRate: " . $ffmpegInstance->getBitRate() .
"getVideoBitRate: " . $ffmpegInstance->getVideoBitRate() .
"getAudioBitRate: " . $ffmpegInstance->getAudioBitRate() .
"getAudioSampleRate: " . $ffmpegInstance->getAudioSampleRate() .
"getVideoCodec: " . $ffmpegInstance->getVideoCodec() .
"getAudioCodec: " . $ffmpegInstance->getAudioCodec() .
"getAudioChannels: " . $ffmpegInstance->getAudioChannels() .
"hasAudio: " . $ffmpegInstance->hasAudio();
I just want to add slightly modified version of the #lonesomeday's answer:
this part is correct but if you have string like this one:
Duration: 00:05:40.11, start: 0.000000, bitrate: 60847 kb/s
Stream #0:0(eng): Video: h264 (Constrained Baseline) (avc1 / 0x31637661),
yuv420p(tv, bt709), 1920x1080, 60846 kb/s, 29.97 fps, 29.97 tbr, 2997 tbn, 5994
tbc (default)
this
preg_match('/(\d+)x(\d+)/', $source, $matches);
will return
$matches[1] == '0';
$matches[2] == '31637661';
So I modified it a little:
preg_match('/(\d{2,4})x(\d{2,4})/', $source, $matches);
This way it wont match one digit or more than 4 digits.

Categories