Transcoding Motion-JPEG (.MOV) to MPEG-4 (H264)

By Beej

 

please see => Newer Approach

 

I have a Panasonic DMC-ZS5 (dpreview, Panasonic) which creates .MOV files that contain the Motion JPEG (M-JPEG) “format” (for want of a more technical term).

In order to stream those videos from my IIS/PHP based photo gallery (zenPhoto.org), they must be converted to a more “web compatible” format like MPEG-4.  I haven’t found a more straightforward approach than direct batch conversion to another format… you can readily automate the conversion of say all the videos in a folder so it’s pretty much turnkey and ignore.

Update 2015-01-05: This is my current go-to:

for %v in (*.mov) do ffmpeg -i "%v" -vcodec h264 -acodec aac -strict -2 "%~nv.mp4"

Notes:

  • make sure to double up all the %%v if you put in .cmd batch file
  • ffmpeg is a very popular 3rd party command line util. I get mine from here.

Update 2015-07-18: cropping 3D movies down to single image

ffmpeg -i "in_movie_file.ext" -vf "crop=960:800:0:0,setdar=4:2" -vcodec h264 -acodec aac -strict -2 "out_movie_file.mp4"
  • obviously check the real resolution before setting the crop… just divide it by 2
  • “setdar” is the aspect ratio… i found it was necessary… one way to find it is with VLC CTRL-J on the original video

VLC will do this via a command line like so:

"c:Program Files (x86)VLCvlc.exe" -vvv %1 --sout=#transcode{acodec=mpga,vcodec=h264,venc=x264,deinterlace,vfilter="rotate{angle=270}"}:standard{mux=mp4,dst="%~n1.mp4"}, vlc://quit

Notes:

  • I’ve had to remove the acodec=mpga for my iPhone MOV’s or else I get garbled audio.
  • I included the vfilter=”rotate…” for rotation syntax since it was so hard for me to find but only include if you want rotation.

However, I noticed that VLC chops off the last 2 seconds no matter what I do… it seemed a little better choosing a different vcodec but h264 is too rocking to use anything else.

So I wound up going with QuickTime as my go-to transcoder for now.  It doesn’t truncate any video and creates a slightly smaller output file than VLC.  The compression is dramatic and h264 does an awesome job with preserving quality… even while maintaining 1280 x 720 HD, a 100MB MJPG will go down to a 5MB h264/MPEG file.

Following code stolen from here and tweaked a little, automates the QuickTime COM API to convert a directory full of MJPG’s (see sample code for Chapter.8 > “BatchExport.js”).

There’s no reason why this shouldn’t be in PowerShell… it’d be interesting to see if it was any more readable.

//----------------------------------------------------------------------------------
//
//    Written by    :    John Cromie
//    Copyright    :    ? 2006 Skylark Associates Ltd.
//                                                                               
//    Purchasers of the book "QuickTime for .NET and COM Developers" are entitled   
//    to use this source code for commercial and non-commercial purposes.                   
//    This file may not be redistributed without the written consent of the author.
//    This file is provided "as is" with no expressed or implied warranty.
//
//----------------------------------------------------------------------------------
 
 
function unquote(str) { return str.charAt(0) == '"' && str.charAt(str.length - 1) == '"' ? str.substring(1, str.length - 1) : str; }
 
 
// Run from command line as follows:
//
// cscript BatchExport.js , , , , , 
 
var sourcePath, destPath, configXMLFilePath, convertFileExtension, exporterType, exportFileExtension;
 
// Get script arguments
if (WScript.Arguments.Length >= 4)
{
    sourcePath = unquote(WScript.Arguments(0));
    destPath = unquote(WScript.Arguments(1));
    configXMLFilePath = unquote(WScript.Arguments(2));
    convertFileExtension = unquote(WScript.Arguments(3));
    exporterType = WScript.Arguments(4);
    exportFileExtension = WScript.Arguments(5);
}
 
//sourcePath = "D:QuickTimeMoviesBirdsKittiwake";
//destPath = "D:QuickTimeMoviesExportDest";
//exporterType = "BMP";
//exportFileExtension = "bmp";
 
// Sanity check arguments
var fso = WScript.CreateObject("Scripting.FileSystemObject");
 
var e = "";
 
if (!fso.FolderExists(sourcePath))
    e += "Source path does not exist : " + "[" + sourcePath + "]n";
    
if (!fso.FolderExists(destPath))
    e += "Destination path does not exist : " + "[" + destPath + "]n";
 
if (!fso.FolderExists(configXMLFilePath))
    e += "Config XML file path does not exist : " + "[" + configXMLFilePath + "]n";
 
if (convertFileExtension == undefined)
    e += "No convert file extension supplied!n";
 
if (exporterType == undefined)
    e += "No exporter type supplied!n";
    
if (exportFileExtension == undefined)
    e += "No exporter file extension supplied!n";
 
if (e != "")
{
    WScript.Echo(e);
    WScript.Echo("Usage:");
    WScript.Echo("cscript BatchExport.js , , , , , ");
    WScript.Quit();
}
 
// Launch QuickTime Player   
var qtPlayerApp = WScript.CreateObject("QuickTimePlayerLib.QuickTimePlayerApp");
 
if (qtPlayerApp == null)
{
    WScript.Echo("Unable to launch QuickTime Player!");
    WScript.Quit();
}
 
var qtPlayerSrc = qtPlayerApp.Players(1);
 
if (qtPlayerSrc == null)
{
    WScript.Echo("Unable to retrieve QuickTime Player instance!");
    WScript.Quit();
}
 
// Set up the exporter and have it configured
var qt = qtPlayerSrc.QTControl.QuickTime;
qt.Exporters.Add();
var exp = qt.Exporters(1);
exp.TypeName = exporterType;
 
// settings file...
var FileSystemObject =  WScript.CreateObject("Scripting.FileSystemObject");
var configXMLFileInfo;
 
if ( FileSystemObject.FileExists(configXMLFilePath) )
    configXMLFileInfo =  FileSystemObject.OpenTextFile( configXMLFilePath );
 
// if settings files exists, load it and assign it to the exporter
if ( configXMLFileInfo )    {
    var configXMLString = configXMLFileInfo.ReadAll();
    // cause the exporter to be reconfigured
    // https://developer.apple.com/technotes/tn2006/tn2120.html
    var tempSettings = exp.Settings;
    tempSettings.XML = configXMLString;
    exp.Settings = tempSettings;
} else  {
    //otherwise, get the settings from the user dialog and save them to xml file for subsequent runs
    exp.ShowSettingsDialog();
 
    var configXMLString = exp.Settings.XML;
    configXMLFileInfo = FileSystemObject.CreateTextFile( configXMLFilePath );
    if ( configXMLFileInfo )  {
        configXMLFileInfo.WriteLine(configXMLString);
        configXMLFileInfo.Close();
    } else {
        WScript.Echo("Unable to create config XML file : " + "[" + configXMLFilePath + "]");
        WScript.Quit();
    }
 
}
 
 
var fldr = fso.GetFolder(sourcePath);
 
// Regular expression to match file extension
var re = new RegExp("."+convertFileExtension+"$", "i");
 
// Iterate over the source files
var fc = new Enumerator(fldr.Files);
for (; !fc.atEnd(); fc.moveNext())
{
    var f = fc.item().Name;
    
    // Filter by file extension
    if (!re.test(f))
        continue;
    
    try
    {
        // Open the movie and export it
        qtPlayerSrc.OpenURL(fc.item());
        
        var mov = qtPlayerSrc.QTControl.Movie;
        if (mov)
        {
            exp.SetDataSource(mov);
            
            // Strip file extension and compose new file name
            f = f.replace(/.[^.]*$/, "");
            var fDest = destPath + "" + f + "." + exportFileExtension;
            
            exp.DestinationFileName = fDest;
            exp.BeginExport();
            
            WScript.Echo("Exported: " + fDest);
        }
    }
    catch (err)
    {
        WScript.Echo("Error Exporting: " + fc.item());    
    }
        
}
 
// Tidy up
qtPlayerSrc.Close();
Tags: Video
Share: Twitter Facebook LinkedIn