Sunday, January 31, 2010

Splitting Still Image Sequences for use with QuickTime Movies

This is another piece of the automation for my current timelapse video project.
Update: I've rewritten all my scripts in Python and will post those soon.

In "Automating Conversion of Still Image Sequences to QuickTime Movies" I showed how to use a script (qtMovieFromStills.js) with QuickTime's COM interface to generate a movie from a sequence of still images.  I pointed out that QuickTime has an upper limit on the size of the movie that can be generated this way and mentioned that I split up my image sequences to work around that problem.  In this article I post a script splitFolders.js that manages the splitting automatically.

You can run this script to split your image sequences, then run qtMovieFromStills.js on each of the parts.

The last script was in JScript and ran inside MicroSoft's Windows Script Host (WSH) environment, largely because that was the example I started from.  I use JavaScript a lot, but not previously for host scripts.  But it was a reasonably good experience so for consistency I decided to stick with it -- I might choose to put them together into a single, larger program in the future.

splitFolders.js takes three arguments:
  1. workFolder: Full path to the folder containing your image sequence.
  2. sequenceName: Image sequences are expected to have names and sequence numbers, like "Foo-001", "Foo-002", ...  In this example the sequenceName is "Foo" or "Foo-".  My script infers "-" or "_" after the sequenceName, so you can enter just "Foo".
  3. maxSize: The maximum number of still images that you want in a single QuickTime movie.  So if you have 12,345 images and enter 5000 for maxSize, they will be split into three folders.
The split folders keep the name of your workFolder, with a number added like this:  if your workFolder is called "Further", the newly created split folders are named "Further-1", "Further-2" and so forth.  They are created with the same parent folder as your workFolder.

Here's an example.  You have the following folder containing 12,345 images:
L:\Images\TL\Further\
The images have names like "FX-0001.JPG", "FX-0002.JPG" and so forth.  The folder also contains some files that aren't part of the image sequence: resume.doc, shopping.txt.

You enter the following command in the Windows shell:
cscript splitFolders.js L:\Images\TL\Further FX 5000
It crunches along for a while, and when it finishes, here's what you'll find:

L:\Images\TL\Further
Now contains only the files that were not part of the image sequence: resume.doc and shopping.txt.
L:\Images\TL\Further-1
Contains FX-0001.JPG through FX-5000.JPG
L:\Images\TL\Further-2
Contains FX-5001.JPG through FX-10000.JPG
L:\Images\TL\Further-3
Contains FX-10001.JPG through FX-12345.JPG
Here's the script.  Copy and paste it into an editor, then save it as splitFolders.js on your computer.  Do be cautious: it's moving your precious images around and I always like to have a backup copy before doing this kind of thing.  If you're really worried, edit the script to use "CopyFile" instead of "MoveFile".  Or comment out the "MoveFile" and just run it to see what it will do.
// WSH JScript
// Author: Chip Chapin <cchapin@gmail.com>
// Last Updated: 2010-01-30

// Split an image sequence into smaller chunks to faciliate processing
// by QuickTime. See http://cchapin.blogspot.com/

function usage(s) {
  WScript.Echo(s);
  WScript.Echo("usage: splitFolders.js workFolder sequenceName maxSize");
  WScript.Echo(
"  workFolder -- Full path to folder containing the image sequence.\n" +
"  sequenceName -- Sequences have file names like Foo-001, Foo-002 ...\n" +
"      In this case the sequenceName would be 'Foo' or 'Foo-'.\n" +
"      (Dash and underscore are ignored at the end of the sequenceName)\n" +
"  maxSize -- Maximum number of sequence files to put in a folder.");
  WScript.Quit();
}

// Get script arguments
if (WScript.Arguments.Length != 3) {
  usage("Expected three arguments");
}
var workFolderName = WScript.Arguments(0);
var sequenceName = WScript.Arguments(1);
var maxSize = WScript.Arguments(2);

var fso =  WScript.CreateObject("Scripting.FileSystemObject");

if (!fso.FolderExists(workFolderName)) {
  usage("Invalid folder path '" + workFolderName + "'");
}
var workFolder = fso.GetFolder(workFolderName);

// Make a list of files that match the sequenceName pattern.
var sequenceFiles = [];
var sequencePattern = RegExp().compile(sequenceName + "[-_]?[0-9]+\.", "i");
var files = new Enumerator(workFolder.Files);
while (!files.atEnd()) {
  var f = files.item();
  if (f.Name.match(sequencePattern)) {
    sequenceFiles.push(f);
  }
  files.moveNext();
}
WScript.Echo("Folder '" + workFolder.Path +
             "' contains " + sequenceFiles.length +
             " images in sequence '" + sequenceName + "'.");
if (sequenceFiles.length == 0) {
  WScript.Quit();
}
if (sequenceFiles.length <= maxSize) {
  WScript.Echo("No need to split.");
  WScript.Quit();
}

// Iterate over the sequence files in the base folder, moving them
// to successive split folders.
var numSplits = Math.ceil(sequenceFiles.length / maxSize);
WScript.Echo("Splitting image sequence '" + sequenceName +
             "' into " + numSplits + " parts.");
var baseFolderName = fso.GetBaseName(workFolderName);
var nextFileInSplit = 1;
var currentSplit = 0;
var files = new Enumerator(workFolder.Files);
for (var fileCount = 0; fileCount < sequenceFiles.length; fileCount++) {
  if (fileCount == 0 || nextFileInSplit > maxSize) {
    currentSplit++;
    nextFileInSplit = 1;
    var splitFolder = workFolder.ParentFolder.Path + "\\" +
                      baseFolderName + "-" + currentSplit;
    WScript.Echo("New split folder '" + splitFolder + "' (" + fileCount + ")");
    fso.CreateFolder(splitFolder);
  }
  var f = sequenceFiles[fileCount];
  // Move a file from workFolder to splitFolder
  WScript.Echo(f.Name + " to " + splitFolder);
  fso.MoveFile(f.Path, splitFolder + "\\" + f.Name);
  nextFileInSplit++;
}

WScript.Echo("Split " + fileCount +
             " files in '" + workFolder +
             "' into " + currentSplit + " folders.");

// End.

References

No comments: