# movies.py
# KMB 11/06/2008

import chunk
import multiprocessing
import os
import struct
import sys
import traceback

import numpy as num
import wx

from params import params
from ellipsesk import annotate_bmp
from version import USE_AVBIN
if USE_AVBIN:
    import pyglet.media as media

# version of sbfmf for writing
__version__ = "0.3b"

#import static_background_fmf as sbfmf
#import motmot.FlyMovieFormat.FlyMovieFormat as fmf
import FlyMovieFormat as fmf
try:
    from FlyMovieFormat import NoMoreFramesException
except ImportError:
    class NoMoreFramesException (Exception): pass
try:
    # FIXME: change back to motmot.ufmf.ufmf
    #import motmot.ufmf.ufmf as ufmf
    import ufmf
except ImportError:
    pass

DEBUG_MOVIES = True

from version import DEBUG
if not DEBUG:
    DEBUG_MOVIES = False


def known_extensions():
    return ['.fmf', '.avi', '.sbfmf', '.ufmf'] # must sync with line 75


class Movie:
    """Generic interface for all supported movie types."""
    def __init__( self, initpath,
                  interactive=True,
                  parentframe=None,
                  open_now=True,
                  default_extension='.fmf' ):
        """Prepare to open a movie (awaiting call to self.open()).
If initpath is a filename, just use it.
If initpath is a directory and interactive is True, then ask user for a filename.
If initpath is a directory and not in interactive mode, it's an error."""

        self.interactive = interactive
        self.dirname = ""
        self.filename = ""
        self.fullpath = ""

        if os.path.isfile( initpath ):
            # it's a file
            self.fullpath = initpath
            self.dirname, self.filename = os.path.split( self.fullpath )

        elif self.interactive:
            # it's a directory -- ask for a filename in that directory

            # make a list of available file extensions, with selected default first
            extensions = {'.fmf': 'fly movie format files (*.fmf)',
                          '.avi': 'audio-video interleave files (*.avi)',
                          '.sbfmf': 'static background fly movie format Files (*.sbfmf)',
                          '.ufmf': 'micro fly movie format Files (*.ufmf)'}
            if len( known_extensions() ) != len( extensions ):
                print "movie-open dialog doesn't list the same number of extensions as known_extensions()"
                
            dialog_str = ''
            # dlg.SetFilterIndex() could do this, too
            if default_extension in extensions.keys():
                dialog_str = extensions[default_extension] + '|*' + default_extension + '|'
                del extensions[default_extension]
            for ext, txt in extensions.iteritems():
                dialog_str += txt + '|*' + ext + '|'
            dialog_str += 'Any (*)|*'

            # show dialog and get selected filename
            dlg = wx.FileDialog( parentframe, "Select movie", initpath, "", dialog_str, wx.OPEN )
        
            if dlg.ShowModal() == wx.ID_OK:
                self.fullpath = dlg.GetPath()
                self.dirname, self.filename = os.path.split( self.fullpath )
            else:
                raise ImportError( "no filename was selected" )
                
            dlg.Destroy()

        else:
            raise ValueError( "not in interactive mode but wasn't given a full filename" )

        if open_now:
            self.open()


    def open( self ):
        """Figure out file type and initialize reader."""

        (front, ext) = os.path.splitext( self.fullpath )
        ext = ext.lower()
        if ext not in known_extensions():
            print "unknown file extension; will try AVbin to open"

        # read FlyMovieFormat
        if ext == '.fmf':
            self.type = 'fmf'
            try:
                self.h_mov = fmf.FlyMovie( self.fullpath )
            except NameError:
                if self.interactive:
                    wx.MessageBox( "Couldn't open \"%s\"\n(maybe FMF is not installed?)"%(filename), "Error", wx.ICON_ERROR|wx.OK )
                raise
            except IOError:
                if self.interactive:
                    wx.MessageBox( "I/O error opening \"%s\""%(self.fullpath), "Error", wx.ICON_ERROR|wx.OK )
                raise
        # read Static Background FlyMovieFormat
        elif ext == '.sbfmf':
            self.type = 'sbfmf'
            try:
                self.h_mov = fmf.FlyMovie( self.fullpath )
            except NameError:
                if self.interactive:
                    wx.MessageBox( "Couldn't open \"%s\"\n(maybe FMF is not installed?)"%(filename), "Error", wx.ICON_ERROR|wx.OK )
                raise
            except IOError:
                if self.interactive:
                    wx.MessageBox( "I/O error opening \"%s\""%(self.fullpath), "Error", wx.ICON_ERROR|wx.OK )
                raise
        # read Micro FlyMovieFormat
        elif ext == '.ufmf':
            self.type = 'ufmf'
            try:
                self.h_mov = ufmf.FlyMovieEmulator( self.fullpath )
            except NameError:
                if self.interactive:
                    wx.MessageBox( "Couldn't open \"%s\"\n(maybe UFMF is not installed?)"%(filename), "Error", wx.ICON_ERROR|wx.OK )
                raise
            except IOError:
                if self.interactive:
                    wx.MessageBox( "I/O error opening \"%s\""%(self.fullpath), "Error", wx.ICON_ERROR|wx.OK )
                raise
            except ufmf.ShortUFMFFileError:
                if self.interactive:
                    wx.MessageBox( "Error opening \"%s\". Short ufmf file."%(filename), "Error", wx.ICON_ERROR|wx.OK )
                raise
            except ufmf.CorruptIndexError:
                if self.interactive:
                    wx.MessageBox( "Error opening \"%s\". Corrupt file index."%(filename), "Error", wx.ICON_ERROR|wx.OK )
                raise
            except ufmf.InvalidMovieFileException:
                if self.interactive:
                    wx.MessageBox( "Error opening \"%s\". Invalid movie file."%(filename), "Error", wx.ICON_ERROR|wx.OK )
                raise
        # read AVI
        elif ext == '.avi':
            try:
                # KB: if use_uncompressed_avi set to False, then
                # only try CompressedAvi class
                if not params.use_uncompressed_avi:
                    if DEBUG: print "Not using uncompressed AVI class"
                    raise
                self.h_mov = Avi( self.fullpath )
                self.type = 'avi'
            except:
                try:
                    self.h_mov = CompressedAvi( self.fullpath )
                    self.type = 'avbin'
                except Exception, details:
                    if self.interactive:
                        if USE_AVBIN and not media.have_avbin:
                            msgtxt = "Failed opening file \"%s\". AVbin could not be loaded, and compressed AVIs cannot be read."%( self.fullpath )
                        else:
                            msgtxt = "Failed opening file \"%s\". AVbin was successfully loaded, but could not read the AVI."%( self.fullpath )
                        wx.MessageBox( msgtxt, "Error", wx.ICON_ERROR|wx.OK )
                    raise
                else:
                    if self.interactive:
                        wx.MessageBox("Your movie is most likely compressed. Out-of-order frame access (e.g. dragging the frame slider toolbars around) will be slow. At this time, the frame chosen to be displayed may be off by one or two frames, i.e. may not line up perfectly with computed trajectories.","Warning",wx.ICON_WARNING|wx.OK)
                    else:
                        print "reading compressed AVI"

            if self.interactive and self.h_mov.bits_per_pixel == 24 and not DEBUG_MOVIES and False:
                wx.MessageBox( "Currently, RGB movies are immediately converted to grayscale. All color information is ignored.", "Warning", wx.ICON_WARNING|wx.OK )

        # unknown movie type
        else:
            try:
                self.h_mov = CompressedAvi( self.fullpath )
                self.type = 'avbin'
            except:
                if self.interactive:
                    wx.MessageBox( "Failed opening file \"%s\"."%(self.fullpath), "Error", wx.ICON_ERROR|wx.OK )
                raise
            else:
                if self.interactive:
                    wx.MessageBox("Ctrax is assuming your movie is in an AVI format and is most likely compressed. Out-of-order frame access (e.g. dragging the frame slider toolbars around) will be slow. At this time, the frame chosen to be displayed may be off by one or two frames, i.e. may not line up perfectly with computed trajectories.","Warning",wx.ICON_WARNING|wx.OK)
                else:
                    print "reading movie as compressed AVI"

        self.file_lock = multiprocessing.RLock()
                    
        # add a buffer of the current frame
        self.bufferedframe_im = None
        self.bufferedframe_stamp = None
        self.bufferedframe_num = None


    def is_open( self ):
        return hasattr( self, 'h_mov' )


    def close( self ):
        """Close the movie file."""
        del self.file_lock
        del self.h_mov
        del self.type
        

    def get_frame( self, framenumber ):
        """Return numpy array containing frame data."""
        # check to see if we have buffered this frame
        if framenumber == self.bufferedframe_num:
            return (self.bufferedframe_im.copy(),self.bufferedframe_stamp)

        with self.file_lock:
            try:
                frame, stamp = self.h_mov.get_frame( framenumber )
            except (IndexError, NoMoreFramesException):
                if self.interactive:
                    wx.MessageBox( "Frame number %d out of range"%(framenumber), "Error", wx.ICON_ERROR|wx.OK )
                else:
                    print "frame", framenumber, "out of range"
                raise
            except (ValueError, AssertionError):
                if self.interactive:
                    wx.MessageBox( "Error reading frame %d"%(framenumber), "Error", wx.ICON_ERROR|wx.OK )
                else:
                    print "error reading frame", framenumber
                raise
            else:
                # store the current frame in the buffer
                self.bufferedframe_im = frame.copy()
                self.bufferedframe_stamp = stamp
                self.bufferedframe_num = framenumber

                return frame, stamp


    def get_n_frames( self ):
        with self.file_lock:
            return self.h_mov.get_n_frames()
    def get_width( self ):
        with self.file_lock:
            return self.h_mov.get_width()
    def get_height( self ):
        with self.file_lock:
            return self.h_mov.get_height()

    def get_some_timestamps( self, t1=0, t2=num.inf ):
        with self.file_lock:
            t2 = min(t2,self.get_n_frames())
            timestamps = self.h_mov.get_all_timestamps()
            timestamps = timestamps[t1:t2]
            return timestamps


    def writesbfmf_start(self,bg,filename):

        # write from start_frame to nframes-1
        self.nframescompress = self.get_n_frames() - params.start_frame

        # allocate array to store the addresses of each frame
        self.writesbfmf_framestarts = num.zeros(self.nframescompress)

        # open the output file
        self.writesbfmf_outfilename = filename
        self.outfile = open(self.writesbfmf_outfilename,'wb')

        # write the header
        self.writesbfmf_writeheader(bg)

    def writesbfmf_isopen(self):
        if not hasattr( self, 'outfile' ) or self.outfile is None:
            return False
        return (not self.outfile.closed)

    def writesbfmf_restart(self,frame,bg,filename):

        self.outfile = None

        self.writesbfmf_outfilename = filename

        self.nframescompress = self.get_n_frames() - params.start_frame

        # allocate array to store the addresses of each frame
        self.writesbfmf_framestarts = num.zeros(self.nframescompress)

        # move the file to a temporary file
        tmpfilename = 'tmp_ctrax_writesbfmf.sbfmf'
        os.rename(filename,tmpfilename)

        # open the old file for reading
        inmovie = Movie( tmpfilename, self.interactive )
            
        # open the output file
        self.outfile = open(filename,"wb")
        
        # rewrite the header
        self.writesbfmf_writeheader(bg)

        # last frame to copy
        i = frame - params.start_frame - 1
        firstaddr = inmovie.h_mov.framelocs[0]
        lastaddr = inmovie.h_mov.framelocs[i]

        self.writesbfmf_framestarts[:i+1] = inmovie.h_mov.framelocs[:i+1]

        if DEBUG_MOVIES: print "copied framestarts: "
        if DEBUG_MOVIES: print str(self.writesbfmf_framestarts[:i+1])

        # seek to the first frame
        inmovie.h_mov.seek(0)

        # copy in pages of size pagesize
        pagesize = int(2**20)
        for j in range(firstaddr,lastaddr,pagesize):
            if DEBUG_MOVIES: print "writing page at %d"%inmovie.h_mov.file.tell()
            buf = inmovie.h_mov.read_some_bytes(pagesize)
            self.outfile.write(buf)

        # write last page
        if j < lastaddr:
            if DEBUG_MOVIES: print "writing page at %d"%inmovie.h_mov.file.tell()
            buf = inmovie.h_mov.read_some_bytes(int(lastaddr-pagesize))
            self.outfile.write(buf)

        # close the input movie and delete
        inmovie.h_mov.close()
        os.remove(tmpfilename)


    def writesbfmf_close(self,frame):
        if hasattr( self, 'outfile' ) and self.outfile is not None:
            # write the index
            self.writesbfmf_writeindex(frame)

            # close the file
            self.outfile.close()


    def writesbfmf_writeindex(self,frame):
        """
        Writes the index at the end of the file. Index consists of nframes unsigned long longs (Q),
        indicating the positions of each frame
        """
        # write the index
        indexloc = self.outfile.tell()
        nframeswrite = frame - params.start_frame + 1
        if DEBUG_MOVIES: print "writing index, nframeswrite = %d"%nframeswrite
        for i in range(nframeswrite):
            self.outfile.write(struct.pack("<Q",self.writesbfmf_framestarts[i]))

        # write the location of the index
        self.outfile.seek(self.writesbfmf_indexptrloc)
        self.outfile.write(struct.pack("<Q",indexloc))            

        # write the number of frames
        self.outfile.seek(self.writesbfmf_nframesloc)
        self.outfile.write(struct.pack("<I",nframeswrite))


    def writesbfmf_writeheader(self,bg):
        """
        Writes the header for the file. Format:
        Number of bytes in version string: (I = unsigned int)
        Version Number (string of specified length)
        Number of rows (I = unsigned int)
        Number of columns (I = unsigned int)
        Number of frames (I = unsigned int)
        Difference mode (I = unsigned int):
          0 if light flies on dark background, unsigned mode
          1 if dark flies on light background, unsigned mode
          2 if other, signed mode
        Location of index (Q = unsigned long long)
        Background image (ncols * nrows * double)
        Standard deviation image (ncols * nrows * double)
        """

        self.nr = self.get_height()
        self.nc = self.get_width()

        # write the number of columns, rows, frames, difference mode
        if bg.bg_type == 'light_on_dark':
            difference_mode = 0
        elif bg.bg_type == 'dark_on_light':
            difference_mode = 1
        else:
            difference_mode = 2
        self.outfile.write(struct.pack("<I",len(__version__)))
        self.outfile.write(__version__)
        self.outfile.write(struct.pack("<2I",int(self.nr),int(self.nc)))
        self.writesbfmf_nframesloc = self.outfile.tell()
        self.outfile.write(struct.pack("<2I",int(self.nframescompress),int(difference_mode)))

        if DEBUG_MOVIES: print "writeheader: nframescompress = " + str(self.nframescompress)

        # compute the location of the standard deviation image
        stdloc = self.outfile.tell() + struct.calcsize("B")*self.nr*self.nc

        # compute the location of the first frame
        ffloc = stdloc + struct.calcsize("d")*self.nr*self.nc

        # where do we write the location of the index -- this is always the same
        self.writesbfmf_indexptrloc = self.outfile.tell()

        # write a placeholder for the index location
        self.outfile.write(struct.pack("<Q",0))

        # write the background image
        self.outfile.write(bg.center)

        # write the standard deviation image
        self.outfile.write(bg.dev)
        

    def writesbfmf_writeframe(self,isfore,im,stamp,currframe):

        if DEBUG_MOVIES: print "writing frame %d"%currframe

        tmp = isfore.copy()
        tmp.shape = (self.nr*self.nc,)
        i, = num.nonzero(tmp)

        # values at foreground pixels
        v = im[isfore]

        # number of foreground pixels
        n = len(i)

        # store the start of this frame
        j = currframe - params.start_frame
        self.writesbfmf_framestarts[j] = self.outfile.tell()

        if DEBUG_MOVIES: print "stored in framestarts[%d]"%j

        # write number of pixels and time stamp
        self.outfile.write(struct.pack("<Id",n,stamp))

        i = i.astype(num.uint32)

        self.outfile.write(i)
        self.outfile.write(v)
        

    def close(self):
        if hasattr(self,'h_mov'):
            with self.file_lock:
                try:
                    self.h_mov.close()
                except:
                    print "Could not close"


"""
AVI class; written by JAB and KMB, altered by Don Olbris.

Don's changes:
important alterations from version I received:
- allows fccHandler = "" or all nulls
- allows width to vary to match actual frame size (suspect that avi pads to 
    multiples of four?) (and let get_width return actual width)
- added various derived attributes to better masquerade as an FMF file
- added an "fmf" mode; in this mode, it reshapes array to same shape as fmf
    (row/column order swapped) -> was commented out, so removed JAB 8/29/11
- added a seek() method

- I want to make this width change more transparent, but I keep
    running into related shape issues

- Avi class is still byte-order undefined, but at least it's read-only
"""

class Avi:
    """Read uncompressed AVI movies."""
    def __init__( self, filename ):

        self.issbfmf = False
 
        # need to open in binary mode to support Windows:
        self.file = open( filename, 'rb' )

        self.frame_index = {} # file locations of each frame

        try:
            self.read_header()
            self.postheader_calculations()
        except Exception, details:
            if DEBUG_MOVIES: print( "error reading uncompressed AVI:" )
            if DEBUG_MOVIES: print( details )
            raise
        
        # added to help masquerade as FMF file:
        self.filename = filename
        self.chunk_start = self.data_start
        # this is a mystery, but I think it's 8 for avi: seems to be offset
        #   from beginning of "chunk" to beginning of array data within chunk
        self.timestamp_len = 8
        if hasattr(self, "newwidth"):
            self.bytes_per_chunk = (self.height * self.newwidth) + self.timestamp_len
        else:
            self.bytes_per_chunk = self.buf_size + self.timestamp_len
        #self.bits_per_pixel = 8
        if DEBUG_MOVIES: print "bits per pix: %d, bytes per chunk %d" % (self.bits_per_pixel, self.bytes_per_chunk)


    def get_all_timestamps( self ):
        """Return a Numpy array containing all frames' timestamps."""
        timestamps = num.zeros( (self.n_frames,) )
        for fr in range( self.n_frames ):
            timestamps[fr] = self.make_timestamp( fr )
        return timestamps
    
        
    def make_timestamp( self, fr ):
        """Approximate timestamp from frame rate recorded in header."""
        if self.frame_delay_us != 0:
            return fr * self.frame_delay_us / 1e6
        elif self.time_scale != 0:
            return fr * self.data_rate / float(self.time_scale)
        else:
            return fr / 30.


    ###################################################################
    # read_header()
    ###################################################################
    def read_header( self ):

        # read RIFF then riffsize
        RIFF, riff_size, AVI = struct.unpack( '4sI4s', self.file.read( 12 ) )
        if not RIFF == 'RIFF':
            print "movie header RIFF error at", RIFF, riff_size, AVI
            raise TypeError("Invalid AVI file. Must be a RIFF file.")
        if (not AVI == 'AVI ') and (not AVI == 'AVIX'):
            print "movie header AVI error at", RIFF, riff_size, AVI
            raise TypeError("Invalid AVI file. File type must be \'AVI \'.")

        # read hdrl
        LIST, hdrl_size, hdrl = struct.unpack( '4sI4s', self.file.read( 12 ) )
        hdrlstart = self.file.tell() - 4

        if not LIST == 'LIST':
            print "movie header LIST 1 error at", LIST, hdrl_size, hdrl
            raise TypeError("Invalid AVI file. Did not find header list.")

        if hdrl == 'hdrl': # a real header
            # read avih 
            avih, avih_size = struct.unpack( '4sI', self.file.read( 8 ) )
            if not avih == 'avih':
                print "movie header avih error at", avih, avih_size
                raise TypeError("Invalid AVI file. Did not find avi header.")
            avihchunkstart = self.file.tell()

            # read microsecperframe
            self.frame_delay_us, = struct.unpack('I',self.file.read(4))

            # skip to nframes
            self.file.seek(3*4,1)
            self.n_frames, = struct.unpack('I',self.file.read(4))
            
            # skip to width, height
            self.file.seek(3*4,1)
            self.width,self.height = struct.unpack('2I',self.file.read(8))

            if DEBUG_MOVIES: print "width = %d, height = %d"%(self.width,self.height)
            if DEBUG_MOVIES: print "n_frames = %d"%self.n_frames

            # skip the rest of the aviheader
            self.file.seek(avihchunkstart+avih_size,0)

            LIST, stream_listsize, strl = \
                  struct.unpack( '4sI4s', self.file.read( 12 ) )

            if (not LIST == 'LIST') or (not strl == 'strl'):
                print "movie header LIST 2 error at", LIST, strl
                raise TypeError("Invalid AVI file. Did not find stream list.")

            strh, strh_size = struct.unpack( '4sI', self.file.read( 8 ) )
            if not strh == 'strh':
                print "movie header strh error at", strh, strh_size
                raise TypeError("Invalid AVI file. Did not find stream header.")

            strhstart = self.file.tell()

            # read stream type, fcc handler
            vids, fcc = struct.unpack( '4s4s', self.file.read( 8 ) )
            # check for vidstream
            if not vids == 'vids':
                print "movie header vids error at", vids
                raise TypeError("Unsupported AVI file type. First stream found is not a video stream.")
            # check fcc
            if fcc not in ['DIB ', '\x00\x00\x00\x00', "", "RAW ", "NONE", chr(24)+"BGR", 'Y8  ']:
                if DEBUG_MOVIES: print "movie header codec error at", fcc
                raise TypeError("Unsupported AVI file type %s, only uncompressed AVIs supported."%fcc)
            if DEBUG_MOVIES: print "codec", fcc

            # skip the rest of the stream header
            self.file.seek(strhstart+strh_size,0)

            strf, strf_size = struct.unpack( '4sI', self.file.read( 8 ) )
            if not strf == "strf":
                print "movie header strf error at", strf
                raise TypeError("Invalid AVI file. Did not find strf.")

            strfstart = self.file.tell()
            bitmapheadersize, = struct.unpack('I',self.file.read(4))

            # skip width, height, planes
            self.file.seek(4*2+2,1)

            # read in bits per pixel
            self.bits_per_pixel, = struct.unpack('H',self.file.read(2))
            if DEBUG_MOVIES: print "bits_per_pixel = %d"%self.bits_per_pixel

            # is this an indexed avi?
            colormapsize = (strf_size - bitmapheadersize)/4
            if colormapsize > 0:
                self.isindexed = True
                self.file.seek(strfstart+bitmapheadersize,0)
                self.colormap = num.frombuffer(self.file.read(4*colormapsize),num.uint8)
                self.colormap = self.colormap.reshape((colormapsize,4))
                self.colormap = self.colormap[:,:-1]
                if DEBUG_MOVIES: print "file is indexed with %d colors" % len( self.colormap )
            else:
                self.isindexed = False

            if self.bits_per_pixel == 24:
                self.isindexed = False

            # skip the rest of the strf
            self.file.seek(hdrlstart+hdrl_size,0)

        else:
            # maybe this is a "second header" following an index... no hdrl
            self.file.seek( -12, os.SEEK_CUR )

        while True:
            # find LIST chunk
            LIST,movilist_size = struct.unpack( '4sI', self.file.read( 8 ) )
            if LIST == 'LIST':
                # find movi
                movi, = struct.unpack('4s',self.file.read(4))
                if DEBUG_MOVIES: print 'looking for movi, found ' + movi
                if movi == 'movi':
                    break
                else:
                    self.file.seek(-4,1)
            # found some other chunk, seek past
            self.file.seek(movilist_size,1)            

        if not movi == 'movi':
            raise TypeError("Invalid AVI file. Did not find movi, found %s."%movi)

        # read extra stuff
        while True:
            fourcc,chunksize, = struct.unpack('4sI',self.file.read(8))
            if DEBUG_MOVIES: print 'read fourcc=%s, chunksize=%d'%(fourcc,chunksize)
            if fourcc == '00db' or fourcc == '00dc':
                self.file.seek(-8,1)
                break
            self.file.seek(chunksize,1)
            
        self.buf_size = chunksize
        if DEBUG_MOVIES: print "chunk size: ", self.buf_size

        # check whether n_frames makes sense (sometimes headers lie)
        approx_file_len = self.buf_size*self.n_frames
        cur_pos = self.file.tell()
        self.file.seek( 0, os.SEEK_END )
        real_file_len = self.file.tell()
        self.file.seek( cur_pos )

        if real_file_len > approx_file_len*1.1:
            print "approximate file length %ld bytes, real length %ld"%(approx_file_len, real_file_len)
            old_n_frames = self.n_frames
            self.n_frames = int( num.floor( (real_file_len - cur_pos)/self.buf_size ) )
            print "guessing %d frames in movie, although header said %d"%(self.n_frames,old_n_frames)


    ###################################################################
    # postheader_calculations()
    ###################################################################
    def postheader_calculations( self ):
        """Tidy up some initialization, immediately after reading header."""
        
        self.data_start = self.file.tell()

        # figure out padding
        depth = self.bits_per_pixel/8
        unpaddedframesize = self.width*self.height*depth

        if unpaddedframesize == self.buf_size:
            # no padding
            self.padwidth = 0
            self.padheight = 0
        elif unpaddedframesize + self.width*depth == self.buf_size:
            self.padwidth = 0
            self.padheight = 1
        elif unpaddedframesize + self.height*depth == self.buf_size:
            self.padwidth = 1
            self.padheight = 0
        else:
            raise TypeError("Invalid AVI file. Frame size (%d) does not match width * height * bytesperpixel (%d*%d*%d)."%(self.buf_size,self.width,self.height,depth))

        if self.bits_per_pixel == 24:
            self.format = 'RGB'
        elif self.isindexed:
            self.format = 'INDEXED'
        elif self.bits_per_pixel == 8:
            self.format = 'MONO8'
        else:
            raise TypeError("Unsupported AVI type. bitsperpixel must be 8 or 24, not %d."%self.bits_per_pixel)

        if DEBUG_MOVIES: print "format = " + str(self.format)

        if self.n_frames == 0:
            loccurr = self.file.tell()
            self.file.seek(0,2)
            locend = self.file.tell()
            self.n_frames = int( num.floor( (locend - self.data_start) / (self.buf_size+8) ) )
            print "n frames = 0; setting to %d"%self.n_frames

        #Barbara Casillas & Victor Mireles, 5/12/13
    	#We try to read the last frame. If it is not accessible
    	# ("frame x out of range" error) then get_frame will fix self.n_frames.
    	#So, we just keep reading the last frame until there is no correction.
    	currentNframes = self.n_frames #(estimated num. of frames)
    	while True:
            frameNumber = self.n_frames - 1
            try:
                self.get_frame(frameNumber)
            except:
                pass
            if self.n_frames == currentNframes:
                break
            currentNframes = self.n_frames


    def nearest_indexed_frame( self, framenumber ):
        """Return nearest known frame index less than framenumber."""
        keys = self.frame_index.keys()
        keys.sort()
        nearest = None
        for key in keys:
            if framenumber > key:
                nearest = key
            else:
                break
        return nearest


    def build_index( self, to_fr ):
        """Build index successively up to a selected frame."""

        # find frame to start from
        near_idx = self.nearest_indexed_frame( to_fr )
        if near_idx is None:
            # no frames read, read first one
            self.file.seek( self.data_start, os.SEEK_SET )
            self.framenumber = 0
            try:
                frame, stamp = self.get_next_frame()
            except:
                if params.interactive:
                    pb.Destroy()
                raise
            else:
                from_fr = 0
        else:
            # start immediately after last known frame
            from_fr = near_idx

        # open progress bar
        if params.interactive and to_fr - from_fr > 10:
            show_pb = True
        else:
            show_pb = False
        if show_pb:
            pb = wx.ProgressDialog( 'Building Frame Index',
                                    "Calculating file location for frame %d"%to_fr,
                                    to_fr - from_fr,
                                    None,
                                    wx.PD_APP_MODAL|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT|wx.PD_REMAINING_TIME )

        # read frames until target frame is reached
        max_increment = 100
        increment = max_increment
        last_fr = from_fr
        this_fr = min( from_fr + increment, to_fr )
        failed_fr = last_fr
        while True:
            # try reading several frames into the future
            self.file.seek( (self.buf_size + 8)*(this_fr - last_fr) + self.frame_index[last_fr], os.SEEK_SET )
            self.framenumber = this_fr
            try:
                frame, stamp = self.get_next_frame()
                if this_fr >= to_fr:
                    break
            except:
                # reading failed -- try reading fewer frames into the future
                if increment == 1:
                    if show_pb:
                        pb.Destroy()
                    raise
                else:
                    increment = max( int( increment/10 ), 1 )
                    failed_fr = last_fr
                    this_fr = min( last_fr + increment, to_fr )
            else:
                # reading successful -- increment and repeat
                if increment != max_increment and this_fr - failed_fr >= 10*increment:
                    # if it's been a long time since failure, speed up again
                    increment = min( 10*increment, max_increment )
                last_fr = this_fr
                this_fr = min( last_fr + increment, to_fr )

            if show_pb:
                (keepgoing, skip) = pb.Update( this_fr - from_fr )
                if not keepgoing:
                    pb.Destroy()
                    raise IndexError( "didn't finish building index to %d"%to_fr )

        if show_pb:
            pb.Destroy()
            
        
    ###################################################################
    # get_frame()
    ###################################################################
    def get_frame( self, framenumber ):
        """Read frame from file and return as NumPy array."""

        if DEBUG_MOVIES: print "uncompressed get_frame(%d)"%framenumber

        if framenumber < 0: raise IndexError
        if framenumber >= self.n_frames: raise NoMoreFramesException
        
        self.framenumber = framenumber

        # read frame from file
        if framenumber in self.frame_index:
            if DEBUG_MOVIES: print "calling frame %d from index at %d"%(framenumber,self.frame_index[framenumber])
            self.file.seek( self.frame_index[framenumber], os.SEEK_SET )
            return self.get_next_frame()
        
        else:
            near_idx = self.nearest_indexed_frame( framenumber )
            if near_idx is not None:
                # offset from nearest indexed frame
                offset = framenumber - near_idx
                self.file.seek( (self.buf_size + 8)*offset + self.frame_index[near_idx], os.SEEK_SET )
            else:
                # offset from beginning of file
                self.file.seek( self.data_start + (self.buf_size+8)*framenumber )

            try:
                return self.get_next_frame()
            except ValueError:
                if framenumber == 0: raise

                self.build_index( framenumber )

                self.file.seek( self.frame_index[framenumber], os.SEEK_SET )
                return self.get_next_frame()


    ###################################################################
    # get_next_frame()
    ###################################################################
    def get_next_frame(self):
        """returns next frame"""

        # test for end of file
        file_data = self.file.read( 8 )
        if len( file_data ) != 8:
            cur_pos = self.file.tell()
            self.file.seek( 0, os.SEEK_END )
            if self.file.tell() >= cur_pos:
                raise IndexError( "error seeking frame %d -- file not readable"%self.framenumber )
            else:
                self.n_frames = self.framenumber - 1
                self.framenumber = self.n_frames
                return self.get_next_frame()
        
        this_frame_id, frame_size = struct.unpack( '4sI', file_data )
        if DEBUG_MOVIES: print 'frame id=%s, sz=%d'%(this_frame_id,frame_size)

        if this_frame_id == 'idx1' or \
               this_frame_id == 'ix00' or \
               this_frame_id == 'ix01': # another index midstream
            a = self.file.read( frame_size )
            this_frame_id, frame_size = struct.unpack( '4sI', self.file.read( 8 ) )
            if DEBUG_MOVIES: print 'skipped index; frame id=' + str(this_frame_id) + ', sz=' + str(frame_size)

        if this_frame_id == 'RIFF': # another whole header
            self.file.seek( -8, os.SEEK_CUR )
            self.read_header()
            this_frame_id, frame_size = struct.unpack( '4sI', self.file.read( 8 ) )
            if DEBUG_MOVIES: print 'skipped another header; frame id=' + str(this_frame_id) + ', sz=' + str(frame_size)

        if hasattr( self, 'frame_id' ) and this_frame_id != self.frame_id:
            # who knows? try skipping ahead a bit
            tries = 0
            while this_frame_id != self.frame_id and tries < 64:
                self.file.seek( -7, os.SEEK_CUR )
                this_frame_id, frame_size = struct.unpack( '4sI', self.file.read( 8 ) )
                tries += 1
            if DEBUG_MOVIES: print "skipped forward %d bytes; now id=%s, sz=%d"%(tries,this_frame_id, frame_size)

        if frame_size != self.buf_size:
            raise ValueError( "Frame size %d on disk does not equal uncompressed size %d; movie must be uncompressed"%(frame_size, self.buf_size) )
        if not hasattr( self, 'frame_id' ):
            self.frame_id = this_frame_id
        elif this_frame_id != self.frame_id:
            if DEBUG_MOVIES: print "looking for header %s; found %s"%(self.frame_id,this_frame_id)
            raise ValueError( "error seeking frame start: unknown data header" )

        # make frame into numpy array
        frame_data = self.file.read( frame_size )
        frame = num.fromstring( frame_data, num.uint8 )

        # reshape...
        width = self.width + self.padwidth
        height = self.height + self.padheight
        if self.isindexed:
            frame = self.colormap[frame,:]
            if params.movie_index_transpose:
                # JAB 20130304: old code always executed this, but it's
                # wrong for the one indexed movie type I have available
                frame.resize((width,height,3))
                frame = frame[:self.width,:self.height,:]
            else:
                frame.resize( (height, width, 3) )
                frame = frame[:self.height,:self.width,:]
            tmp = frame.astype(float)
            tmp = tmp[:,:,0]*.3 + tmp[:,:,1]*.59 + tmp[:,:,2]*.11 # RGB -> L
            if params.movie_index_transpose:
                tmp = tmp.T
            frame = tmp.astype(num.uint8)
        elif frame.size == width*height:
            frame.resize( (height, width) )
            frame = frame[:self.height,:self.width]
        elif frame.size == width*height*3:
            frame.resize( (height, width*3) )
            tmp = frame.astype(float)
            tmp = tmp[:,2:width*3:3]*.3 + \
                tmp[:,1:width*3:3]*.59 + \
                tmp[:,0:width*3:3]*.11 # RGB -> L
            tmp = tmp[:self.height,:self.width]
            frame = tmp.astype(num.uint8)
            #frame = imops.to_mono8( 'RGB24', frame )
            #raise TypeError( "movie must be grayscale" )
        else:
            # frame size doesn't match; for this exercise, pretend the height is 
            #   right and see if width is integral and within 10 of expected;
            #   if so, use that; otherwise, error (djo)
            # raise ValueError( "frame size %d doesn't make sense: movie must be 8-bit grayscale"%(frame.size) )
            if frame.size % height == 0:
                self.newwidth = frame.size / height
                if abs(self.newwidth - width) < 10:
                    frame.resize((self.newwidth, height))
                    frame = frame[:self.width,:self.height]
                else:
                    raise ValueError("apparent new width = %d; expected width = %d"
                        % (height, self.newwidth))
            else:
                print self.width, self.height, self.padwidth, self.padheight
                print self.width*self.height, frame_size, frame.size, self.width*self.height*3, frame_size/3
                print frame_size/self.width/3, frame_size/self.height/3, frame_size % width, frame_size % height
                raise ValueError("apparent new width is not integral; mod = %d" % (frame.size % height))
            
        if self.framenumber not in self.frame_index:
            self.frame_index[self.framenumber] = self.file.tell() - frame_size - 8
            if DEBUG_MOVIES: print "added frame %d to index at %d"%(self.framenumber,self.frame_index[self.framenumber])
        
        return frame, self.make_timestamp( self.framenumber )
        
        # end get_next_frame()
    
    def get_n_frames( self ): 
        return self.n_frames
    
    def get_width( self ): 
        if hasattr(self, "newwidth"):
            return self.newwidth
        else:
            return self.width
    
    def get_height( self ): 
        return self.height
    
    def seek(self,framenumber):
        if framenumber < 0:
            framenumber = self.n_frames + framenumber
        if framenumber in self.frame_index:
            seek_to = self.frame_index[framenumber]
        else:
            seek_to = self.chunk_start + self.bytes_per_chunk*framenumber
        self.file.seek(seek_to)
    
    # end class Avi


class CompressedAvi:

    """Use pyglet.media to read compressed avi files"""
    def __init__(self,filename):

        if not USE_AVBIN:
            raise Exception, 'Trying to read compressed AVI, but USE_AVBIN flag set to False'

        if DEBUG_MOVIES: print 'Trying to read compressed AVI'
        self.issbfmf = False
        self.source = media.load(filename)
        if not hasattr( self.source, '_seek' ):
            # Pyglet >= 1.2
            self.source._seek = self.source.seek
        self.ZERO = 1./1000000.

        # for some video types, if we don't do a read before 
        # seeking, bad things happen
        im = self.source.get_next_video_frame()
        ts = self.source.get_next_video_timestamp()
        if DEBUG_MOVIES: print 'first frame read results in im = ' + str(im)
        if DEBUG_MOVIES: print 'and ts = ' + str(ts)
        self.source._seek(self.ZERO)
        self.start_time = self.source.get_next_video_timestamp()
        if DEBUG_MOVIES: print 'First seek to ZERO succeeded, start_time = ' + str(self.start_time)

        # estimate properties of the video that would be nice 
        # to be able to read in -- i have no idea how accurate 
        # these estimates are
        self.duration_seconds = self.source.duration
        if DEBUG_MOVIES: print 'duration_seconds = ' + str(self.duration_seconds)
        self._estimate_fps()
        if DEBUG_MOVIES: print 'fps estimated to be' + str(self.fps)
        self.n_frames = int(num.floor(self.duration_seconds * self.fps))
        if DEBUG_MOVIES: print "n_frames estimated to be %d"%self.n_frames
        self.frame_delay_us = 1e6 / self.fps
        self._estimate_keyframe_period()
        if DEBUG_MOVIES: print 'keyframe period estimate to be ' + str(self.keyframe_period_s) + ' s, ' + str(self.keyframe_period) + ' frames'
        
        # added to help masquerade as FMF file:
        self.filename = filename

        # read in the width and height of each frame
        self.width = self.source.video_format.width
        self.height = self.source.video_format.height

        self.MAXBUFFERSIZE = num.round(200*1000*1000/self.width/self.height)
        self.buffersize = min(self.MAXBUFFERSIZE,self.keyframe_period)
        if DEBUG_MOVIES: print 'buffersize set to ' + str(self.buffersize)

        # compute the bits per pixel
        self.source._seek(self.ZERO)
        im = self.source.get_next_video_frame()
        im = num.fromstring(im.data,num.uint8)
        self.color_depth = len(im)/self.width/self.height
        if self.color_depth != 1 and self.color_depth != 3:
            raise ValueError( 'color_depth = %d, only know how to deal with color_depth = 1 or colr_depth = 3'%self.color_depth )
        self.bits_per_pixel = self.color_depth * 8

        # allocate the buffer
        self.buffer = num.zeros((self.height,self.width,self.buffersize),dtype=num.uint8)
        self.bufferts = num.zeros(self.buffersize)
        # put the first frame in it
        self.source._seek(self.ZERO)
        self.currframe = 0
        (frame,ts) = self.get_next_frame_and_reset_buffer()

        if DEBUG_MOVIES: print "Done initializing CompressedAVI"

    def get_all_timestamps( self ):
        return num.arange( self.n_frames )/self.fps + self.start_time
        

    def get_frame(self,framenumber):
        """Read frame from file and return as NumPy array."""

        if framenumber < 0: raise IndexError

        # have we already stored this frame?
        if framenumber >= self.bufferframe0 and framenumber < self.bufferframe1:
            off = num.mod(framenumber - self.bufferframe0 + self.bufferoff0,self.buffersize)
            if DEBUG_MOVIES: print "frame %d is in buffer at offset %d"%(framenumber,off)
            return (self.buffer[:,:,off].copy(),self.bufferts[off])

        # is framenumber the next frame to read in?
        if framenumber == self.currframe:
            if DEBUG_MOVIES: print "frame %d is the next frame, just calling get_next_frame"%framenumber
            return self.get_next_frame()

        # otherwise, we need to seek

        # find the last keyframe at or before framenumber
        lastkeyframe = num.floor(framenumber / self.keyframe_period)*self.keyframe_period
        lastkeyframe_s = lastkeyframe / self.fps + self.start_time

        if DEBUG_MOVIES: print "keyframe before frame %d is %d (ts = %f)"%(framenumber,lastkeyframe,lastkeyframe_s)

        didbeginbusy = False
        if params.interactive:
            try:
                wx.Yield()
            except:
                pass
            else:
                if not wx.IsBusy():
                    didbeginbusy = True
                    wx.BeginBusyCursor()

        # is the current frame before the desired frame and at or after the nearest keyframe?
        if framenumber > self.currframe and self.currframe >= lastkeyframe:

            if DEBUG_MOVIES: print "the best thing to do is just to read in frames until we get to desired %d (currframe = %d)"%(framenumber,self.currframe)

            # read into the buffer
            for f in range(self.currframe,framenumber+1):
                try:
                    (frame,ts) = self.get_next_frame()
                except:
                    if didbeginbusy:
                        wx.EndBusyCursor()
                    raise
            
            if didbeginbusy:
                wx.EndBusyCursor()

            return (frame,ts)
            
        # seek around til we find the right window

        if DEBUG_MOVIES: print "we need to seek"

        # make sure we don't move in opposite directions ever
        dirmoved = 0

        # convert from frame to time
        wantts = framenumber / self.fps + self.start_time
        if DEBUG_MOVIES: print "frame %d is equivalent to time stamp %f"%(framenumber,wantts)
        while True:
            # try seeking
            if DEBUG_MOVIES: print "start_time = %f"%self.start_time
            if DEBUG_MOVIES: print "trying to seek to %f (actually %f)"%(lastkeyframe_s,lastkeyframe_s-self.start_time)
            try: 
                self.source._seek(lastkeyframe_s-self.start_time)
            except:
                print 'seeking failed, aborting. tried to seek to %f (actually %f)'%(lastkeyframe_s,lastkeyframe_s-self.start_time)
            tscurr = self.source.get_next_video_timestamp()
            if tscurr is None:
                print 'seeking failed, aborting. timestamp returned is None'
                raise NoMoreFramesException

            if DEBUG_MOVIES: print "ended up seeking to %f"%tscurr

            # are we in the right ballpark?
            if wantts < tscurr:

                lastkeyframe_s = max(self.ZERO,lastkeyframe_s-self.keyframe_period_s)
                if DEBUG_MOVIES: print "still not back far enough, trying to seek to %f"%lastkeyframe_s
                dirmoved = -1
                continue

            elif wantts >= tscurr + self.keyframe_period_s:
                if dirmoved == -1:
                    # we went back and now we think we need to go forward
                    # so we're screwed up
                    # let's just stay
                    if DEBUG_MOVIES: print "uh oh -- we've already gone backward, and now we think we need to go forward, so we're just going to stay"
                    break
                lastkeyframe_s += self.keyframe_period_s
                dirmoved = 1
                continue
            else:
                # we're in the right ballpark
                if DEBUG_MOVIES: print "we're in the right ballpark"
                break

        # end loop searching for keyframe

        lastkeyframe = (tscurr - self.start_time)*self.fps
        self.currframe = int(lastkeyframe)

        (frame,ts) = self.get_next_frame_and_reset_buffer()

        # go forward until we get the right frame
        for f in range(self.currframe,framenumber+1):
            try:
                (frame,ts) = self.get_next_frame()
            except:
                if didbeginbusy:
                    wx.EndBusyCursor()
                raise

        if didbeginbusy:
            wx.EndBusyCursor()

        # return
        return (frame,ts)

    def get_next_frame_and_reset_buffer(self):

        # first frame stored in buffer
        self.bufferframe0 = self.currframe
        # frame after last frame stored in buffer
        self.bufferframe1 = self.currframe + 1
        # buffer will wrap around if frames are read in in-order
        # bufferoff0 is the location in the buffer of the first 
        # frame buffered
        self.bufferoff0 = 0
        (frame,ts) = self._get_next_frame_helper()
        self.buffer[:,:,0] = frame.copy()
        self.bufferts[0] = ts
        # bufferoff is the location in the buffer where we should 
        # write the next frame
        if self.buffersize > 1:
            self.bufferoff = 1
        else:
            self.bufferoff = 0

        # set the current frame
        self.currframe += 1
        self.prevts = ts

        return (frame,ts)

    def _get_next_frame_helper(self):
        ts = self.source.get_next_video_timestamp()
        if ts is None:
            if self.currframe - 1 < self.n_frames:
                self.n_frames = self.currframe - 1
            raise NoMoreFramesException
        im = self.source.get_next_video_frame()
        frame = num.fromstring(im.data,num.uint8)

        if self.color_depth == 1:
            frame.resize((im.height,im.width))
        else: # color_depth == 3
            frame.resize( (self.height, self.width*3) )
            tmp = frame.astype(float)
            tmp = tmp[:,2:self.width*3:3]*.3 + \
                tmp[:,1:self.width*3:3]*.59 + \
                tmp[:,0:self.width*3:3]*.11
            frame = tmp.astype(num.uint8)

        frame = num.flipud(frame)

        return (frame,ts)

    def get_next_frame(self):

        (frame,ts) = self._get_next_frame_helper()

        # store 

        # last frame stored will be 1 more
        self.bufferframe1 += 1

        # are we erasing the first frame?
        if self.bufferoff0 == self.bufferoff:
            self.bufferframe0 += 1
            if self.buffersize > 1:
                self.bufferoff0 += 1
            if DEBUG_MOVIES: print "erasing first frame, bufferframe0 is now %d, bufferoff0 is now %d"%(self.bufferframe0,self.bufferoff0)

        if DEBUG_MOVIES: print "buffer frames: [%d,%d), bufferoffset0 = %d"%(self.bufferframe0,self.bufferframe1,self.bufferoff0)
            
        self.buffer[:,:,self.bufferoff] = frame.copy()
        self.bufferts[self.bufferoff] = ts

        if DEBUG_MOVIES: print "read into buffer[%d], ts = %f"%(self.bufferoff,ts)

        self.bufferoff += 1

        # wrap around
        if self.bufferoff >= self.buffersize:
            self.bufferoff = 0

        if DEBUG_MOVIES: print "incremented bufferoff to %d"%self.bufferoff

        # remember current location in the movie
        self.currframe += 1
        self.prevts = ts

        if DEBUG_MOVIES: print "updated currframe to %d, prevts to %f"%(self.currframe,self.prevts)

        return (frame,ts)

    def _estimate_fps(self):

        if DEBUG_MOVIES: print 'Estimating fps'

        # seek to the start of the stream
        self.source._seek(self.ZERO)

        if DEBUG_MOVIES: print 'First seek succeeded'

        # initial time stamp
        ts0 = self.source.get_next_video_timestamp()
        ts1 = ts0

        if DEBUG_MOVIES: print 'initial time stamp = ' + str(ts0)

        # get the next frame and time stamp a bunch of times
        nsamples = 200
        if DEBUG_MOVIES: print 'nsamples = ' + str(nsamples)
        i = 0 # i is the number of frames we have successfully grabbed
        while True:
            im = self.source.get_next_video_frame()
            ts = self.source.get_next_video_timestamp()
            if DEBUG_MOVIES: print 'i = %d, ts = '%i + str(ts)
            if (ts is None) or num.isnan(ts) or (ts <= ts1):
                break
            i = i + 1
            ts1 = ts
            if i >= nsamples:
                break
        
        if ts1 <= ts0:
            raise ValueError( "Could not compute the fps in the compressed movie" )

        self.fps = float(i) / (ts1-ts0)
        if DEBUG_MOVIES: print 'Estimated frames-per-second = %f'%self.fps
        
    def _estimate_keyframe_period(self):

        if DEBUG_MOVIES: print 'Estimating keyframe period'

        self.source._seek(self.ZERO)

        ts0 = self.source.get_next_video_timestamp()

        if DEBUG_MOVIES: print 'After first seek, ts0 intialized to ' + str(ts0)

        i = 1 # i is the number of successful seeks
        foundfirst = False
        while True:

            # seek to the next frame
            self.source._seek(float(i)/self.fps)

            # get the current time stamp
            ts = self.source.get_next_video_timestamp()

            # did we seek past the end of the movie?
            if ts is None or num.isnan(ts):
                if foundfirst:
                    # we found one keyframe after start, use start
                    self.keyframe_period = i
                    self.keyframe_period_s = self.keyframe_period / self.fps
                else:
                    # then set keyframe period to be length of entire movie
                    self.keyframe_period = self.n_frames + 1
                    self.keyframe_period_s = self.duration_seconds + self.fps
                    if DEBUG_MOVIES: 'Only keyframe found at start of movie, setting keyframe_period = n_frames + 1 = %d, keyframe_period_s = duration_seconds + fps = %f'%(self.keyframe_period,self.keyframe_period_s)

                #raise ValueError( "Could not compute keyframe period in compressed video" )    
                return

            if ts > ts0:
                if foundfirst:
                    break
                else:
                    foundfirst = True
                    i0 = i
                    ts0 = ts

            i = i + 1

        if DEBUG_MOVIES: print "i = %d, i0 = %d"%(i,i0)
        self.keyframe_period = i - i0
        self.keyframe_period_s = self.keyframe_period / self.fps
        if DEBUG_MOVIES: print "Estimated keyframe period = " + str(self.keyframe_period)

    def get_n_frames( self ):
        return self.n_frames

    def get_width( self ):
        return self.width

    def get_height( self ):
        return self.height

    def seek(self,framenumber):

        ts = float(framenumber) / self.fps + self.start_time
        self.source._seek(ts)
        ts = self.source.get_next_video_timestamp()
        self.prevts = ts - 1. / self.fps
        self.currframe = (ts - self.start_time)*self.fps

        return self.currframe

def write_results_to_avi(movie,tracks,filename,f0=None,f1=None):

    nframes = len(tracks)
    if f0 is None:
        f0 = params.start_frame
    if f1 is None:
        f1 = nframes + params.start_frame - 1

    f0 -= params.start_frame
    f1 -= params.start_frame
    f0 = max(0,min(nframes-1,f0))
    f1 = max(0,min(nframes-1,f1))
    nframes_write = f1-f0+1

    # open the file for output
    outstream = open(filename,'wb')

    # write the header
    write_avi_header(movie,tracks,filename,outstream,f0,f1)

    # get the current location
    movilistloc = outstream.tell()

    # write the frames
    offsets = num.zeros(nframes_write)
    for i in range(f0,f1+1):
        if (i % 100) == 0:
            print 'Frame %d / %d'%(i,nframes_write)

        offsets[i-f0] = write_avi_frame(movie,tracks,i,outstream)

        if params.interactive:
            wx.Yield()
        if params.app_instance is not None and not params.app_instance.alive:
            offsets = offsets[:i-f0+1]
            break

    # get offset relative to movilist
    offsets -= movilistloc + 4

    # write the index
    write_avi_index(movie,offsets,outstream)

    # close
    outstream.close()

def write_avi_index(movie,offsets,outstream):

    idx1size = 8 + 16*len( offsets )
    BYTESPERPIXEL = 3
    bytesperframe = int(movie.get_width()*movie.get_height()*BYTESPERPIXEL)

    write_chunk_header('idx1',int(idx1size),outstream)

    for o in offsets:
        try:
            bin_offset = struct.pack( 'I', int(o) )
        except struct.error:
            traceback.print_exc()
            print "writing index %d"%o
            break
        
        outstream.write(struct.pack('4s','00db'))
        outstream.write(struct.pack('I',16))
        outstream.write( bin_offset )
        outstream.write(struct.pack('I',int(bytesperframe)))

def write_avi_frame(movie,tracks,i,outstream):

    height = movie.get_height()
    width = movie.get_width()
    BYTESPERPIXEL = 3
    bytesperframe = width*height*BYTESPERPIXEL

    if tracks is None:
        return
    if i >= len(tracks):
        return

    # global frame index
    j = params.start_frame + i

    # read in the video frame
    try:
        frame, last_timestamp = movie.get_frame(j)
    except (IndexError,NoMoreFramesException):
        return

    # get the current tracks
    ellipses = tracks[i]

    # get tails
    old_pts = []
    early_frame = int(max(0,i-params.tail_length))
    for j in range(early_frame,i+1):
        #print "j = %d"%j
        dataframe = tracks[j]
        #print "dataframe = " + str(dataframe)
        these_pts = []
        ellplot = []
        for ellipse in dataframe.itervalues():
            if num.isnan(ellipse.center.x) or \
                    num.isnan(ellipse.center.y):
                continue
            these_pts.append( (ellipse.center.x,ellipse.center.y,
                               ellipse.identity) )
            ellplot.append(ellipse)
        old_pts.append(these_pts)

    # draw on image
    bitmap,resize,img_size = annotate_bmp(frame,ellplot,old_pts,
                                          params.ellipse_thickness,
                                          [height,width])
    img = bitmap.ConvertToImage()
    # the image is flipped
    img = img.Mirror(True)
    img = img.GetData()

    # write chunktype
    outstream.write(struct.pack('4s','00db'))
    # write size of frame
    outstream.write(struct.pack('I',bytesperframe))

    # write frame
    offset = outstream.tell()
    outstream.write(img[::-1])
    pad = bytesperframe%2
    if pad == 1:
        outstream.write(struct.pack('B',0))
    return offset

def write_avi_header(movie,tracks,filename,outstream,f0,f1):

    # movie size
    BYTESPERPIXEL = 3
    nframes = f1-f0+1
    width = movie.get_width()
    height = movie.get_height()
    bytesperframe = width*height*BYTESPERPIXEL

    # chunk sizes if 0 frames
    avihsize = 64
    #strnsize = 8 + len(filename) + 1
    strllistsize = 116
    strhsize = 56
    strfsize = 48
    hdrllistsize = avihsize + strllistsize + 12
    movilistsize = 12
    idx1size = 8
    riffsize = hdrllistsize + movilistsize + idx1size
    # add in frames
    movilistsize += nframes * (4+4+bytesperframe+(bytesperframe%2))
    idx1size += nframes * (4*4)
    riffsize +=  nframes * (4+4+bytesperframe + 4*4 + (bytesperframe%2))
    ## add in strnsize
    #addon = strnsize + (strnsize%2)
    #riffsize += addon
    #hdrllistsize += addon
    #strllistsize += addon

    # write the RIFF chunk header
    write_chunk_header('RIFF',riffsize,outstream)
    # write AVI fourcc
    outstream.write(struct.pack('4s','AVI '))
    # write hdrl LIST
    write_list_header('hdrl',hdrllistsize-8,outstream)
    # write avih chunk
    write_chunk_header('avih',avihsize-8,outstream)

    ## write main avi header
    # microseconds per frame
    if hasattr(movie,'frame_delay_us'):
        microsecperframe = movie.frame_delay_us
    elif hasattr(movie.h_mov,'frame_delay_us'):
        microsecperframe = movie.h_mov.frame_delay_us
    else:
        microsecperframe = estimate_frame_delay_us(movie.h_mov)
    outstream.write(struct.pack('I',int(round(microsecperframe))))
    # maximum bytes per second
    framespersec = int( round( 1e6/microsecperframe ) )
    bytespersec = framespersec*bytesperframe
    outstream.write(struct.pack('I',int(num.ceil(bytespersec))))
    # reserved
    outstream.write(struct.pack('I',0))
    # flags
    outstream.write(struct.pack('I',16))
    # number of frames
    outstream.write(struct.pack('I',nframes))
    # initial frame
    outstream.write(struct.pack('I',0))
    # number of streams
    outstream.write(struct.pack('I',1))
    # suggested buffer size
    outstream.write(struct.pack('I',bytesperframe))
    # width
    outstream.write(struct.pack('I',width))
    # height
    outstream.write(struct.pack('I',height))
    # frame rate
    outstream.write(struct.pack('2I',100,100*framespersec))
    # not sure -- start, length
    outstream.write(struct.pack('2I',0,0))

    # strl list
    write_list_header('strl',strllistsize-8,outstream)
    # strh chunk
    write_chunk_header('strh',strhsize-8,outstream)

    ## write stream header
    # FCC type
    outstream.write(struct.pack('4s','vids'))
    # FCC handler -- 'DIB '
    #outstream.write(struct.pack('I',0))
    outstream.write( struct.pack( '4s', 'DIB ' ) )
    # Flags
    outstream.write(struct.pack('I',0))
    # Reserved
    outstream.write(struct.pack('I',0))
    # Initial Frame
    outstream.write(struct.pack('I',0))
    # Frame rate
    outstream.write(struct.pack('2I',100,100*framespersec))
    # not sure -- start, length
    outstream.write(struct.pack('2I',0,0))
    # suggested buffer size
    outstream.write(struct.pack('I',bytesperframe))
    # quality
    outstream.write(struct.pack('I',7500))
    # not sure -- sample size
    outstream.write(struct.pack('I',0))

    # Write strf chunk
    write_chunk_header('strf',strfsize-8,outstream)

    ## Write bitmap header
    # Size
    outstream.write(struct.pack('I',40))
    # width
    outstream.write(struct.pack('I',width))
    # height
    outstream.write(struct.pack('I',height))
    # planes
    outstream.write(struct.pack('H',1))
    # bits per pixel
    outstream.write(struct.pack('H',24))
    # FourCC: DIBS
    outstream.write(struct.pack('I',0))
    # image size
    outstream.write(struct.pack('I',bytesperframe))
    # not sure
    outstream.write(struct.pack('4I',0,0,0,0))

    ## Write stream name chunk and data
    #write_chunk_header('strn',strnsize-8,outstream)
    #outstream.write(filename)
    #outstream.write(struct.pack('B',0))
    #if (len(filename)%2) == 1:
    #    outstream.write(struct.pack('B',0))

    # movi list
    write_list_header('movi',movilistsize,outstream)

def write_chunk_header(chunktype,chunksize,outstream):
    try:
        outstream.write(struct.pack('4sI',chunktype,chunksize))
    except struct.error, details:
        traceback.print_exc()
        print "writing '%s' with size %d"%(chunktype,chunksize)
        outstream.write(struct.pack('4sI',chunktype,0))
        

def write_list_header(listtype,listsize,outstream):
    try:
        outstream.write(struct.pack('4sI4s','LIST',listsize,listtype))
    except struct.error, details:
        traceback.print_exc()
        print "writing '%s' with size %d"%(listtype,listsize)
        outstream.write(struct.pack('4sI4s','LIST',0,listtype))


def estimate_frame_delay_us(mov):

    if not hasattr(mov,'chunk_start'):
        return 0

    # go to beginning of first frame
    if mov.issbfmf:
        return .05*1e6
    else:
        mov.file.seek(mov.chunk_start)
        # read the first timestamp
        stamp0 = mov.get_next_timestamp()
        # go to the last frame
        mov.file.seek(mov.chunk_start+mov.bytes_per_chunk*(mov.n_frames-1))
        # read the last timestamp
        stamp1 = mov.get_next_timestamp()


        frame_delay_us = float(stamp1-stamp0)/float(mov.n_frames-1)*1e6
        return frame_delay_us
