]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Almost completely rewritten for cleaner code.
authorGuido van Rossum <guido@python.org>
Mon, 7 Sep 1992 09:24:17 +0000 (09:24 +0000)
committerGuido van Rossum <guido@python.org>
Mon, 7 Sep 1992 09:24:17 +0000 (09:24 +0000)
Demo/sgi/video/VFile.py

index e43bd9446d15fe99855a4c1646dd28160a3c2f68..0f723063749681a4feb659b527c60ab4c8567ccc 100755 (executable)
-# VFile -- two classes for Video Files.
-# VinFile -- for video input.
-# VoutFile -- for video output.
+# Classes to read and write CMIF video files.
+# (For a description of the CMIF video format, see cmif-file.ms.)
+
+
+# Layers of functionality:
+#
+# VideoParams: maintain essential parameters of a video file
+# Displayer: display a frame in a window (with some extra parameters)
+# Grabber: grab a frame from a window
+# BasicVinFile: read a CMIF video file
+# BasicVoutFile: write a CMIF video file
+# VinFile: BasicVinFile + Displayer
+# VoutFile: BasicVoutFile + Displayer + Grabber
+
+
+# Imported modules
 
 import sys
 import gl
 import GL
 import colorsys
 
-Error = 'VFile.Error' # Exception
 
-# Missing from GL.py:
+# Exception raised for various occasions
+
+Error = 'VFile.Error'                  # file format errors
+CallError = 'VFile.CallError'          # bad call
+
+
+# Constants returned by gl.getdisplaymode(), from <gl/get.h>
+
 DMRGB = 0
+DMSINGLE = 1
+DMDOUBLE = 2
+DMRGBDOUBLE = 5
+
+
+# Max nr. of colormap entries to use
 
 MAXMAP = 4096 - 256
 
-def conv_grey(l,x,y): return colorsys.yiq_to_rgb(l,0,0)
-def conv_yiq (y,i,q): return colorsys.yiq_to_rgb(y, (i-0.5)*1.2, q-0.5)
-def conv_hls (l,h,s): return colorsys.hls_to_rgb(h,l,s)
-def conv_hsv (v,h,s): return colorsys.hsv_to_rgb(h,s,v)
-def conv_rgb (r,g,b):
+
+# Parametrizations of colormap handling based on color system.
+# (These functions are used via eval with a constructed argument!)
+
+def conv_grey(l, x, y):
+       return colorsys.yiq_to_rgb(l, 0, 0)
+
+def conv_yiq(y, i, q):
+       return colorsys.yiq_to_rgb(y, (i-0.5)*1.2, q-0.5)
+
+def conv_hls(l, h, s):
+       return colorsys.hls_to_rgb(h, l, s)
+
+def conv_hsv(v, h, s):
+       return colorsys.hsv_to_rgb(h, s, v)
+
+def conv_rgb(r, g, b):
        raise Error, 'Attempt to make RGB colormap'
-def conv_rgb8(rgb,d1,d2):
+
+def conv_rgb8(rgb, d1, d2):
        rgb = int(rgb*255.0)
        r = (rgb >> 5) & 0x07
        g = (rgb     ) & 0x07
        b = (rgb >> 3) & 0x03
        return (r/7.0, g/7.0, b/3.0)
 
-# Class VinFile represents a video file used for input.
-#
-# It has the following methods:
-# init(filename)
-# initfp(fp, filename)
-# reopen()
-# rewind()
-# getnextframe()
-# skipnextframe()
-# (and many more)
-#
-# The following read-only data members provide public information:
-# version
-# filename
-# width, height
-# packfactor
-# c0bits, c1bits, c2bits, chrompack
-# offset
-# format
-#
-# These writable data members provide additional parametrization:
-# magnify
-# xorigin, yorigin
-# fallback
 
+# Choose one of the above based upon a color system name
 
+def choose_conversion(format):
+       try:
+               return eval('conv_' + format)
+       except:
+               raise Error, 'Unknown color system: ' + `format`
 
-# XXX it's a total mess now -- VFile is a new base class
-# XXX to support common functionality (e.g. showframe)
 
-class VFile:
+# Routines to grab data, per color system (only a few really supported).
+# (These functions are used via eval with a constructed argument!)
+
+def grab_rgb(w, h, pf):
+       if gl.getdisplaymode() <> DMRGB:
+               raise Error, 'Sorry, can only grab rgb in single-buf rgbmode'
+       if pf <> 1 and pf <> 0:
+               raise Error, 'Sorry, only grab rgb with packfactor 1'
+       return gl.lrectread(0, 0, w-1, h-1), None
+
+def grab_rgb8(w, h, pf):
+       if gl.getdisplaymode() <> DMRGB:
+               raise Error, 'Sorry, can only grab rgb8 in single-buf rgbmode'
+       if pf <> 1 and pf <> 0:
+               raise Error, 'Sorry, can only grab rgb8 with packfactor 1'
+       r = gl.getgdesc(GL.GD_BITS_NORM_SNG_RED)
+       g = gl.getgdesc(GL.GD_BITS_NORM_SNG_GREEN)
+       b = gl.getgdesc(GL.GD_BITS_NORM_SNG_BLUE)
+       if (r, g, b) <> (3, 3, 2):
+               raise Error, 'Sorry, can only grab rgb8 on 8-bit Indigo'
+       # XXX Dirty Dirty here.
+       # XXX Set buffer to cmap mode, grab image and set it back.
+       # XXX (Shouldn't be necessary???)
+       gl.cmode()
+       gl.gconfig()
+       gl.pixmode(GL.PM_SIZE, 8)
+       data = gl.lrectread(0, 0, w-1, h-1)
+       data = data[:w*h]       # BUG FIX for python lrectread
+       gl.RGBmode()
+       gl.gconfig()
+       gl.pixmode(GL.PM_SIZE, 32)
+       return data, None
+
+def grab_grey(w, h, pf):
+       raise Error, 'Sorry, grabbing grey not implemented'
+
+def grab_yiq(w, h, pf):
+       raise Error, 'Sorry, grabbing yiq not implemented'
+
+def grab_hls(w, h, pf):
+       raise Error, 'Sorry, grabbing hls not implemented'
+
+def grab_hsv(w, h, pf):
+       raise Error, 'Sorry, grabbing hsv not implemented'
+
+
+# Choose one of the above based upon a color system name
+
+def choose_grabber(format):
+       try:
+               return eval('grab_' + format)
+       except:
+               raise Error, 'Unknown color system: ' + `format`
+
+
+# Base class to manage video format parameters
+
+class VideoParams:
+
+       # Initialize an instance.
+       # Set all parameters to something decent
+       # (except width and height are set to zero)
+
+       def init(self):
+               # Essential parameters
+               self.format = 'grey'    # color system used
+               # Choose from: 'rgb', 'rgb8', 'hsv', 'yiq', 'hls'
+               self.width = 0          # width of frame
+               self.height = 0         # height of frame
+               self.packfactor = 1     # expansion using rectzoom
+               # if packfactor == 0, data is one 32-bit word/pixel;
+               # otherwise, data is one byte/pixel
+               self.c0bits = 8         # bits in first color dimension
+               self.c1bits = 0         # bits in second color dimension
+               self.c2bits = 0         # bits in third color dimension
+               self.offset = 0         # colormap index offset (XXX ???)
+               self.chrompack = 0      # set if separate chrominance data
+               return self
+
+       # Set the frame width and height (e.g. from gl.getsize())
+
+       def setsize(self, size):
+               self.width, self.height = size
+
+       # Retrieve the frame width and height (e.g. for gl.prefsize())
+
+       def getsize(self):
+               return (self.width, self.height)
+
+       # Set all parameters.
+       # This does limited validity checking;
+       # if the check fails no parameters are changed
+
+       def setinfo(self, values):
+               (self.format, self.width, self.height, self.packfactor,\
+                       self.c0bits, self.c1bits, self.c2bits, self.offset, \
+                       self.chrompack) = values
+
+       # Retrieve all parameters in a format suitable for a subsequent
+       # call to setinfo()
 
-       #
-       # getinfo returns all info pertaining to a film. The returned tuple
-       # can be passed to VoutFile.setinfo()
-       #
        def getinfo(self):
                return (self.format, self.width, self.height, self.packfactor,\
-                         self.c0bits, self.c1bits, self.c2bits, self.offset, \
-                         self.chrompack)
+                       self.c0bits, self.c1bits, self.c2bits, self.offset, \
+                       self.chrompack)
+
+       # Write the relevant bits to stdout
+
+       def printinfo(self):
+               print 'Format:  ', self.format
+               print 'Size:    ', self.width, 'x', self.height
+               print 'Pack:    ', self.packfactor, '; chrom:', self.chrompack
+               print 'Bits:    ', self.c0bits, self.c1bits, self.c2bits
+               print 'Offset:  ', self.offset
+
+
+# Class to display video frames in a window.
+# It is the caller's responsibility to ensure that the correct window
+# is current when using showframe(), initcolormap() and clear()
+
+class Displayer(VideoParams):
+
+       # Initialize an instance.
+       # This does not need a current window
+
+       def init(self):
+               self = VideoParams.init(self)
+               # User-settable parameters
+               self.magnify = 1.0      # frame magnification factor
+               self.xorigin = 0        # x frame offset
+               self.yorigin = 0        # y frame offset (from bottom)
+               self.quiet = 0          # if set, don't print messages
+               self.fallback = 1       # allow fallback to grey
+               # Internal flags
+               self.colormapinited = 0 # must initialize window
+               self.skipchrom = 0      # don't skip chrominance data
+               return self
 
-       # reopen() raises Error if the header is bad (which can only
-       # happen if the file was written to since opened).
+       # setinfo() must reset some internal flags
 
-       def reopen(self):
-               self.fp.seek(0)
-               x = self.initfp(self.fp, self.filename)
+       def setinfo(self, values):
+               VideoParams.setinfo(values)
+               self.colormapinited = 0
+               self.skipchrom = 0
 
-       def setconvcolor(self):
-               try:
-                       self.convcolor = eval('conv_'+self.format)
-               except:
-                       raise Error, \
-                         self.filename + ': unknown colorsys ' + self.format
+       # Show one frame, initializing the window if necessary
 
        def showframe(self, data, chromdata):
-               w, h, pf = self.width, self.height, self.packfactor
                if not self.colormapinited:
                        self.initcolormap()
+               w, h, pf = self.width, self.height, self.packfactor
                factor = self.magnify
                if pf: factor = factor * pf
                if chromdata and not self.skipchrom:
@@ -102,50 +242,62 @@ class VFile:
                if pf:
                        gl.writemask((1 << self.c0bits) - 1)
                        gl.pixmode(GL.PM_SIZE, 8)
-                       gl.rectzoom(factor, factor)
                        w = w/pf
                        h = h/pf
+               gl.rectzoom(factor, factor)
                gl.lrectwrite(self.xorigin, self.yorigin, \
                        self.xorigin + w - 1, self.yorigin + h - 1, data)
 
+       # Initialize the window: set RGB or colormap mode as required,
+       # fill in the colormap, and clear the window
+
        def initcolormap(self):
-               self.colormapinited = 1
                if self.format == 'rgb':
                        gl.RGBmode()
                        gl.gconfig()
-                       gl.RGBcolor(200, 200, 200)
+                       self.colormapinited = 1
+                       gl.RGBcolor(200, 200, 200) # XXX rather light grey
                        gl.clear()
                        return
                gl.cmode()
                gl.gconfig()
                self.skipchrom = 0
+               if self.offset == 0:
+                       self.mask = 0x7ff
+               else:
+                       self.mask = 0xfff
                if not self.quiet:
                        sys.stderr.write('Initializing color map...')
-               self.initcmap()
+               self._initcmap()
+               self.colormapinited = 1
                self.clear()
                if not self.quiet:
                        sys.stderr.write(' Done.\n')
 
+       # Clear the window
+
        def clear(self):
+               if not self.colormapinited: raise CallError
                if self.offset == 0:
                        gl.color(0x800)
                        gl.clear()
-                       self.mask = 0x7ff
                else:
-                       self.mask = 0xfff
                        gl.clear()
 
-       def initcmap(self):
+       # Do the hard work for initializing the colormap
+
+       def _initcmap(self):
+               convcolor = choose_conversion(self.format)
                maxbits = gl.getgdesc(GL.GD_BITS_NORM_SNG_CMODE)
                if maxbits > 11:
                        maxbits = 11
                c0bits, c1bits, c2bits = self.c0bits, self.c1bits, self.c2bits
                if c0bits+c1bits+c2bits > maxbits:
                        if self.fallback and c0bits < maxbits:
-                               # Cannot display film in this mode, use mono
+                               # Cannot display frames in this mode, use grey
                                self.skipchrom = 1
                                c1bits = c2bits = 0
-                               self.convcolor = conv_grey
+                               convcolor = choose_conversion('grey')
                        else:
                                raise Error, 'Sorry, '+`maxbits`+ \
                                  ' bits max on this machine'
@@ -158,9 +310,10 @@ class VFile:
                        offset = self.offset
                if maxbits <> 11:
                        offset = offset & ((1<<maxbits)-1)
-               #for i in range(512, MAXMAP):
+               # XXX why is this here?
+               # for i in range(512, MAXMAP):
                #       gl.mapcolor(i, 0, 0, 0)
-               #void = gl.qtest()    # Should be gl.gflush()
+               # gl.gflush()
                for c0 in range(maxc0):
                        c0v = c0/float(maxc0-1)
                        for c1 in range(maxc1):
@@ -174,366 +327,420 @@ class VFile:
                                        else:
                                                c2v = c2/float(maxc2-1)
                                        index = offset + c0 + (c1<<c0bits) + \
-                                                 (c2 << (c0bits+c1bits))
-                                       rv, gv, bv = self.convcolor( \
-                                                 c0v, c1v, c2v)
-                                       r, g, b = int(rv*255.0), \
-                                                 int(gv*255.0), int(bv*255.0)
+                                               (c2 << (c0bits+c1bits))
                                        if index < MAXMAP:
+                                               rv, gv, bv = \
+                                                 convcolor(c0v, c1v, c2v)
+                                               r, g, b = int(rv*255.0), \
+                                                         int(gv*255.0), \
+                                                         int(bv*255.0)
                                                gl.mapcolor(index, r, g, b)
-               void = gl.gflush()
+               gl.gflush() # send the colormap changes to the X server
+
 
-       
+# Class to grab frames from a window.
+# (This has fewer user-settable parameters than Displayer.)
+# It is the caller's responsibility to initialize the window and to
+# ensure that it is current when using grabframe()
 
-class VinFile(VFile):
+class Grabber(VideoParams):
 
-       # init() and initfp() raise Error if the header is bad.
-       # init() raises whatever open() raises if the file can't be opened.
+       # XXX The init() method of VideoParams is just fine, for now
+
+       # Grab a frame.
+       # Return (data, chromdata) just like getnextframe().
+
+       def grabframe(self):
+               grabber = choose_grabber(self.format)
+               return grabber(self.width, self.height, self.packfactor)
+
+
+# Read a CMIF video file header.
+# Return (version, values) where version is 0.0, 1.0, 2.0 or 3.0,
+# and values is ready for setinfo().
+# Raise Error if there is an error in the info
+
+def readfileheader(fp, filename):
+       #
+       # Get identifying header
+       #
+       line = fp.readline(20)
+       if   line == 'CMIF video 0.0\n':
+               version = 0.0
+       elif line == 'CMIF video 1.0\n':
+               version = 1.0
+       elif line == 'CMIF video 2.0\n':
+               version = 2.0
+       elif line == 'CMIF video 3.0\n':
+               version = 3.0
+       else:
+               # XXX Could be version 0.0 without identifying header
+               raise Error, \
+                       filename + ': Unrecognized file header: ' + `line`[:20]
+       #
+       # Get color encoding info
+       #
+       if version <= 1.0:
+               format = 'grey'
+               c0bits, c1bits, c2bits = 8, 0, 0
+               chrompack = 0
+               offset = 0
+       elif version == 2.0:
+               line = fp.readline()
+               try:
+                       c0bits, c1bits, c2bits, chrompack = eval(line[:-1])
+               except:
+                       raise Error, filename + ': Bad 2.0 color info'
+               if c1bits or c2bits:
+                       format = 'yiq'
+               else:
+                       format = 'grey'
+               offset = 0
+       elif version == 3.0:
+               line = fp.readline()
+               try:
+                       format, rest = eval(line[:-1])
+               except:
+                       raise Error, filename + ': Bad 3.0 color info'
+               if format == 'rgb':
+                       c0bits = c1bits = c2bits = 0
+                       chrompack = 0
+                       offset = 0
+               elif format == 'grey':
+                       c0bits = rest
+                       c1bits = c2bits = 0
+                       chrompack = 0
+                       offset = 0
+               else:
+                       try:
+                           c0bits, c1bits, c2bits, chrompack, offset = rest
+                       except:
+                           raise Error, filename + ': Bad 3.0 color info'
+       #
+       # Get frame geometry info
+       #
+       line = fp.readline()
+       try:
+               x = eval(line[:-1])
+       except:
+               raise Error, filename + ': Bad (w,h,pf) info'
+       if len(x) == 3:
+               width, height, packfactor = x
+               if packfactor == 0 and version < 3.0:
+                       format = 'rgb'
+       elif len(x) == 2 and version <= 1.0:
+               width, height = x
+               packfactor = 2
+       else:
+               raise Error, filename + ': Bad (w,h,pf) info'
+       #
+       # Return (version, values)
+       #
+       values = (format, width, height, packfactor, \
+                 c0bits, c1bits, c2bits, offset, chrompack)
+       return (version, values)
+
+
+# Read a *frame* header -- separate functions per version.
+# Return (timecode, datasize, chromdatasize).
+# Raise EOFError if end of data is reached.
+# Raise Error if data is bad.
+
+def readv0frameheader(fp):
+       line = fp.readline()
+       if not line or line == '\n': raise EOFError
+       try:
+               t = eval(line[:-1])
+       except:
+               raise Error, 'Bad 0.0 frame header'
+       return (t, 0, 0)
+
+def readv1frameheader(fp):
+       line = fp.readline()
+       if not line or line == '\n': raise EOFError
+       try:
+               t, datasize = eval(line[:-1])
+       except:
+               raise Error, 'Bad 1.0 frame header'
+       return (t, datasize, 0)
+
+def readv2frameheader(fp):
+       line = fp.readline()
+       if not line or line == '\n': raise EOFError
+       try:
+               t, datasize = eval(line[:-1])
+       except:
+               raise Error, 'Bad 2.0 frame header'
+       return (t, datasize, 0)
+
+def readv3frameheader(fp):
+       line = fp.readline()
+       if not line or line == '\n': raise EOFError
+       try:
+               t, datasize, chromdatasize = x = eval(line[:-1])
+       except:
+               raise Error, 'Bad 3.0 frame header'
+       return x
+
+
+# Write a CMIF video file header (always version 3.0)
+
+def writefileheader(fp, values):
+       (format, width, height, packfactor, \
+               c0bits, c1bits, c2bits, offset, chrompack) = values
+       #
+       # Write identifying header
+       #
+       fp.write('CMIF video 3.0\n')
+       #
+       # Write color encoding info
+       #
+       if format == 'rgb':
+               data = ('rgb', 0)
+       elif format == 'grey':
+               data = ('grey', c0bits)
+       else:
+               data = (format, (c0bits, c1bits, c2bits, chrompack, offset))
+       fp.write(`data`+'\n')
+       #
+       # Write frame geometry info
+       #
+       if format == 'rgb':
+               packfactor = 0
+       elif packfactor == 0:
+               packfactor = 1
+       data = (width, height, packfactor)
+       fp.write(`data`+'\n')
+
+
+# Basic class for reading CMIF video files
+
+class BasicVinFile(VideoParams):
 
        def init(self, filename):
                if filename == '-':
-                       return self.initfp(sys.stdin, filename)
-               return self.initfp(open(filename, 'r'), filename)
+                       fp = sys.stdin
+               else:
+                       fp = open(filename, 'r')
+               return self.initfp(fp, filename)
 
        def initfp(self, fp, filename):
-               self.colormapinited = 0
-               self.magnify = 1.0
-               self.xorigin = self.yorigin = 0
-               self.fallback = 1
-               self.skipchrom = 0
+               self = VideoParams.init(self)
                self.fp = fp
                self.filename = filename
-               self.quiet = 0
-               #
-               line = self.fp.readline()
-               if line == 'CMIF video 1.0\n':
-                       self.version = 1.0
-               elif line == 'CMIF video 2.0\n':
-                       self.version = 2.0
-               elif line == 'CMIF video 3.0\n':
-                       self.version = 3.0
-               else:
-                       raise Error, self.filename + ': bad video format'
-               #
-               if self.version < 2.0:
-                       self.c0bits, self.c1bits, self.c2bits = 8, 0, 0
-                       self.chrompack = 0
-                       self.offset = 0
-                       self.format = 'grey'
+               self.version, values = readfileheader(fp, filename)
+               VideoParams.setinfo(self, values)
+               if self.version == 0.0:
+                       w, h, pf = self.width, self.height, self.packfactor
+                       if pf == 0:
+                               self._datasize = w*h*4
+                       else:
+                               self._datasize = (w/pf) * (h/pf)
+                       self._readframeheader = self._readv0frameheader
+               elif self.version == 1.0:
+                       self._readframeheader = readv1frameheader
                elif self.version == 2.0:
-                       line = self.fp.readline()
-                       try:
-                               self.c0bits, self.c1bits, self.c2bits, \
-                                       self.chrompack = eval(line[:-1])
-                               if self.c1bits or self.c2bits:
-                                       self.format = 'yiq'
-                               else:
-                                       self.format = 'grey'
-                               self.offset = 0
-                       except:
-                               raise Error, \
-                                 self.filename + ': bad 2.0 color info'
+                       self._readframeheader = readv2frameheader
                elif self.version == 3.0:
-                       line = self.fp.readline()
-                       try:
-                               self.format, rest = eval(line[:-1])
-                               if self.format == 'rgb':
-                                       self.offset = 0
-                                       self.c0bits = 0
-                                       self.c1bits = 0
-                                       self.c2bits = 0
-                                       self.chrompack = 0
-                               elif self.format == 'grey':
-                                       self.offset = 0
-                                       self.c0bits = rest
-                                       self.c1bits = self.c2bits = \
-                                               self.chrompack = 0
-                               else:
-                                       self.c0bits,self.c1bits,self.c2bits,\
-                                         self.chrompack,self.offset = rest
-                       except:
-                               raise Error, \
-                                       self.filename + ': bad 3.0 color info'
-
-               self.setconvcolor()
-               #
-               line = self.fp.readline()
+                       self._readframeheader = readv3frameheader
+               else:
+                       raise Error, \
+                               filename + ': Bad version: ' + `self.version`
+               self.framecount = 0
+               self.atframeheader = 1
                try:
-                       x = eval(line[:-1])
-                       if self.version > 1.0 or len(x) == 3:
-                               self.width, self.height, self.packfactor = x
-                               if self.packfactor == 0:
-                                       self.format = 'rgb'
-                       else:
-                               sef.width, self.height = x
-                               self.packfactor = 2
-               except:
-                       raise Error, self.filename + ': bad (w,h,pf) info'
-               self.frameno = 0
-               self.framecache = []
-               self.hascache = 0
-               #
+                       self.startpos = self.fp.tell()
+                       self.canseek = 1
+               except IOError:
+                       self.startpos = -1
+                       self.canseek = 0
                return self
 
-       def warmcache(self):
-               if self.hascache: return
-               n = 0
-               try:
-                       while 1:
-                               void = self.skipnextframe()
-                               n = n + 1
-               except EOFError:
-                       pass
-               if not self.hascache:
-                       raise Error, 'Cannot warm cache'
+       def _readv0frameheader(self, fp):
+               t, ds, cs = readv0frameheader(fp)
+               ds = self._datasize
+               return (t, ds, cs)
 
        def close(self):
                self.fp.close()
-               self.fp = None
+               del self.fp
+               del self._readframeheader
+
+       def setinfo(self, values):
+               raise CallError # Can't change info of input file!
+
+       def setsize(self, size):
+               raise CallError # Can't change info of input file!
 
        def rewind(self):
-               if self.hascache:
-                       self.frameno = 0
-               else:
-                       self.reopen()
+               if not self.canseek:
+                       raise Error, self.filename + ': can\'t seek'
+               self.fp.seek(self.startpos)
+               self.framecount = 0
+               self.atframeheader = 1
 
-       def position(self):
-               if self.frameno >= len(self.framecache):
-                       raise EOFError
-               self.fp.seek(self.framecache[self.frameno][0])
+       def warmcache(self):
+               pass
 
-       # getnextframe() raises EOFError (built-in) if there is no next frame,
-       # or if the next frame is broken.
-       # So to getnextframeheader(), getnextframedata() and skipnextframe().
+       def printinfo(self):
+               print 'File:    ', self.filename
+               print 'Version: ', self.version
+               VideoParams.printinfo(self)
 
        def getnextframe(self):
-               time, size, chromsize = self.getnextframeheader()
-               data, chromdata = self.getnextframedata(size, chromsize)
-               return time, data, chromdata
-
-       def getnextframedata(self, size, chromsize):
-               if self.hascache:
-                       self.position()
-               self.frameno = self.frameno + 1
-               data = self.fp.read(size)
-               if len(data) <> size: raise EOFError
-               if chromsize:
-                       chromdata = self.fp.read(chromsize)
-                       if len(chromdata) <> chromsize: raise EOFError
-               else:
-                       chromdata = None
-               #
-               return data, chromdata
+               t, ds, cs = self.getnextframeheader()
+               data, cdata = self.getnextframedata(ds, cs)
+               return (t, data, cdata)
 
        def skipnextframe(self):
-               time, size, chromsize = self.getnextframeheader()
-               self.skipnextframedata(size, chromsize)
-               return time
-
-       def skipnextframedata(self, size, chromsize):
-               if self.hascache:
-                       self.frameno = self.frameno + 1
-                       return
-               # Note that this won't raise EOFError for a partial frame.
-               try:
-                       self.fp.seek(size + chromsize, 1) # Relative seek
-               except:
-                       # Assume it's a pipe -- read the data to discard it
-                       dummy = self.fp.read(size + chromsize)
+               t, ds, cs = self.getnextframeheader()
+               self.skipnextframedata(ds, cs)
+               return t
 
        def getnextframeheader(self):
-               if self.hascache:
-                       if self.frameno >= len(self.framecache):
-                               raise EOFError
-                       return self.framecache[self.frameno][1]
-               line = self.fp.readline()
-               if not line:
-                       self.hascache = 1
-                       raise EOFError
-               #
-               w, h, pf = self.width, self.height, self.packfactor
+               if not self.atframeheader: raise CallError
+               self.atframeheader = 0
                try:
-                       x = eval(line[:-1])
-                       if type(x) in (type(0), type(0.0)):
-                               time = x
-                               if pf == 0:
-                                       size = w * h * 4
-                               else:
-                                       size = (w/pf) * (h/pf)
-                       elif len(x) == 2:
-                               time, size = x
-                               cp = self.chrompack
-                               if cp:
-                                       cw = (w + cp - 1) / cp
-                                       ch = (h + cp - 1) / cp
-                                       chromsize = 2 * cw * ch
-                               else:
-                                       chromsize = 0
-                       else:
-                               time, size, chromsize = x
-               except:
-                       raise Error, self.filename + ': bad frame header'
-               cdata = (self.fp.tell(), (time, size, chromsize))
-               self.framecache.append(cdata)
-               return time, size, chromsize
-
-       def shownextframe(self):
-               time, data, chromdata = self.getnextframe()
-               self.showframe(data, chromdata)
-               return time
-
-#
-# A set of routines to grab images from windows
-#
-def grab_rgb(w, h, pf):
-       if gl.getdisplaymode() <> DMRGB:
-               raise Error, 'Sorry, can only grab rgb in single-buf rgbmode'
-       if pf <> 1 and pf <> 0:
-               raise Error, 'Sorry, only grab with packfactor=1'
-       return gl.lrectread(0, 0, w-1, h-1), None
-       
-def grab_rgb8(w, h, pf):
-       if gl.getdisplaymode() <> DMRGB:
-               raise Error, 'Sorry, can only grab rgb in single-buf rgbmode'
-       if pf <> 1 and pf <> 0:
-               raise Error, 'Sorry, can only grab with packfactor=1'
-       r = gl.getgdesc(GL.GD_BITS_NORM_SNG_RED)
-       g = gl.getgdesc(GL.GD_BITS_NORM_SNG_GREEN)
-       b = gl.getgdesc(GL.GD_BITS_NORM_SNG_BLUE)
-       if (r,g,b) <> (3,3,2):
-               raise Error, 'Sorry, can only grab rgb8 on 8-bit Indigo'
-       # Dirty Dirty here. Set buffer to cmap mode, grab image and set it back
-       gl.cmode()
-       gl.gconfig()
-       gl.pixmode(GL.PM_SIZE, 8)
-       data = gl.lrectread(0, 0, w-1, h-1)
-       data = data[:w*h]       # BUG FIX for python lrectread
-       gl.RGBmode()
-       gl.gconfig()
-       gl.pixmode(GL.PM_SIZE, 32)
-       return data, None
-       
-def grab_grey(w, h, pf):
-       raise Error, 'Sorry, grabbing grey not implemented'
+                       return self._readframeheader(self.fp)
+               except Error, msg:
+                       # Patch up the error message
+                       raise Error, self.filename + ': ' + msg
+
+       def getnextframedata(self, ds, cs):
+               if self.atframeheader: raise CallError
+               if ds:
+                       data = self.fp.read(ds)
+                       if len(data) < ds: raise EOFError
+               else:
+                       data = ''
+               if cs:
+                       cdata = self.fp.read(cs)
+                       if len(cdata) < cs: raise EOFerror
+               else:
+                       cdata = ''
+               self.atframeheader = 1
+               self.framecount = self.framecount + 1
+               return (data, cdata)
+
+       def skipnextframedata(self, ds, cs):
+               if self.atframeheader: raise CallError
+               # Note that this won't raise EOFError for a partial frame
+               # since there is no easy way to tell whether a seek
+               # ended up beyond the end of the file
+               if self.canseek:
+                       self.fp.seek(ds + cs, 1) # Relative seek
+               else:
+                       dummy = self.fp.read(ds + cs)
+                       del dummy
+               self.atframeheader = 1
+               self.framecount = self.framecount + 1
 
-def grab_yiq(w, h, pf):
-       raise Error, 'Sorry, grabbing yiq not implemented'
 
-def grab_hls(w, h, pf):
-       raise Error, 'Sorry, grabbing hls not implemented'
+class BasicVoutFile(VideoParams):
 
-def grab_hsv(w, h, pf):
-       raise Error, 'Sorry, grabbing hsv not implemented'
-
-#
-# The class VoutFile is not as well-designed (and tested) as VinFile.
-# Notably it will accept almost any garbage and write it to the video
-# output file
-#
-class VoutFile(VFile):
        def init(self, filename):
                if filename == '-':
-                       return self.initfp(sys.stdout, filename)
+                       fp = sys.stdout
                else:
-                       return self.initfp(open(filename,'w'), filename)
-                       
+                       fp = open(filename, 'w')
+               return self.initfp(fp, filename)
+
        def initfp(self, fp, filename):
+               self = VideoParams.init(self)
                self.fp = fp
-               self.format = 'grey'
-               self.width = self.height = 0
-               self.packfactor = 1
-               self.c0bits = 8
-               self.c1bits = self.c2bits = 0
-               self.offset = 0
-               self.chrompack = 0
+               self.filename = filename
+               self.version = 3.0 # In case anyone inquires
                self.headerwritten = 0
-               self.quiet = 0
-               self.magnify = 1
-               self.setconvcolor()
-               self.xorigin = self.yorigin = 0
                return self
 
+       def flush(self):
+               self.fp.flush()
+
        def close(self):
                self.fp.close()
-               x = self.initfp(None, None)
+               del self.fp
 
        def setinfo(self, values):
-               self.format, self.width, self.height, self.packfactor,\
-                         self.c0bits, self.c1bits, self.c2bits, self.offset, \
-                         self.chrompack = values
-               self.setconvcolor()
+               if self.headerwritten: raise CallError
+               VideoParams.setinfo(self, values)
 
        def writeheader(self):
+               if self.headerwritten: raise CallError
+               writefileheader(self.fp, self.getinfo())
                self.headerwritten = 1
-               if self.format == 'rgb':
-                       self.packfactor = 0
-               elif self.packfactor == 0:
-                       self.packfactor = 1
-               self.fp.write('CMIF video 3.0\n')
-               if self.format == 'rgb':
-                       data = ('rgb', 0)
-               elif self.format == 'grey':
-                       data = ('grey', self.c0bits)
-               else:
-                       data = (self.format, (self.c0bits, self.c1bits, \
-                                 self.c2bits, self.chrompack, self.offset))
-               self.fp.write(`data`+'\n')
-               data = (self.width, self.height, self.packfactor)
-               self.fp.write(`data`+'\n')
-               try:
-                       self._grabber = eval('grab_' + self.format)
-               except:
-                       raise Error, 'unknown colorsys: ' + self.format
-
-       def writeframeheader(self, data):
-               if not self.headerwritten:
-                       raise Error, 'Writing frame data before header'
-               # XXXX Should we sanity check here?
-               self.fp.write(`data`+'\n')
-
-       def writeframedata(self, data, chromdata):
-               # XXXX Check sizes here
-               self.fp.write(data)
-               if chromdata:
-                       self.fp.write(chromdata)
-
-       def writeframe(self, time, data, chromdata):
-               if chromdata:
-                       clen = len(chromdata)
-               else:
-                       clen = 0
-               self.writeframeheader((time, len(data), clen))
-               self.writeframedata(data, chromdata)
+               self.atheader = 1
+               self.framecount = 0
+
+       def rewind(self):
+               self.fp.seek(0)
+               self.headerwritten = 0
+               self.atheader = 1
+               self.framecount = 0
+
+       def printinfo(self):
+               print 'File:    ', self.filename
+               VideoParams.printinfo(self)
+
+       def writeframe(self, t, data, cdata):
+               if data: ds = len(data)
+               else: ds = 0
+               if cdata: cs = len(cdata)
+               else: cs = 0
+               self.writeframeheader(t, ds, cs)
+               self.writeframedata(data, cdata)
+
+       def writeframeheader(self, t, ds, cs):
+               if not self.headerwritten: self.writeheader()
+               if not self.atheader: raise CallError
+               self.fp.write(`(t, ds, cs)` + '\n')
+               self.atheader = 0
+
+       def writeframedata(self, data, cdata):
+               if not self.headerwritten or self.atheader: raise CallError
+               if data: self.fp.write(data)
+               if cdata: self.fp.write(cdata)
+               self.atheader = 1
+               self.framecount = self.framecount + 1
+
+
+# Classes that combine files with displayers and/or grabbers:
+
+class VinFile(BasicVinFile, Displayer):
+
+       def initfp(self, fp, filename):
+               self = Displayer.init(self)
+               return BasicVinFile.initfp(self, fp, filename)
+
+       def shownextframe(self):
+               t, data, cdata = self.getnextframe()
+               self.showframe(data, cdata)
+               return t
+
+
+class VoutFile(BasicVoutFile, Displayer, Grabber):
+
+       def initfp(self, fp, filename):
+               self = Displayer.init(self)
+##             self = Grabber.init(self) # XXX not needed
+               return BasicVoutFile.initfp(self, fp, filename)
+
+
+# Simple test program (VinFile only)
 
-       def grabframe(self):
-               return self._grabber(self.width, self.height, self.packfactor)
-               
 def test():
-       import sys, time
-       filename = 'film.video'
+       import time
        if sys.argv[1:]: filename = sys.argv[1]
+       else: filename = 'film.video'
        vin = VinFile().init(filename)
-       print 'File:    ', filename
-       print 'Version: ', vin.version
-       print 'Size:    ', vin.width, 'x', vin.height
-       print 'Pack:    ', vin.packfactor, '; chrom:', vin.chrompack
-       print 'Bits:    ', vin.c0bits, vin.c1bits, vin.c2bits
-       print 'Format:  ', vin.format
-       print 'Offset:  ', vin.offset
+       vin.printinfo()
        gl.foreground()
-       gl.prefsize(vin.width, vin.height)
+       gl.prefsize(vin.getsize())
        wid = gl.winopen(filename)
        vin.initcolormap()
        t0 = time.millitimer()
        while 1:
-               try:
-                       t, data, chromdata = vin.getnextframe()
-               except EOFError:
-                       break
-               dt = t + t0 - time.millitimer()
-               if dt > 0:
-                       time.millisleep(dt)
-               vin.showframe(data, chromdata)
-       print 'Done.'
+               try: t = vin.shownextframe()
+               except EOFError: break
+               dt = t0 + t - time.millitimer()
+               if dt > 0: time.millisleep(dt)
        time.sleep(2)
-