title: Handling correctly the ffmpeg & ffprobe with php
body: <p><strong>Handling correctly the ffmpeg &amp; ffprobe with php</strong></p>
  
  <p><em>maybe not relevant final goals:</em></p>
  
  <ol>
  <li>upload clip with ajax</li>
  <li>get ajax info from <code>ffprobe</code> using php as json executing <code>ffprobe</code> once only (no <code>ffmpeg</code>)</li>
  <li>handle all calculations with javascript</li>
  <li>maybe an extra php script tool that can create gifs, extract frames(thumbs), or a video grid preview</li>
  <li>when rdy ajax the conversion info to the final php conversion script executing ffmpeg once only (just the final ffmpeg string.).</li>
  </ol>
  
  <p>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:</p>
  
  <p><strong>1. HOW TO GET THE INFO?</strong></p>
  
  <p>I'm using <strong>ffprobe</strong> to get the file information as json with the following command:</p>
  
  <pre><code>ffprobe -v quiet -print_format json -show_format -show_streams -show_packets '.$video
  </code></pre>
  
  <p>this gives you a lot of information, but some relevant stuff is not always present. I need the <strong>duration</strong> (in milliseconds),the <strong>fps</strong> (as a float) and the <strong>total frames</strong> (as an integer).</p>
  
  <p>i know that these values can sometimes be found inside this array: </p>
  
  <pre><code>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
  </code></pre>
  
  <p>but most of the time <code>nb_frames</code> is missing, also <code>avg_frame_rate</code> differs from <code>r_frame_rate</code>, which is also not always available.</p>
  
  <p>I know that i could use multiple commands to increase the chance to get the correct values.. but srsly???</p>
  
  <pre><code>//fps
  ffmpeg -i INPUT 2&gt;&amp;1 | sed -n "s/.*, \(.*\) fp.*/\1/p" 
  //duration
  ffmpeg -i INPUT 2&gt;&amp;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&gt;&amp;1 | tr ^M '\n' | awk '/^frame=/ {print $2}'|tail -n 1
  </code></pre>
  
  <p>I don't want to execute ffmpeg 3 times to get this information; I'd prefer to just use ffprobe.</p>
  
  <p><strong>So... is there an elegant way to get the extra info that is not always present inside the ffprobe output (fps, frames, duration)???</strong></p>
  
  <p>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.</p>
  
  <pre><code>ffmpeg -i INPUT -vf 'select=gte(n\,FRAMENUMBER)' -vframes 1 -f image2 OUTPUT
  </code></pre>
  
  <p>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...</p>
  
  <hr>
  
  <p><strong>2. WHILE READING THE LOG HOW TO SKIP ERRORS AND DETERMINE IF THE CONVERSION IS FINISHED?</strong></p>
  
  <p>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.</p>
  
  <p>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 <code>frame</code> ....</p>
  
  <p><strong>How can i find out when the conversion progress is finished?</strong></p>
  
  <p>maybe a way to delete the log with ffmpeg command??And skip/log the errors??</p>
  
  <p>i'm using server sent events to read the log...
  here is the php code</p>
  
  <pre><code>&lt;?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);
  }
  ?&gt;
  </code></pre>
  
  <p>And here the SSE js</p>
  
  <pre><code>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]+'&lt;br&gt;'+
    'Time Left2: '+(ms2TimeString(((frames-x.frame)/x.fps)*1000)+(timeString2ms(x.time)/(duration*1000)*100|0)).split('.')[0]+'&lt;br&gt;'+
    'Estimated Total: '+ms2TimeString(estimatedTotalTime*1000).split('.')[0]+'&lt;br&gt;'+
    '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;
    }
   }
  }
  </code></pre>
  
  <p>EDIT</p>
  
  <p>HERE IS A SAMPLE OUTPUT after detecting h264 codec in a m2ts with ac3 audio</p>
  
  <p>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 &amp; more browsers compatible file. </p>
  
  <pre><code>$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." &lt;/dev/null &gt;/dev/null 2&gt;".$progress." &amp;";
  </code></pre>
  
  <p>if you have any questions about the code or want to see more code just ask... </p>