Something I have been wanting to do for a while now is build a video converter into my Module Builder to enable automatic conversion of video into the various web friendly formats but I could never find a way to do it.

Over the weekend I discovered this tutorial on playing back any video in Adobe AIR. The tutorial runs through how to set up a NativeProcess to hook into FFmpeg to stream video from any format into FLV. FFmpeg is an open source media library for playback and conversion of almost any type of video or audio and is used by VLC and many other applications. I finally had a possible way of converting video within an AIR app! And one of the great things about this is since its using the NativeProcess API the conversion works asynchronously and the app doesn’t lock up while its converting.


So the code below shows how to convert a video file into WebM using FFmpeg and a NativeProcess. I recommend you watch the tutorial I mentioned before using this to get some better understanding of the process.

// set up vars for the native process
var _process:NativeProcess;
var _processArgs:Vector.;
var _nativeProcessStartupInfo:NativeProcessStartupInfo;

// these will be used to calculate progress
var _currentSeconds:Number = 0;
var _totalSeconds:Number = 0;

function convert(inPath:String,outPath:String):void
{
  _nativeProcessStartupInfo = new NativeProcessStartupInfo();

  // set executable to the location of ffmpeg.exe
  _nativeProcessStartupInfo.executable = File.applicationDirectory.resolvePath("path/to/ffmpeg.exe");

  // set up the process aarguments - these arguments work for WebM
  // you can find arguments on the net for specific formats by searching google
  // for example: ffmpeg convert mov to ogv
  _processArgs = new Vector.();
  _processArgs.push('-y'); // always overwrite existing file
  _processArgs.push('-i'); // input flag
  _processArgs.push(inPath); // input path
  _processArgs.push('-s'); // video size flag
  _processArgs.push('640x360'); // video size
  _processArgs.push('-b:v'); // bitrate:video flag
  _processArgs.push('512K'); // bitrate
  _processArgs.push('-b:a'); // bitrate:audio flag
  _processArgs.push('128K'); // bitrate
  _processArgs.push(outPath); // output path

  _nativeProcessStartupInfo.arguments = _processArgs;

  // create new native process
  _process = new NativeProcess();

  // add listeners
  // ffmpeg sends conversion status data through the STANDARD_ERROR_DATA channel
  _process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, progress);

  // when conversion is completed
  _process.addEventListener(NativeProcessExitEvent.EXIT, onExit);

  // start the process
  _process.start(_nativeProcessStartupInfo);

}

Now for the STANDARD_ERROR_DATA event. This function will handle all messages sent from FFmpeg which contain valuable information such as video metadata, current encoding status and any errors.

function progress(e:ProgressEvent):void
{
  // read the data from the error channel bytearray to string
  var s:String = _process.standardError.readUTFBytes(_process.standardError.bytesAvailable);

  // stuff for finding timecodes
  var reg:RegExp;
  var matches:Array;
  var time:Array;

  // if the message includes frame=, this is a frame progress message
  // the message will be something like:
  // frame= 1018 fps=226 q=31.0 size= 3634kB time=00:00:41.79 bitrate= 712.2kbits/s
  // we need to extract the current time: time=00:00:41:79
  if (s.indexOf("frame=") != -1)
  {
    //is progress
    reg = /time=([^ ]+)/; // regexp to extract time portion
    matches = s.match(reg);

    if (matches.length > 0)
    {
      // split timestamp into sections
      time = matches[0].substring(5).split(":");
      // calculate the total seconds from the time stamp to get current seconds
      _currentSeconds = Math.round(((Number(time[0]) * 3600) + (Number(time[1]) * 60) + Number(time[2])));

    }
  }
  // Duration is sent at the beginning of the process which tells us how long the video is
  else if (s.indexOf("Duration:") != -1)
  {
    // find duration
    reg = /Duration:([^,]+)/; // regepx to extract duration portion
    matches = s.match(reg);

    if (matches.length > 0)
    {
      // split timestamp into sections
      time = matches[0].split(":");
      // calculate the total seconds from the time stamp to get total seconds
      _totalSeconds = Math.round(((Number(time[1]) * 3600) + (Number(time[2]) * 60) + Number(time[3])));
    }
  }
  // trace out the message if it contains Error, as there was probably   something wrong with the encoding settings
  else if (s.indexOf("Error ") != -1)
  {
    trace("Error: " + s);
  }

  // trace percentage
  trace( Math.round(_currentSeconds / _totalSeconds * 100) + "%");
}

And finally the EXIT event which we will just use to trace ‘Complete’:

function onExit(e:NativeProcessExitEvent):void
{
  trace("Conversion complete.");
}

Now we can call convert to convert a video:

var inpath:String = File.desktopDirectory.resolvePath("path/to/video.mp4").nativePath;
var outpath:String = File.desktopDirectory.resolvePath("path/to/video.webm").nativePath;

convert(inpath,outpath);

This is just the basics of converting a video and there are a lot more things you can do with the process arguments and you could also handle arguments for different output formats.