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.