Tuesday, December 25, 2007

Using FFMPEG to convert video files to FLV format

While trying to figure out how to convert video clips to FLV format I found a blog entry that mentions you can use FFMPEG and the java Runtime library to do this. If you are not familiar with FFMPEG their documentation describes it as a "very fast video and audio converter".

The blog entry function worked well with small files, but attempting to convert larger files seemed to cause the process to hang. I ran the code several times, waiting a few minutes in between each attempt. But each time the processes went out into never-never-land and .. well, never came back. I checked the Task Manager afterward and it showed several orphaned ffmpeg.exe processes. Finally I resorted to bouncing the CF Server. That did kill the orphaned processes. But I was still puzzled about why I could successfully convert the same files using ffmpeg.exe on the command line but not using Runtime.exec().

Finally I found two articles that helped explain the problem. The first entry on http://blog.taragana.com/ explains that failure to drain the input/output streams of the subprocess "may cause the subprocess to block, and even deadlock." The source of this information is an old but excellent article on www.javaworld.com . It provides a more in-depth explanation of the issue and describes how to properly use Runtime.exec().

Armed with these three references, I combined all of the suggestions and it seems to have resolved the problem. Obviously this code belongs in a function, but the sample below should give the general idea. Disclaimer: Bear in mind this is the first time I have used ffmpeg.exe and Runtime.exec() so use it at your own risk ;)


UPDATE: In my testing ffmpeg.exe appears to write to a single stream. For programs that write to both the output and error stream you need to process the streams in seperate threads to prevent blocking or deadlocks. See Runtime.exec(), mencoder and cfthread for an example.



<!--- test file paths --->
<cfset ffmpegPath = "c:\bin\ffmpeg.exe">
<cfset inputFilePath = "c:\bin\testInput.mp4">
<cfset ouputFilePath = "c:\bin\testOuput.flv">
<cfset resultLog = "c:\bin\testOuput_result.log">
<cfset errorLog = "c:\bin\testOuput_error.log">

<!--- convert the file --->
<cfset results = structNew()>
<cfscript>
try {
runtime = createObject("java", "java.lang.Runtime").getRuntime();
command = '#ffmpegPath# -i "#inputFilePath#" -g 300 -y -s 300x200 -f flv -ar 44100 "#ouputFilePath#"';
process = runtime.exec(#command#);
results.errorLogSuccess = processStream(process.getErrorStream(), errorLog);
results.resultLogSuccess = processStream(process.getInputStream(), resultLog);
results.exitCode = process.waitFor();
}
catch(exception e) {
results.status = e;
}
</cfscript>

<!--- display the results --->
<cfdump var="#results#">


<!--- function used to drain the input/output streams. Optionally write the stream to a file --->
<cffunction name="processStream" access="public" output="false" returntype="boolean" hint="Returns true if stream was successfully processed">
<cfargument name="in" type="any" required="true" hint="java.io.InputStream object">
<cfargument name="logPath" type="string" required="false" default="" hint="Full path to LogFile">
<cfset var out = "">
<cfset var writer = "">
<cfset var reader = "">
<cfset var buffered = "">
<cfset var line = "">
<cfset var sendToFile = false>
<cfset var errorFound = false>

<cfscript>
if ( len(trim(arguments.logPath)) ) {
out = createObject("java", "java.io.FileOutputStream").init(arguments.logPath);
writer = createObject("java", "java.io.PrintWriter").init(out);
sendToFile = true;
}

reader = createObject("java", "java.io.InputStreamReader").init(arguments.in);
buffered = createObject("java", "java.io.BufferedReader").init(reader);
line = buffered.readLine();
while ( IsDefined("line") ) {
if (sendToFile) {
writer.println(line);
}
line = buffered.readLine();
}
if (sendToFile) {
errorFound = writer.checkError();
writer.flush();
writer.close();
}
</cfscript>
<!--- return true if no errors found. --->
<cfreturn (NOT errorFound)>
</cffunction>


If you are interested in testing this example, all you should need is

1. The ffmpeg binary. You can download a version at sourceforge.net

2. A sample video file

In my travels I read a tip from techrepulic.com that mentions a simple program called FLV player. The program allows you to view .flv files on a hard drive. While not the only way to view .flv files, it is a handy program.

As always, comments/corrections/suggestions are welcome. Enjoy!

10 comments:

Daniel January 23, 2008 at 7:15 AM  

Hi I used you code as the basis for a video system and I am writing a blog entry on how to re-encode WMV9I2 video files to .flv programatically and I just wanted to see if you would mind me re-posting it. I have links to your blog and this article in the text. Let me know. Article I am working on

Daniel January 24, 2008 at 8:08 AM  

Thanks ! just finished putting the post together. It converts video to flv, handles odd windows media codecs that FFMPEG can't and then thumbnails the resulting .flv file. Works like a charm. Link

Albert K,  January 31, 2008 at 9:35 PM  

I am not able to convert my MP4 video from my Casio 10MP camera to FLV with FFMPEG. It seems the problem has to do with the audio conversion. Does anyone know how to convert MP4 video?

cfSearching January 31, 2008 at 11:13 PM  

Albert K,

I have not done much work with the MP4 format beyond basic testing. You might try asking on the lists over at

http://lists.mplayerhq.hu/
http://lists.mplayerhq.hu/pipermail/ffmpeg-user/2006-June/003350.html

Daniel March 27, 2008 at 12:27 PM  

I believe that you have to set some specific libraries to be included when you compile FFMPEG in order to be able to convert MP4 videos.

You could try a fresh compile from the sources if you are courageous.

Thomas,  June 4, 2008 at 6:21 PM  

I love your code example here. Works great. Afew thing I would like to ask.. I'd like to be able to pull out the video duration info and save it to a variable. How can I do that?

cfSearching June 13, 2008 at 1:08 PM  

@Thomas,

You can get the raw information from ffmpeg:

ffmpeg.exe -i someVideo.ext

But you will probably need to parse the raw response to extract the details you want. If you are using the Runtime.exec method, the results will be sent to an output file. Use cffile to read the file contents into a variable. The rest is just a matter of using the right string functions.

It is worth mentioning you may also be able to use cfexecute (instead of Runtime.exec). The problems I experienced with cfexecute were fixed in the CF 8.0.1 update. For earlier versions,
there is a windows o/s trick that works, courtesy Ben Forta
.

One advantage of using cfexecute is that you can read the results directly into a variable, skipping the output file requirement.

HTH

James November 3, 2008 at 7:49 AM  

Hi,

Thanks for this, this was EXACTLY what i was looking for :)

1 question tho - has anyone got any pointers on any possible way to implement a graphical or textual way of showing the progress on the cfm page?

Many thanks

James

cfSearching November 4, 2008 at 10:46 AM  

@James,

It is not really possible to do a true progress bar without knowing how long the process will take.
But you could simulate one. Here is an article that discusses an ajax based progress bar. It is geared around multiple tasks but you could adapt it. It should work even if you are not using cfthread.

http://www.coldfusionjedi.com/index.cfm/2007/9/27/CFThread-Demo-with-Status-Messages
http://cfsilence.com/blog/client/index.cfm/2007/10/15/Checking-Thread-Progress-With-Ajax

Of course for the simpler needs, there is always the low-tech retro progress bar hack (read: animated gif ;-)

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep