Jens Nöckel's Homepage

Computer notes home

Frame-by frame animation as Quicktime with variable frame duration

To create a flipbook (or frame-by-frame) animation from a sequence of images, there are many command-line and GUI-based tools. An overview is given on the page "Creating Animations for Presentations and the web". Here, I'll discuss one method, which I called makeQuicktime.

This is a python script which allows you to assemble a sequence of images into a Quicktime movie that may contain a separate frame duration for each image. In frame-by-frame animation, it is inefficient to keep an image on the screen for long periods by just copying that frame multiple times to fit it into the constant global frame rate. Pausing on certain images is an effective stylistic element, but it shouldn't take up excessive amounts of disk space.

In Quicktime, it is possible to give each frame of an image sequence its own duration, allowing a paused frame to appear static while the movie keeps running, at no additional expense in disk storage.

I've addressed this problem in this StackExchange post, and here I'm listing the required Python code in a more readable form:

#!/usr/bin/python
from Foundation import NSNumber
from AppKit import NSImage
from QTKit import *

class QuickTimeError(Exception):
    @classmethod
    def from_nserror(cls, nserror):
        return cls(nserror.userInfo()['NSLocalizedDescription'])

def createQT(infiles, sequence, durations, outfile):
    attrs = {QTAddImageCodecType: 'avc1', QTAddImageCodecQuality: NSNumber.numberWithLong_(codecHighQuality)}
    mov, err = QTMovie.alloc().initToWritableFile_error_(outfile, None)
    if mov is None:
        raise QuickTimeError.from_nserror(err)
    n = len(durations)-1
    i = 0
    for index in sequence:
        img = NSImage.alloc().initWithContentsOfFile_(infiles[sequence[index]])
        t = durations[i]
        if i<n:
            i = i+1
        else:
            i = 0
        time = QTMakeTime(t, 600)
        mov.addImage_forDuration_withAttributes_(img, time, attrs)
    mov.updateMovieFile()

if __name__ == '__main__':
    import os,sys,csv,string

    buildfile = sys.argv[1]
    framedata = csv.reader(open(buildfile, 'rb'), delimiter=' ')
    imagefiles = []
    durations = []
    for row in framedata:
        if os.path.exists(row[0]):
            imagefiles.append(row[0])
            if len(row)>1:
                durations.append(float(row[1]))
            else:
                durations.append(30)
        else:
            print row[0]+': File not found'
    if len(imagefiles)==0:
        print 'No images found. Movie not created'
    else:
        print 'Creating movie from frames:'
        print '\n'.join(imagefiles)
        outfileName = os.path.abspath(sys.argv[2])
        createQT(imagefiles, range(len(durations)), durations, outfileName)
    

This source code can be saved under any file name, e.g., makeQuicktime, and must then be made executable (chmod 700 makeQuicktime).

Here is a Mathematica line that generates 11 frames of an example animation:

frames = Table[
   Graphics[
    Text[Style["Slow Down", FontFamily -> "Futura", FontColor -> Blue,
       FontSize -> 48], {0, y}], Background -> Cyan, 
    PlotRange -> {{-1, 1}, {.1, 1.5}}], {y, 1.2, .2, -.1}];
Export["frame00000001.png", frames, "VideoFrames"];

To call the script, you first have to prepare a driver file (e.g., buildFile) that lists each image file name in a separate row. Next to each frame, you add an image duration (separated by a space), given in 1/600 of a second:

frame00000001.png 30
frame00000002.png 30
frame00000003.png 30
frame00000004.png 30
frame00000005.png 50
frame00000006.png 50
frame00000007.png 50
frame00000008.png 100
frame00000009.png 100
frame00000010.png 100
frame00000011.png 1200
      

If you leave out the duration, a standard value (30) is inserted instead. If you call createQT from another program, it is also allowed to give a durations array that is shorter than the list of frames (sequence). In that case, the elements of durations are repeated cyclically.

To call the script, you specify the name of the driver file as the first argument, and give the name of the movie output file as the second argument:

makeQuicktime buildFile outputMovie.mov
      

Attached here is a zip file containing some test images. The command to execute in this directory is ./makeQTmovie.py buildfile newMovie.mov.


Jens Nöckel
Last modified: Mon Apr 21 16:52:30 PDT 2014