1

Handling correctly the ffmpeg & ffprobe with php

maybe not relevant final goals:

  1. upload clip with ajax
  2. get ajax info from ffprobe using php as json executing ffprobe once only (no ffmpeg)
  3. handle all calculations with javascript
  4. maybe an extra php script tool that can create gifs, extract frames(thumbs), or a video grid preview
  5. when rdy ajax the conversion info to the final php conversion script executing ffmpeg once only (just the final ffmpeg string.).

I'm trying to write my own ffmpeg local web video editor that converts all formats to mp4 automatically. As mp4 is the most compatible container now and the h264+aac/+ac3 is also one of the best compressions. I also want to be able to cut, crop, resize, remove streams, add streams and more. I'm stuck on some simple problems:

1. HOW TO GET THE INFO?

I'm using ffprobe to get the file information as json with the following command:

ffprobe -v quiet -print_format json -show_format -show_streams -show_packets '.$video

this gives you a lot of information, but some relevant stuff is not always present. I need the duration (in milliseconds),the fps (as a float) and the total frames (as an integer).

i know that these values can sometimes be found inside this array:

format.duration //Total duration
streams[0].duration //Video duration
streams[1].duration //Audio duration

streams[0].avg_frame_rate //Average framerate
streams[0].r_frame_rate //Video framerate

streams[0].nb_frames //Total frames

but most of the time nb_frames is missing, also avg_frame_rate differs from r_frame_rate, which is also not always available.

I know that i could use multiple commands to increase the chance to get the correct values.. but srsly???

//fps
ffmpeg -i INPUT 2>&1 | sed -n "s/.*, \(.*\) fp.*/\1/p" 
//duration
ffmpeg -i INPUT 2>&1 | awk '/Duration/ {split($2,a,":");print a[1]*3600+a[2]*60+a[3]}'
//frames
ffmpeg -i INPUT -vcodec copy -f rawvideo -y /dev/null 2>&1 | tr ^M '\n' | awk '/^frame=/ {print $2}'|tail -n 1

I don't want to execute ffmpeg 3 times to get this information; I'd prefer to just use ffprobe.

So... is there an elegant way to get the extra info that is not always present inside the ffprobe output (fps, frames, duration)???

In the preview i want to be able to jump correctly to a specific frame (NOT TIME). if the above parameters are aviable i can do that using this command.

ffmpeg -i INPUT -vf 'select=gte(n\,FRAMENUMBER)' -vframes 1 -f image2 OUTPUT

using the above command by setting the framenumber to the last frame always returns a black frame. if there are 50 frames (for example) the range is 1-50 -- correct? Frame 50 is black, frame 1 is ok, frame 0 returns an error...


2. WHILE READING THE LOG HOW TO SKIP ERRORS AND DETERMINE IF THE CONVERSION IS FINISHED?

I'm able to upload one single video per time (per page) and i can read the current progress from the ffmpeg generated output log until i don't close the page. more control/multiple conversions would be nice.

i'm reading the last line of the log with a custom tail function but as this is a log that also includes errors i don't always get a nice line containing the desidered values. btw to check if the progress is complete i check if the last line CONTAINS the WORD frame ....

How can i find out when the conversion progress is finished?

maybe a way to delete the log with ffmpeg command??And skip/log the errors??

i'm using server sent events to read the log... here is the php code

<?php
setlocale(LC_CTYPE, "en_US.UTF-8");
function tailCustom($filepath,$lines=1,$adaptive=true){
 // a custom function to get the last line of a textfile.
}
function send($data){
 echo "id: ".time().PHP_EOL;
 echo "data: ".$data.PHP_EOL;
 echo PHP_EOL;
 ob_flush();
 flush();
}
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while(true){
 send(tailCustom($_GET['log'].".log"));
 sleep(1);
}
?>

And here the SSE js

function startSSE(fn){
 sse=new EventSource("ffmpegProgress.php?log="+encodeURIComponent(fn));
 sse.addEventListener('message',conversionProgress,false);
}
function conversionProgress(e){
 if(e.data.substr(0,6)=='frame='){
  inProgress=true;
  var x=e.data.match(/frame=\s*(.*?)\s*fps=\s*(.*?)\s*q=\s*(.*?)\s*size=\s*(.*?)\s*time=\s*(.*?)\s*bitrate=\s*(.*?)\s*$/);
  x.shift();x={frame:x[0]*1,fps:x[1]*1,q:x[2],size:x[3],time:x[4],bitrate:x[5]};
var elapsedTime = ((new Date().getTime()) - startTime);
var chunksPerTime = timeString2ms(x.time) / elapsedTime;
var estimatedTotalTime = duration / chunksPerTime;
var timeLeftInSeconds = Math.abs(elapsedTime-(estimatedTotalTime*1000));
var withOneDecimalPlace = Math.round(timeLeftInSeconds * 10) / 10;
  conversion.innerHTML='Time Left: '+ms2TimeString(timeLeftInSeconds).split('.')[0]+'<br>'+
  'Time Left2: '+(ms2TimeString(((frames-x.frame)/x.fps)*1000)+(timeString2ms(x.time)/(duration*1000)*100|0)).split('.')[0]+'<br>'+
  'Estimated Total: '+ms2TimeString(estimatedTotalTime*1000).split('.')[0]+'<br>'+
  'Elapsed Time: '+ms2TimeString(elapsedTime).split('.')[0];
 }else{
  if(inProgress){
   sse.removeEventListener('message',conversionProgress,false);
   sse.close();
   sse=null;
   conversion.textContent='Finished in '+ms2TimeString((new Date().getTime()) - startTime).split('.')[0];
   //delete log/old file??
   inProgress=false;
  }
 }
}

EDIT

HERE IS A SAMPLE OUTPUT after detecting h264 codec in a m2ts with ac3 audio

As most devices can already read h264 i just need to convert the audio in aac and copy the same audio AC3 as second track. and put everything inside a mp4 container. So that i have a Android/chrome/ios & more browsers compatible file.

$opt="-map 0:0 -map 0:1 -map 0:1 -c:v copy -c:a:0 libfdk_aac -metadata:s:a:0 language=ita -b:a 128k -ar 48000 -ac 2 -c:a:1 copy -metadata:s:a:1 language=ita -movflags +faststart";

$i="in.m2ts";
$o="out.mp4";
$t="title";
$y="2014";
$progress="nameoftheLOG.log";

$cmd="ffmpeg -y -i ".escapeshellarg($i)." -metadata title=".$t." -metadata date=".$y." ".$opt." ".$o." </dev/null >/dev/null 2>".$progress." &";

if you have any questions about the code or want to see more code just ask...

cocco
  • 16,442
  • 7
  • 62
  • 77
  • yes.. i'm already running the processes in the background.1. But some info about the video/audio is missing.2. i need a better way to parse the log ... to get always the correct % of the conversion and the correct end of the conversion... Maybe i could use PID wich is described in the comment of the last answer in the second link... but how? – cocco Sep 29 '14 at 10:10
  • How about... you don't use `php` for this? I mean, `python` is a lot more powerful and easy when it comes to parsing string and files, and there are extensive [tutorials](http://www.linuxuser.co.uk/tutorials/build-a-media-converter-with-python-qt-and-ffmpeg), [tools](http://python-video-converter.readthedocs.org/en/latest/intro.html) and [general talk](http://stackoverflow.com/questions/9896644/) about using it with ffmpeg and ffprobe. Then you just need to run the python process from php and find a way to [communicate between the two](http://stackoverflow.com/questions/5965655/) – yuvi Sep 29 '14 at 10:22
  • bacause also php is powerfull.. and i can write php .. i write in php since various years... more than 15... and also php can do that.. (comunicate with ffmpeg). Again.. 1.How to get the framerate/duration/frames FROM FFMPEG OR FFPROBE. 2.how to know exactly know when the conversion is finished. there is no need for python. i added a sample output generated by php.... – cocco Sep 29 '14 at 10:36
  • php can do that, but python (and ruby, and other similar languages) make string parsing a lot easier than php. It was only a suggestion anyway. I'll try have a look at your code later today – yuvi Sep 29 '14 at 10:43
  • btw maybe i explained myself not good enough ... the whole code already works with 70% of the videos... some old maybe broken files don't return correcctly the whole data i need to do extra manipulations.And if the ffmpeg output throws an error while converting the progress process on the client returned with servers sent events stops.... – cocco Sep 29 '14 at 10:43

0 Answers0