Saturday, March 27, 2010

My Experience With Deep Venous Thrombosis (DVT) [Part One]

This is intended to be the first of several posts relating my current experience with DVT. I wrote most of this in the days right after my diagnosis. Rather than waiting until I can write the whole story, I decided to go ahead and post this much.

My first memory of the term "DVT" is on the referral form given to me by my doctor, requesting an ultrasound scan of my "lower left extremity". I didn't know what it meant but I knew that we were trying to find out why my left leg sometimes felt like I was wearing a rubber band above my knee. This had been going on for about four weeks. It was Tuesday, February 23 2010.

I'm less clear on the onset of this discomfort -- it must have been gradual -- but particularly recall the night at the end of January when I came home with pain in my left calf along with a "puffy" feeling, desperate to put my foot up. My wife Evelyn brought me blankets to elevate it and a hot pad to make it feel better. I called upon The Google and soon concluded that the most likely causes were dehydration or shin splints. There was a third possibility, but it was much less likely; I was happy to exclude it: a blood clot.

There followed four weeks of careful stretching, drinking more water, elevation when possible, and moderate gentle exercise. I also tried massaging the calf, but usually had to stop quickly. It seemed to discomfit my entire leg below the knee. The feeling was difficult to describe. Not a pain, but a funny sort of ache: if you can imagine a limb feeling faint then that's pretty close.

The acute discomfort I felt at the end of January did subside, but only to a vague ache. The calf felt stretched and my ankle was a little puffy. Sitting in certain cars began to be uncomfortable. And it wasn't getting any better.

I had to explain to my work colleagues why I wouldn't go jogging with them, and why I was putting my foot up more. "Shin splints" was a good explanation for a week or so, but after that at least one of them gave me that most basic of advice, "See a doctor."

Thursday February 18th I took a short flight from San Jose to San Diego and my discomfort increased. The return flight Saturday was a little better, but only because I sat in an exit row and could fully stretch out. I told myself that I'd see a doctor as soon as possible the next week.

Sure enough, come Monday I called my employer's in-house health clinic and took their next available appointment that Thursday. Using the clinic would minimize my time off work, and having endured this condition for weeks already, another few days didn't seem important.

But Tuesday morning I awoke in somewhat greater discomfort. Not only did my calf feel inflated and achy, but my hands felt puffy too. As I got in the car to drive to work I started to worry a little.

Among my friends are at least two who have had strokes. The results aren't pretty and even partial recovery takes a long time. I really didn't know enough to say whether my symptoms could lead to a stroke (they don't), but it reminded me that some of these conditions can be completely mitigated if caught early enough, and can be life-changing or fatal if not.

I started to worry a little more. Before I got on the main road I was calling my regular doctor.

His response was pretty impressive -- I left a message describing my symptoms and within five minutes his office called back and asked me to come in right away. They are on my way to work so I was there in about ten minutes.

This is where things could have gone awry with a less thorough doctor. My physical symptoms were not very impressive: no discoloration and only a small amount of swelling. He thought it was probably just a strain injury, but ordered tests to make sure: one of them was an ultrasound scan for DVT.

This is also where things could have gone awry with a less motivated patient. I had two weeks before my followup appointment and therefore could easily have waited to schedule my tests. But I didn't. As soon as I left the doctor's office I called the hospital vascular lab to schedule the ultrasound. I asked if they had something "today" and they fit me in.

The Ultrasound Test

Debbie, the attractive and friendly ultrasound technician, does DVT scans all the time.She joked about there being a "special on DVT scans" that day -- I guess she'd had a bunch of them. At the hands of an expert they are pretty fast, and mostly negative.

Trousers off, I couldn't help warming at her touch. But of course she is all business. Starting with the ultrasound probe at my crotch, she scanned down my left femural vein, explaining as she did that we were viewing a cross section. Most of us have seen neo-natal sonograms of unborn children. This test is similar, but also employs Doppler sonography to measure the velocity of blood moving through the vein.

It was looking fine until she got to my knee, where she immediately said, "Oh, there's a clot." All I could see on the ultrasound was a shadow, but Debbie showed how it was really an obstruction inside my vein. Besides measuring its size, she also employed the scanner to measure the degree of obstruction by squeezing the vein lower in my calf and checking the change in blood flow.

I guess most patients react with fear or horror, but my own reaction was relief: now I knew why my leg was bothering me. My doubt was replaced by certainty: there was no longer an unknown foe, but something that could be named and treated.

But I have to admit that I was surprised how quickly things moved from there. Debbie called my doctor and told him of her findings. They put me on the line: he wanted me to go to the emergency room where they would start treatment immediately with "blood thinners".

I was about to become a patient.

Thursday, February 25, 2010

Introducing Time-Light-Distance: My New Photo Blog

I've been keeping Chip's blog for a few years now and have been finding it increasingly enjoyable.  But while it does reflect my diverse interests, the lack of any coherent theme must make it difficult to follow for anyone interested in a specific topic.

So lately, as I've been posting more about photography, I have been thinking about creating a separate, specialized blog as the new home for all my photo-related posts.  I was still thinking about it when, a couple of weeks ago, Trey Ratcliff (StuckInCustoms.com) challenged his readers to consider trying out SmugMug.  Well, I'd been thinking about that too.

So I decided to do both at the same time.

The result is my new blog and photo/video sharing site Time - Light - Distance.  As much as possible I am trying to integrate SmugMug with Blogger to offer a better photo and video sharing experience, as well as providing a soapbox where I can discuss my work.

This isn't the end of Chip's Blog, but all my new photography and video posts will be going to TLD.  Over time I also plan to migrate the older posts as well. For anyone who finds the things I'm doing in these areas helpful or interesting, I hope TLD will be easier to follow.  For myself, I hope that the sharper focus will make a difference in the quality and quantity of my work.

Saturday, February 13, 2010

Enhancing An Image Sequence With PhotoShop Filters

I've been working on an article about incorporating PhotoShop filters into my time-lapse workflow, and decided to post this tutorial separately.  Note that this requires the Extended versions of PhotoShop.

Two Things I Didn't Know About PhotoShop

I need efficient methods to enhance the images in my time-lapse projects.  For some projects I want to use PhotoShop and the question is how best to apply it to a large number of images.  PhotoShop provides  automation, batch tools, and a scripting interface, so there are lots of ways to do this.  But my first efforts suggested that it might be too slow.

As I investigated that further, I learned two new things about PhotoShop (Extended):
  1. PhotoShop supports image sequences.  I should be able to load a sequence into PS and apply powerful photographic filters such as Topaz to each frame automatically without additional scripting.
  2. PhotoShop supports video.  You can indeed set up filters and apply them to the entire video. 

You need the Extended version of PhotoShop for either of these.

The rest of this article is a step-by-step guide to filtering each frame of an image sequence.  I'll discuss the workflow considerations in a separate article.

Filtering an Image Sequence, Step-By-Step

I'm using Photoshop CS4 Extended on Windows Vista.  The procedure is essentially the same on Mac.

1. Start PhotoShop and from the menu choose File::Open.  Navigate to the folder containing your image sequence.

2. Select the first file in your sequence and select the "Image Sequence" button.  Then click "Open".

3. Choose a frame rate -- I happen to be using 60 for this project -- and click "OK".  PhotoShop takes about 10 seconds to open my 1,139 frame sample project.

4. I like to see the Animation Timeline, though it really isn't needed for what we're doing here.  If you don't see it, click on the workspace dropdown at the top right and switch to the "Video" workspace.

5. In order to apply filters to all frames in the sequence, you need to first convert it to a "Smart Object".  You can use the main menu Layer::Smart Objects::Convert to Smart Object, or just right click in the Layer panel and select "Convert to Smart Object".

6. Now apply your filter to the layer.  In this example I'm using Topaz Adjust 3 from Topaz Labs.

7. Each filter will be different, so I won't go through the Topaz dialog here.  By the way, Trey Ratcliff has a great review of Topaz Adjust on his blog, StuckInCustoms, and provides a discount code for purchase.

8. When you finish the filter dialog, PhotoShop works for a while and shows you the result on the selected frame.  Notice the new "Smart Filters" section appearing in your layers panel.

9. Now we're going to save our sequence as a new set of images with the filtering applied.  In the main menu choose "File::Export::Render Video".  No, we are not actually going to render video, and yes, it is quite confusing.

10. You have many choices here. I'll explain each of the ones that we're going to use right now.  The numbers in the screen shot are keyed to each item in the explanation.

  • Item 1: Location Name.  This is where you will set the "prefix name" of your image sequence filenames.  My screen shot is a bad example!  The prefix name of "Untitled-1" results in an image sequence with names like Untitled-10001.jpg, Untitled-10002.jpg and so forth.
  • Item 2: Select Folder and Create New Subfolder. You have choices; I leave "Select Folder" set to my "project" folder and click "Create New Subfolder" so that all my images will go into a new subfolder.  This is faster than using Select Folder to create a new folder.
  • Item 3: File Options. Select "Image Sequence", not "QuickTime Export".  You are welcome to experiment for yourself, but I've had nothing but problems attempting to perform the filter rendering and video rendering in the same step.
  • Item 4: Details about the exported files.  Keep the original image size ("Size: Document Size"), export "All frames" rather than a subset, and render them at same frame rate you started with ("Document Frame Rate").
  • Item 5: JPEG Settings. Click the "Settings" button to popup the JPEG Options dialog.  Set the quality level however suits you -- if you check the "preview" box it will estimate the file size for you.

11. Click "OK" on the JPEG Options dialog and then click the "Render" button.  After 10-20 seconds you should see an "Exporting Video" progress bar.  Now find something else to do, because it is s-l-o-w.  My machine, with these settings, will render at 12-13 frames per minute.  For this example, the sequence consists of 1,139 frames and I expect it to take about 90 minutes.

You can also watch its progress by looking in the output directory and watching the files appear.  I find this more soothing than the progress bar.

When PhotoShop is finished, you have a new image sequence that has been filtered.  This result is the same as applying a batch operation to all the files, but in my experience it is much faster.

I'll be discussing this technique in the workflow article, coming soon.

Saturday, February 06, 2010

Setting Up a Project at "Google Code"

Last weekend I set up my seq-to-qt project at Google Code. Here's a short tutorial.
In the past few years Google has been promoting their own infrastructure vision for open source software projects. It's called "Project Hosting on Google Code".

For a long time now we've had SourceForge and it is still the leading home for open source software projects.  SourceForge is a great resource -- why Google Code?

"Google Code" itself was born as a way to host Google's own APIs and open source projects -- Google's channel to the developer community.  It made sense to implement a flexible infrastructure that others could use as well, hence "Project Hosting".  Offering such a service is consistent with the company's other hosted services, like Sites and Blogger -- you can read their mission statement if you like.

Getting Started

Here's how to set up a new project using Project Hosting.
  1. Open Google's "Getting Started" page.  Keep it open in one browser tab or window so you can refer to it while performing the various actions in another window.
  2. Visit "Create Project" and fill out the form shown at right.  The most important thing is the project name -- you can change anything else later, including your choice of license and version control system.
  3. Click the "Create project" button at the bottom.
You've now created your project at Google Code and you'll see your project's home page for the first time.  The URL in this case is http://code.google.com/p/my-cool-project/

To change anything except the project name visit the "Administer" tab.  That's also where you can set up your project to use tools like Google Analytics and Google Groups.

Posting Your Code

One of the first things you'll want to do is to actually post your source code and perhaps compiled binaries.  Even if you're using a version control system (and you should), open source projects typically also post compressed archives -- bundles of files that a user can easily download.  A common example would be a zip file.

Compressed archives are produced by many different programs.  Some of the most well-known are WinZip (Windows), gzip (Unix/Linux) and Stuffit (Mac).  Your operating system may even have the capability built-in, like Windows Vista (select files, right-click, "Send to:Compressed (zipped) Folder").

Let's step through this.
  1. Create your archive.  The name should be descriptive and include a date or version number, like "my-cool-project_2010-02-06.zip"
  2. Click on your project "Downloads" tab.  A page opens like the one below:

  3. Click on "New download" to open the download dialog:

  4. Enter a short summary of what's in your file, some labels that you think will help others find it, and use the "Choose File" button to pick the file on your computer.  Finally click "Submit file".

Now your file is uploaded and appears in the list under the Downloads tab, where anyone can get it.

Next Steps

Open Source software development is not just about posting your code for all to use. It's about enabling communities of developer/users to share their improvements. Doing this successfully at any kind of scale requires a revision control system. So your next step is probably to set up your source code repository using either Mercurial or Subversion.

People who are interested in your project need to communicate, not just with you, but with each other. The "Google Groups" facility provides more features than a conventional mail list and can be linked automatically to your project page.

References

Monday, February 01, 2010

seq_to_qt: Python Conversion of Still Image Sequences to QuickTime Movies on Windows

Update: I've created a project for seq_to_qt at code.google.com.  This makes it easier for me to manage updates to the code.

In several recent posts (1, 2, 3) I've discussed my progress toward automating the workflow of creating time-lapse movies from still image sequences on Windows.  I used JScript+WSH for the original scripts because that's how the samples I started with were written.  But I've now rewritten the system in Python and extended it to provide "seamless" automation from end to end of the QuickTime part of my process.

What It Does

seq_to_qt provides a set of functions that I am finding useful for automating my workflow in producing timelapse movies.  My camera software saves images in separate folders for each day and numbers the images sequentially.  That's where seq_to_qt enters the picture.  I can give it a simple list of folders that need to processed and it will take care of everything else involved in generating a QuickTime movie for each sequence.

Key Benefits

  • Saves me time, errors, and drudgery.  Fire it off once to handle multiple image sequences and leave for the day (or weekend).
  • Splits long image sequences into chunks, if necessary, so that QuickTime doesn't choke.
  • The sequence splitter itself is very general and could be used to divide any sequence of numbered files.
  • Flexible Python implementation is relatively easy to extend and modify.

Getting Started

Download and install Apple QuickTime.  You will need to pay for the "Pro" license.
Download and install compatible versions of Python and PyWin32.  I have tested seq_to_qt with Python 2.6.
Download my Python files seq_to_qt (links to download page at the project site) and put them in a convenient place.

Running The Scripts

Python is flexible and there are a lot of things you can do.  But here is a simple way to get started.

My working folders are organized like this:
Project
    scripts  (this is where I put seq_to_qt and any others)
    seq1 (each set of image sequence files is in its own folder)
       XXX_0001.jpg  (numbered sequentially with a common prefix)
       XXX_0002.jpg
       ...
    seq2
    seq3
    ...
You don't have to set yours up the same way, but I thought it might help to see how I'm using it.
  1. Create a "test" sequence with no more than about 200 frames. To do this, create a new folder in your Project folder and there copy the first 1-200 frames of your favorite sequence.
  2. Make a copy of seq_batch_convert.py and edit it to provide the file paths for your image sequences.  If you want to see how the chunk splitting works, set the chunk_size to something small, like 50.
  3. Now run your script!  From a command window, your command will look something like this:
    c:\Python26\python scripts\seq_batch_convert.py
  4. And if all goes well a QuickTime movie will be produced.
The output of the scripts is quite verbose, so you should get a good view into what is being done.

Future Work

I want to extend the automation tools to include image processing prior to movie production.  Also, I may want to automate more of the production work that takes place in my video editor after the QuickTime portion is done.

References

Source Code

You can download a zip file of the seq_to_qt package.  Or just copy and paste it from here.
  • seq_to_qt.py is the module that contains all the functionality, and is used by the other files.  The other files are intended to be used as commands.
  • seq_batch_convert.py -- A simple command file used to enter your data and initiate the complete conversion.  Copy this file and edit it with your own image sequence information, then run it.  
  • qt_movie_from_stills.py -- If you wish, you can use this for the conversion of a single sequence, without splitting.  I would normally use seq_batch_convert.py instead.
  • make_seq_chunks.py -- Does only the chunk splitting on a sequence, no conversion, and therefore no dependence on QuickTime. 
Files have been syntax-highlighted using Pygments.

seq_to_qt.py


#! /usr/bin/env python
"""
Convert one or more still image sequences into QuickTime movies.

Author: Chip Chapin 
For More Info: http://cchapin.blogspot.com

You must have the PyWin32 COM interface installed,
see http://sourceforge.net/projects/pywin32/

Acknowledgements:
  The QuickTime interface was originally written as a JScript+WSH
  script, which I based on a 2006 blog post by Luc-Eric Rousseau
  (XSIBlog http://www.xsi-blog.com/archives/103).  Rousseau's script
  was in turn based on sample code by John Cromie, author of the
  book "QuickTime for .NET and COM Developers" (Elsevier 2006,
  http://www.skylark.ie/qt4.net/samplecode.asp).

Latest Update: 2010-01-31

TODO: Some sort of progress indicator during the initial rendering stage.
TODO: Better solution to the QT_PLAYER_DELAY hack.
"""
import datetime
import math
import os
import re
import shutil
import sys
import tempfile
import time
import win32com.client

CODEC_INFO_FILENAME = "C:\\qtMovieFromStillsCodecInfo.xml"
DEFAULT_CHUNK_FILES=7000
DEFAULT_FRAME_RATE = "60"

# HACK: Delay (sec) while the QuickTime player initializes.
QT_PLAYER_DELAY = 7.0

def abort(message=None):
    """Print error message and exit."""
    if message:
        print "ERROR: ", message
    sys.exit(1)

def usage(message=None):
    """Print usage message and exit."""
    if message:
        print message
    sys.exit(2)

def check_file_folder(fpath):
    """Returns true if the directory of fpath exists and is writable."""
    fdir = os.path.dirname(fpath)
    try:
        f = tempfile.TemporaryFile("w", dir=fdir)
    except OSError:
        return False
    f.close()
    return True

def file_exists(fpath):
    """Returns true if file fpath exists and is readable."""
    try:
        f = open(fpath, "r")
    except IOError:
        return False
    f.close()
    return True

def uniqueify(fpath):
    """Check to see if the file exists.  If so, uniqueify the filename."""
    upath = fpath
    count = 0
    while file_exists(upath):
        count = count + 1
        (root, ext) = os.path.splitext(fpath)
        upath = "%s-%02d%s" % (root, count, ext)
    if count:
        print "WARN: You already have a file '%s'" % fpath
        print "   Saving as '%s' instead." % upath
    return upath

def create_new_movie_from_images(sourcePath, frameRate, qtControl):
    """Create the movie from the still image sequence."""
    print "Creating new movie from still sequence '" + sourcePath + "'..."
    try:
        qtControl.CreateNewMovieFromImages(sourcePath,
                                           frameRate,
                                           True) # rate is in frames per second
    except:
        print "ERROR creating movie "
        #+ e.number +
        #           " (" + (e.number>>16 & 0x1FFF) +
        #           "-" + (e.number & 0xffff) + ")");
        #WScript.Echo(e.description);
        # TODO: Find more reliable error reporting.
        # The following doesn't work if the QTControl object is gone.
        print "QuickTime error " + qtControl.ErrorCode
        qte = qtControl.QuickTime.Error
        print "  " + qte.ErrorCode + ", " + qte.Description
        print "  " + qte.SourceReference
        raise

    qtMovie = qtControl.Movie;
    if not qtMovie:
        abort("No movie created (" + qtControl.ErrorCode + ")")
    duration = qtMovie.Duration;
    if (duration == 0):
        # This test isn't as helpful as I thought it would be.  I thought it
        # would catch the case where QT does not have a valid input file, but in
        # that case it seems to create a two-second empty movie 
        # (ie. duration = 20*framerate).
        abort("Movie has duration 0.")

    # Duration is the number of frames * 10.
    print "Created new movie, duration %d" % duration

def get_quicktime_exporter(qtControl):
    """Set up the QuickTime movie exporter."""
    qt = qtControl.QuickTime
    if (qt.Exporters.Count == 0):
        # Only add an exporter if needed.
        qt.Exporters.Add()
        print "Adding new Exporter."
    else:
        print "Using existing Exporter."
    qtExporter = qt.Exporters(1)
    if not qtExporter:
        abort("Unable to get Exporter.")
    qtExporter.TypeName = "QuickTime Movie"
    qtExporter.SetDataSource(qtControl.Movie)

    if file_exists(CODEC_INFO_FILENAME):
        print "Reading codec config from '" + CODEC_INFO_FILENAME + "'"
        CodecFileInfo = open(CODEC_INFO_FILENAME, "r")
        xmlCodecInfoText = CodecFileInfo.read()
        # Cause the exporter to be reconfigured.
        # http://developer.apple.com/technotes/tn2006/tn2120.html
        tempSettings = qtExporter.Settings
        tempSettings.XML = xmlCodecInfoText
        qtExporter.Settings = tempSettings
    else:
        # Use the Settings dialog box, then save the results.
        qtExporter.ShowSettingsDialog()
        xmlCodecInfoText = qtExporter.Settings.XML
        try:
            CodecFileInfo = open(CODEC_INFO_FILENAME, "w")
            CodecFileInfo.write(xmlCodecInfoText)
            CodecFileInfo.close()
        except IOError:
            print ("Warning: failed to save codec info to '" +
                   CODEC_INFO_FILENAME + "'")
            print "continuing ..."
    return qtExporter

def export_movie(qtExporter, destPath):
    """Export the movie."""
    print "Exporting ..."
    try:
        qtExporter.DestinationFileName = destPath
        qtExporter.ShowProgressDialog = True
        # Uncomment this line if you want the export dialog box to appear.
        # qtExporter.ShowExportDialog();
        qtExporter.BeginExport()  # This can take a l-o-n-g time.
        print "Exported to '%s'" % destPath
    except:
        print "ERROR exporting '%s'" % destPath
        #print "ERROR " + (e.number>>16 & 0x1FFF) +
        #      "-" + (e.number & 0xffff) + 
        #      " exporting '" + destPath + "'"
        #print e.description
        #WScript.Echo(JSON.stringify(e, null, 2));
        #qte = qt.Error;
        #print "QuickTime Error %d, %s" % (qte.ErrorCode, qte.Description)
        #WScript.Echo(JSON.stringify(qte, null, 2));
        raise

def qt_movie_from_stills(sourcePath, destPath, frameRate):
    """Create and export a QuickTime movie from a sequence of images."""
    # Launch QuickTime Player Application
    qtPlayerApp = win32com.client.Dispatch(
                    "QuickTimePlayerLib.QuickTimePlayerApp")
    time.sleep(QT_PLAYER_DELAY);  # Give it time to launch.
    if not qtPlayerApp:
        abort("Failed to launch QuickTime Player App.")

    # Get the QuickTime player and its associated controller.
    # NOTE: The script will abort here if the player hasn't had time
    # to initialize.  It should work if you run it again, or you can increase
    # QT_PLAYER_DELAY.
    qtPlayer = qtPlayerApp.Players(1);
    if not qtPlayer:
        abort("Failed to get QuickTime Player.");
    print "Got player '" + qtPlayer.Caption + "'"
    qtControl = qtPlayer.QTControl
    
    create_new_movie_from_images(sourcePath, frameRate, qtControl)
    qtExporter = get_quicktime_exporter(qtControl)
    export_movie(qtExporter, destPath)
    # Closing the player causes failures for subsequent invocations.
    # qtPlayer.Close();

def seq_file_list(a_seq_file):
    """Return the list of file names in the sequence."""
    # Extract the leading invariant part of the sequence filename.
    # For example, if the name is Fooo-0001.jpg, the invariant is "Fooo-".
    # Use non-greedy match so the numbers are kept out.
    (work_folder, sequence_proto) = os.path.split(a_seq_file)
    sm = re.match("(.*?)[0-9]+\.", sequence_proto)
    if not sm:
        usage(("'%s' doesn't look like the start of a sequence.\n"
              % sequence_proto) +
"Sequences consist of sequentially numbered file names like\n" +
"   Grog-001.jpg, Grog-002.jpg, Grog-...\n" +
"\n" +
"  sequenceExample -- Path to any file in the sequence.\n" +
"  maxSize -- Maximum number of sequence files to put in a folder.");
        
    # Construct a regexp for matching sequence file names.
    sq_name = sm.group(1)
    sq_re = re.compile("^" + sq_name + "[0-9]+\.");

    # Make a list of files that match the sq_re pattern.  "fnmatch" patterns
    # are not powerful enough to be reliable so we don't use glob.
    sq_files = []
    dir = os.listdir(work_folder)
    dir.sort()
    for f in dir:
      if sq_re.match(f):
        sq_files.append(f)
    return (sq_files, sq_name)

def split_file_list(a_seq_file, sq_files, sq_name, max_size):
    """Split the sequence files into successive split folders.
    Returns the list of split folder names.
    """
    work_folder = os.path.dirname(a_seq_file)

    sq_count = len(sq_files)
    num_splits = math.ceil(sq_count / max_size)
    print ("Splitting file sequence '%s' into %d parts."
           % (sq_name, num_splits))
    parent_folder = os.path.dirname(work_folder)
    file_count = 0
    next_file_in_split = 1
    current_split = 0
    splits = []
    for fname in sq_files:
        if (file_count == 0 or next_file_in_split > max_size):
            current_split = current_split + 1
            next_file_in_split = 1
            split_folder = os.path.join(parent_folder,
                                        ("%s-%d" % (work_folder, current_split)))
            print "New split folder '%s' (%d)" % (split_folder, file_count)
            try:
                os.mkdir(split_folder, 0755)
            except EnvironmentError as (errno, strerror):
                print "ERROR: Failed to create split directory '%s'" % split_folder
                print "[Error %d] %s" % (errno, strerror)
                abort()
            splits.append((split_folder, fname))
            
        # Move the file from work_folder to split_folder
        shutil.move(os.path.join(work_folder, fname),
                    os.path.join(split_folder, fname))
        file_count = file_count + 1
        next_file_in_split = next_file_in_split + 1

    print ("Split %d files in '%s' into %d folders."
           %(file_count, work_folder, current_split))
    return splits

def make_seq_chunks(a_seq_file, max_size):
    """Split a file sequence into folders of no more than max_size files each.
    Returns a list of (folder, starting_file) duples.

    Sequences have file names that end with a sequence number,
    like (Foo-001.jpg, Foo-002.jpg, ...).
    """
    (sq_files, sq_name) = seq_file_list(a_seq_file)
    sq_count = len(sq_files)
    print "Sequence '%s' contains %d members." % (a_seq_file, sq_count)    
    if sq_count <= max_size:
        print "No need to split."
        if (sq_count == 0):
            return []
        else:
            return [os.path.split(a_seq_file)]
    else:
        return split_file_list(a_seq_file, sq_files, sq_name, max_size)

def seq_name_from_filename(fname):
    """Extract the 'sequence name' from a representative filename."""
    sm = re.match("(.*?)[-_]?[0-9]+\.", os.path.basename(fname))
    if not sm:
        print ("'%s' doesn't look like the start of a sequence."
                  % fname)
        return None
    return sm.group(1)

def convert_sequences(base_dir, seqs,
                      frame_rate=DEFAULT_FRAME_RATE,
                      chunk_size=DEFAULT_CHUNK_FILES):
    """Generates QuickTime movies for a set of image sequences.

    base_dir: Full path to the common parent directory.
    seqs: List of duples describing image sequences.
        Each duple contains the FOLDER NAME of the sequence
        and the STARTING IMAGE file name: (folder, file)
        Names are unqualified, i.e. relative to their parent directories.
    frameRate: Numeric frame rate in frames per second.
    chunk_size: Maximum number of frames to be processed in
        a single movie.
    """
    for (sq_dir, sq_file) in seqs:
        src = "%s\\%s\\%s" % (base_dir, sq_dir, sq_file)
        if not file_exists(src):
            print "WARN: '%s' is not readable.  Skipping." % src
            continue
        # Possibly split the image sequence into chunks.
        splits = make_seq_chunks(src, chunk_size)
        # Iterate over the chunks.
        for (split_folder, split_file) in splits:
            sq_name = seq_name_from_filename(sq_file)
            if (sq_name):
                dst = ("%s\\%s_%s.mov" %
                       (base_dir, sq_name, os.path.basename(split_folder)))
                dst = uniqueify(dst)
                print time.strftime("%Y-%m-%d %X  Converting:") 
                print ("    Folder %s\n    Start %s\n    Dest %s"
                       % (split_folder, split_file, dst))
                qt_movie_from_stills(src, dst, frame_rate)
                print time.strftime("Complete at %Y-%m-%d %X")

def main(argv):
    """Main Program"""
    usage("This is the seq_to_qt module.\n" +
          "For batch conversion, edit 'seq_batch_convert.py'.  It calls the functions here.")

if __name__ == "__main__":
    main(sys.argv)


seq_batch_convert.py


#! /usr/bin/env python
"""
Convert a batch of image file sequences to QuickTime movies.

Edit this file and list your sequences below.

Author: Chip Chapin 
For More Info: http://cchapin.blogspot.com
"""
import seq_to_qt

DEFAULT_BASE_PATH = "L:\\Graphics\\Images\\SX110\\MTV-TL01"
DEFAULT_SEQUENCE_FILE = "MTV-TL01_0001.JPG"
DEFAULT_CHUNK_SIZE = 7000

def usage():
    print "Usage: seq_batch_convert.py "
    exit(2)
    
def main(argv):
    """Main Program"""
    # Step 1: Base directory.  This is the parent of your
    # sequence directories.
    base_path = DEFAULT_BASE_PATH
    
    # Step 2: List your image sequences.
    # Each sequence is a duple containing the
    # FOLDER NAME and the STARTING IMAGE of the sequence
    # Example:
    #sequences =  [("Test01-1", "MTV-TL01_0001.JPG"),
    #              ("Test01-2", "MTV-TL01_0076.JPG"),
    #              ("Test01-3", "MTV-TL01_0151.JPG")]
    sequences =  [("2009-12-15b", "MTV-TL01_7001.JPG"),
                  ("2009-12-19b", "MTV-TL01_7001.JPG"),
                  ("2009-12-22b", "MTV-TL01_7001.JPG"),
                  ("2009-12-23b", "MTV-TL01_7001.JPG"),
                  ("2009-12-24b", "MTV-TL01_7001.JPG")]

    # Step 3: How many frames per second should your movie run at?
    frame_rate = 60

    # Step 4: Movie chunk size-- how large a movie (how many frames)
    # can QuickTime process on your computer?  Try the default.
    # If QuickTime aborts during the export phase, make it smaller.
    #chunk_size = 75  # Low value for testing. Normally use DEFAULT_CHUNK_SIZE
    chunk_size = DEFAULT_CHUNK_SIZE
    
    # Now do the conversion.
    seq_to_qt.convert_sequences(base_path,
                                sequences,
                                frame_rate,
                                chunk_size)
    
if __name__ == "__main__":
    import sys
    main(sys.argv)


qt_movie_from_stills.py


#! /usr/bin/env python
"""
Create a QuickTime movie from a sequence of still images on Windows.

Usage: python qt_movie_from_stills.py sourcepath destpath [framerate]

Use FULLY-QUALIFIED Windows file paths. Relative paths don't work well
inside QuickTime -- it is likely to fail with no error report from QT.

You must have the PyWin32 COM interface installed,
see http://sourceforge.net/projects/pywin32/

Author: Chip Chapin 
For More Info: http://cchapin.blogspot.com
Acknowledgements:
  I originally wrote this as a JScript+WSH script based on a post by
  Luc-Eric Rousseau (XSIBlog 2006, http://www.xsi-blog.com/archives/103).
  His was in turn based on sample code by John Cromie that accompanied
  his book "QuickTime for .NET and COM Developers"
  (Elsevier 2006, http://www.skylark.ie/qt4.net/samplecode.asp).

Last Update: 2010-01-31

TODO: Some sort of progress indicator during the initial rendering stage.
"""
import seq_to_qt
import os

def usage(message=None):
    """Print usage message and exit."""
    if message:
        print message
    print "Usage: qt_movie_from_stills.py sourcepath destpath [framerate]"
    print "Hint: Avoid relative paths, use fully-qualified Windows paths"
    print "(e.g. C:\\a\\b\\c.jpg)."
    sys.exit(2)

def main(argv):
    """Main Program"""
    if (len(argv) < 3):
        usage()
    sourcePath = argv[1]
    destPath = argv[2]
    if (len(argv) >= 4):
        frameRate = argv[3]
    else:
        frameRate = seq_to_qt.DEFAULT_FRAME_RATE
    print "FrameRate: " + frameRate

    if not seq_to_qt.file_exists(sourcePath):
        usage("Missing source file '" + sourcePath +"'")
    if seq_to_qt.file_exists(destPath):
        # .mov files take a long time to create.  Avoid
        # overwriting one by mistake.
        usage("Will not overwrite destination file '" + destPath + "'")
    if not seq_to_qt.check_file_folder(destPath):
        usage("Unusable destination file path '" + destPath +"'")
    seq_to_qt.qt_movie_from_stills(sourcePath, destPath, frameRate)

if __name__ == "__main__":
    import sys
    main(sys.argv)


make_seq_chunks.py


#! /usr/bin/env python
"""
Author: Chip Chapin 
Last Updated: 2010-01-31

Split a sequence of files into smaller chunks.
I use this for dividing still image sequences into pieces
that QuickTime can handle.

See http://cchapin.blogspot.com/
"""
import seq_to_qt
import os

def usage(message=None):
    """Print usage message and exit."""
    if message:
        print message
    print "Usage: make_seq_chunks.py sequenceFile maxSize"
    print (
"\nSequences have sequentially numbered file names like\n" +
"    Foo-001.jpg, Foo-002.jpg...\n" +
"sequenceFile -- Path to any file in the sequence.\n" +
"maxSize -- Maximum number of sequence files to put in a folder.");
    sys.exit(2)

def main(argv):
    """Main Program"""
    if (len(argv) != 3):
        usage("Expected two arguments")  
    a_seq_file = os.path.abspath(argv[1])
    max_size = float(argv[2])
    
    if not os.path.isfile(a_seq_file):
        usage("'%s' is not a file." % a_seq_file)
    seq_to_qt.make_seq_chunks(a_seq_file, max_size)

if __name__ == "__main__":
    import sys
    main(sys.argv)