I recently analyzed one of our large Actionscript 2 apps for the possibility of converting it over to Actionscript 3. Doing this is a little bit behind the times, since AS3 came out years ago, but this app is huge, so procrastination set in. The application in question has over 100,000 lines of code, not including comments or other non compiling lines. In short, it is fairly monstrous in size for an AS2 application. The goal was to see how hard it would be to migrate it over to AS3 so that it would compile and function as expected.

For information on why you would want to move to AS3, look at summaries like this. Also, Adobe’s migration site is a good place to start.

Since this app had many output artifacts (swfs and what not), I chose one of them, changed the corresponding fla file’s publish settings to Actionscript 3 and tried to compile. This lead to a lengthy and sometimes frustrating cycle of:

  • Attempt publish, resulting in many compiler errors
  • Fix compilation errors
  • Repeat

After many cycles, I eventually wound up with no compilation errors, and discovered a new, albeit familiar cycle of:

  • Attempt execution of swf, resulting in run-time errors
  • Fix cause of run-time error
  • Repeat

After many cycles of this, I had a working swf. Along the way, I took notes as to the types of changes I was making so that I could later look at the entire code base to estimate how much change was potentially there. Ultimately, it was decided that we would likely not be putting the effort to convert the entire app at this time, but the list of changes may help others who are going through a similar migration.

This list is not complete, for sure. It’s also not guaranteed that I didn’t mess up in taking my notes, since the whole process was fairly frustrating. Your mileage may vary and I offer no warranty of any kind :)

Basic Stuff

These changes applied to all classes that existed in the app.

  • Add package declarations, and make sure classes are public. For example:
            package foo.bar {
              import ...;
              public class Photny extends Blork {
                ...
              }
            }
  • Make sure methods and properties called by external package classes are public
  • private variables in parent classes accessed in children must be marked protected in order to be accessed in the child class.
  • Use the override attribute when overriding a parent class method:
    	override function showMessage() { ... }
  • Include import statements where missing (AS3 requires and will complain when it’s not present), for example:
    • MovieClip: import flash.display.MovieClip;
    • TextFormat: import flash.text.TextFormat;
  • Change mx.controls.* to fl.controls.*
  • Replace Void declarations with void:
    	function set doSomething():Void { ... }

    becomes

    	function set doSomething():void { ... }
  • Functions that have parameters but are called without them, for instance:
        	private function processMode(pCleanup:Boolean) { ... }
            //  and is called like this:
           	processMode();

    This is no longer allowed. Use function defaults in AS3:

        	private function processMode(pCleanup:Boolean=true) { ... }
            //  now you can call it like this:
           	processMode();
  • Default values for objects have changed
    • Number: default values for Number class is now NaN, so beware of this:
                var num:Number;
                if((num==undefined) || (num==null)) { ... }// this will not be true
    • Boolean: default value is false, (not null)
    • int, uint: default is 0
  • Look for use of arguments.length or arguments[] – use the ...rest construct instead

Forms

This app happened to make limited use of Flash MX forms (mx.screens.Form class). Forms support does not seem to be carried over into Actionscript 3 at all, so the use of these had to be refactored out entirely. I simulated forms, placing various input elements (TextInputs, Buttons, etc) into separate layers and then grouping them together so I could make them visible and invisible in the same way that the forms were displayed.

XML

The XML class is different,now based on E4X. For example, look for stuff like:

          xml.firstChild
          // or
          if (allNodes[u].nodeName == "address") { ... }
          // or
          var outputNodes = allNodes[u].childNodes;

and get rid of it in favor of E4X constructs, which allow you to access things directly, like:

          xml..address[0].street_address[0]
          ...
          myFunc( xml..address[0].street_address[0].@type );
          ...
          for each(var address:XML in xml..address) {
              var childNodes:XMLList = address.children();
              ...
          }

MovieClip differences

  • Replace calls to MovieClip properties, for instance:
    Old New
    _visible visible
    _x x
    _y y
    _width width
    _height height
  • The MovieClip.createEmptyMovieClip() is no longer used, given the new DisplayList architecture, so code like this:
            mc.createEmptyMovieClip("myChildMc", 1000);

    Has got to go in favor of something like this:

            var myChildMc:MovieClip = new MovieClip();
            addChild(myMc); // assuming you're a DisplayObject

    Further, if you go and address your child MC with code like this:

            mc.myChildMc

    You’ll have to refactor that too. There is no association outside of what the display list does (via addchild()). As a real hack, since MovieClip is a dynamic AS3 class, you could manually set the child to the parent by doing this after you create the child:

            mc.myChildMc = myChildMc;

    And then it should work. Another alternative is to add myChildMc with a name:

            myChildMc.name="child-mc";
            mc.addChild(myChildMc)

    Then get it later this way:

            // less efficient than mc.getChildAt(int) but it works.
            (mc.getChildByName("child-mc") as MovieClip)
  • Calls that set the depth to the highest possible, like this:
            stage.createEmptyMovieClip("upperMC", 1000);

    Cannot be done with AS3. You will need to make sure that the added DisplayObject has the highest possible index value via DisplayObjectContainer.setChildIndex(). So in other words, either add the thing you want on top last, or change it after you’ve added everything so that it has the highest index value.

  • Drawing related methods have been moved off of the MovieClip class:
    • MovieClip.clear() is no longer available, replace with MovieClip.graphics.clear()
    • MovieClip.lineTo(0,0) is no longer available, replace with MovieClip.graphics.lineTo(0, 0)
    • MovieClip.endFill() is no longer available, replace with MovieClip.graphics.endFill()
    • MovieClip.lineStyle(2,lnColor) is no longer available, replace with MovieClip.graphics.lineStyle(2,lnColor)
    • MovieClip.beginFill(color, fillAlpha) is no longer available, replace with MovieClip.graphics.beginFill(color, fillAlpha)
    • MovieClip.moveTo(50,25) is no longer available, replace with MovieClip.graphics.moveTo(50,25)
  • MovieClipLoader() calls are no longer allowed, so replace this type of stuff:
               var listenerObj = {};
               listenerObj.homeref = this;
               listenerObj.onLoadInit = Delegate.create(this, mcLoaded);
               listenerObj.onLoadError = Delegate.create(this, mcNotLoaded);
    	   loader.addListener(listenerObj);
               loader.loadClip(theUrl, _mainMC);

    with something like this:

               _loader = new Loader();
               _loader.contentLoaderInfo.addEventListener(Event.INIT, onMcLoaded);
               _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onMcIOError);
               _loader.contentLoaderInfo.addEventListener(HTTPStatusEvent.HTTP_STATUS, onMcStatus);
               // start a timer, if it triggers, we have our timeout condition
               _mcTimer = new Timer(20000, 1); // 20 seconds
               _mcTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onMcTimeout);
               _mcTimer.start();
               trace("about to load mc (" + theUrl + ")");
               _loader.load(new URLRequest(theUrl));
               ...
               // BEGIN: mc loading handlers
               private function onMcLoaded(pEvent:Event):void { /* turn off timer */ }
               private function onMcIOError(pEvent:IOErrorEvent):void { /* turn off timer */ }
               private function onMcStatus(pEvent:HTTPStatusEvent) { /* turn off timer, make sure we got 200 status */ }
               private function onMcTimeout(pEvent:TimerEvent):void { }
               // END: mc loading handlers
  • _root no longer exists the way it did in AS2. This is a topic unto itself. If you absolutely need to have something like the root MC, you could keep a reference to it in some type of global object and set it yourself.

Various Component differences

  • You can no longer create TextFields from MovieClip objects. So instead of stuff like this:
                mc.createTextField("myHeader",mc.getNextHighestDepth(), 60, 35, 200, 30);

    You should be doing something like this:

            var myHeader:TextField = new TextField();
            myHeader.x=60;
            myHeader.y=35;
            myHeader.width=200;
            myHeader.height=30;
            parentMC.addChild(myHeader);
  • Replace calls to TextField.setNewTextFormat(myFmt) with TextField.defaultTextFormat = myFmt
  • Replace calls to
            mc.createClassObject(CheckBox,"myCheck",mc.getNextHighestDepth(), {label:"Enable Logging"});

    with something like this:

            var myCheck:CheckBox = new CheckBox();
            myCheck.label = "Enable Logging";
            mc.addChild(myCheck);
  • Setting the targets to UIScrollBar objects has changed. Change this:
            myScroll.setScrollTarget(mc.someTextArea);

    with something like this:

            myScroll.scrollTarget = someTextArea;
  • TextField (vertical) scroll positions used to be controlled like this:
            _errorMesgBox.scroll = 0;

    but is now this:

            _errorMesgBox.scrollV = 0;
  • Setting html text on mx.controls.Labels is different, so change:
            label.html = true;
            label.text = "hello world";

    to this:

            label.htmlText = "hello world";

Nickels and Dimes

There are alot of smaller changes that were not very frequent, but nonetheless were allowed by AS2 but not AS3.

  • Remove nulls in function definitions. Change this:
    		someHandler.onFocusOut = function(null, mc) { ... }

    to this:

                    someHandler.onFocusOut = function(pUnused:Object, mc) { ... }
  • Operators ‘and’, ‘or’ are no longer valid. Replace with ‘&&’, ‘||’. Change this:
            if ( foo == null or foo == 0) { ... }

    to this:

            if ( foo == null || foo == 0 ) { ... }
  • The CSSStyleDeclartion don’t exist in AS3. Use the flash.text.StyleSheet class instead.
  • Delegates must be removed, so replace and refactor calls like this:
            myCheck.addEventListener("click", Delegate.create(this, toggleStuff));

    into something like this:

           myCheck.addEventListener(MouseEvent.CLICK, toggleStuff);

    then alter

            function toggleStuff() { ... }

    to:

            function toggleStuff(pEvent:MouseEvent) { ... }

    Be sure to examine the inside of the toggleStuff() function and make sure that we use pEvent.target when we need it, etc.

  • References to _global.whatever are no longer allowed. One solution is to use a homebrewed standin Global class, then, something like:
            _global.styles.helloStyle.styleName = "helloStyle";

    becomes:

           Global.global.styles.helloStyle = new mx.styles.CSSStyleDeclaration();
  • The setInterval() is different. What was previously something like this:
            _myTimer = setInterval(this, "checkStuff", 100);

    turns into this:

            import flash.utils.*;
            ...
            _myTimer = setInterval(checkStuff, 100);
  • There is no longer a Color class that can change the color of a MovieClip, so this type of thing cannot be done any longer:
    	var colr = new Color(mc);
    	colr.setRGB(0x000000);

    use this instead:

            import flash.geom.ColorTransform;
            ...
            var nuColor:ColorTransform = new ColorTransform();
            nuColor.color = 0x000000;
            mc.transform.colorTransform = nuColor;
  • Calls to System.capabilities.os were moved, depending on what you’re looking for, replace with:
             import flash.system.Capabilities;
             Capabilities.os...
  • Include import for setting system’s clipboard
              System.setClipboard(myTextArea.text);

    to:

              import flash.system.System;
              ...
              System.setClipboard(myTextArea.text);
  • Calls to fscommand(string, bool) require import, so replace with:
            import flash.system.fscommand;
            fscommand("foo", "true");
  • This now requires import:
            System.security.allowDomain(_theUrl);

    so include this:

            import flash.system.Security;
             ...
            Security.allowDomain(_theUrl);
  • the Key class is no longer available, so this:
    	var keyListener:Object = new Object();
    	keyListener.homeref    = this;
    	keyListener.onKeyDown = function() {
    		if (Key.isDown(Key.CONTROL) && Key.isDown(Key.SHIFT) &&(Key.getCode() == 54) ) {
    			trace("you pushed ctrl-shft-6" );
    			this.homeref.onDKeyPress();
    		}
    	};
    	Key.addListener(keyListener);

    Should be rewritten like this:

            import flash.ui.Keyboard;
            ...
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            ...
            private function onKeyDown(pEvent:KeyboardEvent):void {
              if(pEvent.controlKey && pEvent.shiftKey && Keyboard.NUMBER_6) {
              	trace("you pushed ctrl-shft-6" );
    		onDKeyPress();
              }
            }

And the list, I’m sure, goes well beyond this.  Good luck if you consider migrating such an app. You should carefully weigh the benefit of doing so. If you do, just remember that the code you wind up with will likely be far from ‘best practices’ AS3, but if successful you’ll wind up with a faster app due to the more efficient AS3 virtual machine, among other potential benefits.

I’m not a fan of conflicts, especially source control merge conflicts. Recently for a project, I had to merge some code into our main trunk in svn from a maintenance branch and ran into bunch of problems due to merge conflicts. Since it wasn’t my code that conflicted and there were alot of conflicts over many, long lines, looking at svn’s diff output made me a bit confused and dizzy. The time had come to start using a more graphical merge tool with svn.

I looked around and found several merge tools. Some were shareware, some free. I settled on trying Diffmerge: it was free, available on mac (and windows and linux) and seemed pretty feature rich.

I’ve always found merging a bit confusing, and to make matters worse, it’s not altogether straightforward as to how one gets SVN to use external merge tools. There’s some info in the manual, notably here and here

First, download diffmerge. Once you’ve downloaded the diskimage file, open it and you’ll see a DiffMerge.app directory. Drop that in your /Applications folder.

Next, you’ll need a little wrapper shell script to invoke DiffMerge from svn. I’m using this, and placing it in my home directory: ~/scripts/diffmerge/diffmerge-svnmerge.sh:

#!/bin/bash
DIFFMERGE_PATH=/Applications/DiffMerge/DiffMerge.app
DIFFMERGE_EXEC=${DIFFMERGE_PATH}/Contents/MacOS/DiffMerge
# svn will invoke this with a bunch of arguments.  These are:
# $1 - path to the file that is the original
# $2 - path to the file that's the incoming merge version
# $3 - path to the file that's the latest from trunk (current working copy)
# $4 - path to where svn expects the merged output to be written
${DIFFMERGE_EXEC} --nosplash -m -t1="Incoming Merge Changes"  -t2="Original (merged)" -t3="Current Working Copy changes" -r="$4" "$2" "$1" "$3"

As noted in the comments, svn will call this wrapper script with arguments. These are briefly described in a paragraph in here.

Now, we need to get subversion to call this script at the right time. To do so, we can either set the SVN_MERGE environment variable to point to the path to the script or edit our subversion config file. I’ll choose the latter, so open ~/.subversion/config and add a line under the [helpers] section like this:

merge-tool-cmd = <HOME>/scripts/diffmerge/diffmerge-svnmerge.sh

Make sure your path is correct, and you should probably make it absolute.  Also make sure your script has its executable script set by running:

chmod +x ~/scripts/diffmerge/diffmerge-svnmerge.sh

Now, we’re all set. Let’s run through a bit of a contrived example of how this might be used. I will use example addresses and locations for the svn urls.

Let’s assume we have an svn repository at a url like this : https://svn.example.org/svn/my-example-repo/trunk/test

We will add a new file to the test directory called test.txt with the following contents:

apple pie
orange sherbet
pear sorbet
banana pudding

We’ll add and commit this. Now let’s create a branch to work on this privately with the following command:

svn copy https://svn.example.org/svn/my-example-repo/trunk/test https://svn.example.org/svn/my-example-repo/branches/my_private_test_branch

We will now checkout and work on the branch, editing the branch’s test.txt in the branch so that it looks like this:

peaches and cream   #branch
apple pie
cherry              #branch
orange sherbet
kiwi freeze         #branch
pear sorbet
banana pudding

We’ve clearly marked the line changes with a #branch ‘comment’. Then, we commit this to the branch.

Now let’s say someone else has been changing the trunk version of the file, like so:

peach pie              #trunk
apple pie
orange sherbet
blueberry pancakes     #trunk
pear sorbet
strawberries and cream #trunk
banana pudding

they committed their changes to trunk. For clarity, I’ve marked the changed lines with a #trunk comment

Merge Conflict Time

It’s time to move the changes from the branch to the trunk. We go into our local working copy of the trunk, make sure we have no local changes and we’re up to date with what’s in trunk. From the root of our local copy, we run a merge, resulting in:

mymac:test user$ svn merge https://svn.example.org/svn/my-example-repo/branches/my_private_test_branch
Conflict discovered in 'test.txt'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options:

If we select diff-full, we get this:

Select: (p) postpone, (df) diff-full, (e) edit,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options: df
--- /var/folders/IL/ILlIoHEjED4wET70+ahtVE+++TM/-Tmp-/tempfile.3.tmp	Wed Oct  7 13:31:33 2009
+++ .svn/tmp/test.txt.tmp	Wed Oct  7 13:31:33 2009
@@ -1,4 +1,16 @@
+<<<<<<< .working +peach pie              #trunk +======= +peaches and cream   #branch +>>>>>>> .merge-right.r10787
 apple pie
+cherry              #branch
 orange sherbet
+<<<<<<< .working +blueberry pancakes     #trunk +======= +kiwi freeze         #branch +>>>>>>> .merge-right.r10787
 pear sorbet
+strawberries and cream #trunk
 banana pudding
Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options:

Which, even in our simple but contrived example, is a bit confusing and definetely an eyesore. It’s time to use our custom external merge software.

Use option l, which, due to our config file changes, will launch DiffMerge ('l' is not seen as an option, but it is one. Use 's' to see it listed). DiffMerge should launch and you should see something like this:

what was passed to diffmerge were three files:

  • The Original file from which both versions were derived from (ie, the file as it stood when the svn copy was made)
  • The file from the branch (as it is now).  This is the Incoming Merge changes.
  • The working copy, which is basically the latest copy from the trunk

Since svn could not automagically merge it, we’re passing it to DiffMerge. The report mentions that DiffMerge has automatically merged 2 changes, but 2 others could not be done automatically. Those are left for us to merge with the help of the DiffMerge display. Click OK and we get a screen that looks like the following:

The center column is marked ‘Original (merged)’. This is the base/original file from which the branch version and Current working copy (trunk) version are derived. However, since DiffMerge automatically made two merges on it’s own, it’s no longer exactly like the original. You can see that DiffMerge merged in the ‘cherry’ line from the branch copy and the ‘strawberries and cream’ from the working trunk copy.

However, since both the branch and the working copy have incoming changes to the same line, that is the line that we need to merge manually. There are two cases of this (2 conflicts):

For the conflict in the middle of the file (‘kiwi freeze’ vs. ‘blueberry pancakes’), let’s say we want both the changes from the right and the changes from the left to be in the merged (middle) version. So in this case, click on the line ‘kiwi freeze’, then either right click and select ‘insert this’ or press the ‘apply change from right’ button. That gets us this:

We also want the change from the right, so right click on the ‘blueberry pancakes’ line and select ‘Prepend this’.

Selecting that will prepend the ‘blueberry pancakes’ line to what we just moved. Now it looks like this:

That takes care of the first conflict, let’s move on to the other one (the one in the first line). You can see that line 1 in both the right and left have changes, and could thus not be automatically merged. (Notice how DiffMerge tells you what parts of the line match and what parts don’t by the colors, which is very helpful for long lines of configuration options where only one character or word changed). Let’s take the one from the right, so click on ‘peach pie’ and then hit the ‘apply change from right’ button. You’ll get this:

However, let’s change it in the final merge, from ‘peach pie’ to ‘peach cobbler’. To do so, click in the middle column, right after peach pie. you’ll get a text cursor. Delete ‘pie’ and make it ‘cobbler’. You now see this:

Perfect, now the middle column represents exactly how we want this merged.

Just to point out a feature, if we hit the ‘Reference View’ button in the bottom center, we get to see all three files as they were passed into DiffMerge from svn. It looks like this:

That can be a handy view, since it shows the state of the files prior to any automatic or manually done merge editing occured.

Clicking back to the Edit View, we’re now ready to save this (when we save, it saves what is in the middle column). Save it using the save button or File | Save. Quit out of DiffMerge and we’re back at the command prompt with another message like this:

Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options:

We know that we just manually merged this file, and since we saved it, we should be good to go, select 'r' for resolved and we get:

Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (mc) mine-conflict, (tc) theirs-conflict,
        (s) show all options: r
--- Merging r10785 through r10787 into '.':
U    test.txt
 U   .
mymac:test user$

The file as it now stands in the working copy is:

mymac:test user$ cat test.txt
peach cobbler              #trunk
apple pie
cherry              #branch
orange sherbet
blueberry pancakes     #trunk
kiwi freeze         #branch
pear sorbet
strawberries and cream #trunk
banana pudding
mymac:test user$

That’s exactly what we wanted, also indicated by the diff:

mymac:test user$ svn diff test.txt
Index: test.txt
===================================================================
--- test.txt	(revision 10787)
+++ test.txt	(working copy)
@@ -1,7 +1,9 @@
-peach pie              #trunk
+peach cobbler              #trunk
 apple pie
+cherry              #branch
 orange sherbet
 blueberry pancakes     #trunk
+kiwi freeze         #branch
 pear sorbet
 strawberries and cream #trunk
 banana pudding
mymac:test user$

We check this in and we’re done.

Today, I learned something new about bash scripting – the power of brace expansion.  Brace expansion in bash is a way to quickly generate variations of strings used for command line arguments.  I won’t explain this since  this Linux Journal article and the Bash Reference Manual already do an excellent job of it.

First, let me describe my problem.  Some years ago, during a construction project, I set up a cheap web cam in a shed at the site and wrote a quick script that grabbed an image every minute and stored it.  I did this so that I could later create a stop motion video animation of the whole project.  If you’re interested, I posted the videos I made to Flickr.  If you want to look at the one with the most action, just look at this video.

For the duration of the project (about 5 weeks) I wound up with over 60,000 images, all consistently named with timestamps and in directories by day.  I browsed through the thumbnails of each day, and wrote a small text file containing the date and time ranges I was interested in for the video (about 12,000 images).  So I wound up with a manually created text file that had lines that looked like this:

8/25 - 8:56 -> 4:49 - smoke, jackhammer

I planned on using Quicktime Pro to create the stop motion video, by using its feature to open an ‘Image Sequence’.  An image sequence is a directory full of images that are named in a sequence (like picture1.jpg, picture2.jpg, picture3.jpg, etc).  Using ‘Open Image Sequence’, select picture1.jpg and QTPro will find all the sibling images in that directory and create a stop action movie for you in the order the files are named.

This is great, but I had 60,000 files to sort through.  I needed a way to copy (and rename) the images of the time ranges I wanted and put them into a directory for quicktimepro to use.  I decided to specify my time ranges using bash brace expression arguments.  So the images, which were consistently named like this:

YEAR-MONTH-DAY/cam_YEAR-MONTH-DAY_HOUR_MIN_SEC.jpg
for example: 2004-08-25/cam_2004-08-25_13_59_00.jpg

I then changed my text file with human readable date ranges, from above, into something that had lines like this:

2004-08-25/cam_2004-08-25_{08_5{6..9},09,{10..15},16_0,16_{10..49}}*.jpg #8/25-8:56->4:49-smoke,jackhammer

I just needed to write a script that kept a counter, processed each line of my date range file by cp’g each matching file into a new directory and renaming that file based on a counter.  Sounds easy.  First though, I had to learn something about the brace expansion.

Brace expansion only happens once, right after the command line is tokenized.  So this works:

$ echo foo{1,2,3}
foo1 foo2 foo3

That’s great, but this does not work:

$ myline=foo{1,2,3}
$ echo $myline
foo{1,2,3}

This does not work since the brace expansion (foo{1,2,3} -> foo1 foo2 foo3) happens prior to the shell parameter expansion ($myline -> foo{1,2,3}).  To examplify the order, try the reverse experiment.  It should work out the same way:

$ one=1
$ two=2
$ echo values_{$one,$two}
values_1 values_2

So it makes sense, but if we still want to force our $myline variable value through brace expansion, we’ll need to have bash evaluate it twice.  for this, we’ll need the bash builtin ‘eval’ command.  eval evaluates a command line for you, and using it with echo and backtics you can get it to double evaluate our variable, like this:

$ myline=foo{1,2,3}
$ evaluatedline=`eval echo $myline`
$ echo $evaluatedline
foo1 foo2 foo3

And there you go.  Now, using a similar mechanism, we can iterate through our file based list of brace expressions, expand each one and copy the files I want to a new location and sequenced name.  The full script to do this is here:

#!/bin/bash
OUTPUT_DIR=./quicktime/qt_input_files
OUTPUT_FILE_PREFIX=p
OUTPUT_FILE_SUFFIX=.jpg
FILE_OF_PATTERNS=file-list.txt

# make the output directory if it doesn't exist
if [ ! -e $OUTPUT_DIR ]
then
 mkdir -p $OUTPUT_DIR
fi

# clear out previous run's output
# use this construct since passing such a huge amount (12,000) of
#   files to rm (via rm *.jpg)
#    will fail otherwise (due to 'too many args' error)
ls -tr $OUTPUT_DIR | xargs -t -I{} rm -f ${OUTPUT_DIR}/{}

COUNT=1
# $FILE_OF_PATTERNS is the path to a file that contains path specifiers,
#   one per line, like this:
# 2004-10-26/cam_2004-10-26_{08,09_00}*.jpg
# each line specifies some files we want to include in the animation
# do this for each pattern spec I want to grab
for i in `egrep -o '^[^#]*'  $FILE_OF_PATTERNS`
do
 echo =========== PROCESSING: $i ================
 # force the brace expression through another evaluation, like so:
 foo=`eval echo $i`
 # for every file that matches this sub pattern, copy that image
 #   to a file named with a more simple sequence (p1.jpg, p2.jpg, ...)
 for file in `ls $foo 2> /dev/null`
 do
   cp -p ${file} ${OUTPUT_DIR}/${OUTPUT_FILE_PREFIX}${COUNT}${OUTPUT_FILE_SUFFIX}
   let COUNT=$COUNT+1
 done
done