Created Vedit.py, the video editor.  This uses the classes in Viewer.py.
Viewer.py in turn requires changes to VFile.py (unfortunately that file
is now a complete mess...).
diff --git a/Demo/sgi/video/README b/Demo/sgi/video/README
index eedb85f..85855a9 100644
--- a/Demo/sgi/video/README
+++ b/Demo/sgi/video/README
@@ -100,6 +100,10 @@
 		manipulating the time codes (e.g. faster/slower, or
 		regenerate time codes, or drop frames too close apart)
 
+Vedit.py	interactive video editing program
+
+Viewer.py	two viewer classes used by Vedit
+
 
 The following are C programs, either for efficiency or because they
 need to link with a C library:
diff --git a/Demo/sgi/video/VFile.py b/Demo/sgi/video/VFile.py
index 17f677f..b16b2ab 100755
--- a/Demo/sgi/video/VFile.py
+++ b/Demo/sgi/video/VFile.py
@@ -52,116 +52,12 @@
 # xorigin, yorigin
 # fallback
 
-class VinFile:
 
-	# init() and initfp() raise Error if the header is bad.
-	# init() raises whatever open() raises if the file can't be opened.
 
-	def init(self, filename):
-		if filename == '-':
-			return self.initfp(sys.stdin, filename)
-		return self.initfp(open(filename, 'r'), filename)
+# XXX it's a total mess now -- VFile is a new base class
+# XXX to support common functionality (e.g. showframe)
 
-	def initfp(self, fp, filename):
-		self.colormapinited = 0
-		self.magnify = 1.0
-		self.xorigin = self.yorigin = 0
-		self.fallback = 1
-		self.skipchrom = 0
-		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'
-		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'
-		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'
-
-		try:
-			self.convcolor = eval('conv_'+self.format)
-		except:
-			raise Error, \
-			  self.filename + ': unknown colorsys ' + self.format
-		#
-		line = self.fp.readline()
-		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
-		#
-		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 close(self):
-		self.fp.close()
-		self.fp = None
-
+class VFile:
 
 	#
 	# getinfo returns all info pertaining to a film. The returned tuple
@@ -179,96 +75,12 @@
 		self.fp.seek(0)
 		x = self.initfp(self.fp, self.filename)
 
-	def rewind(self):
-		if self.hascache:
-			self.frameno = 0
-		else:
-			self.reopen()
-
-	def position(self):
-		if self.frameno >= len(self.framecache):
-			raise EOFError
-		self.fp.seek(self.framecache[self.frameno][0])
-
-	# 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 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
-
-	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.
+	def setconvcolor(self):
 		try:
-			self.fp.seek(size + chromsize, 1) # Relative seek
+			self.convcolor = eval('conv_'+self.format)
 		except:
-			# Assume it's a pipe -- read the data to discard it
-			dummy = self.fp.read(size + chromsize)
-
-	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
-		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
+			raise Error, \
+			  self.filename + ': unknown colorsys ' + self.format
 
 	def showframe(self, data, chromdata):
 		w, h, pf = self.width, self.height, self.packfactor
@@ -369,6 +181,205 @@
 						gl.mapcolor(index, r, g, b)
 		void = gl.gflush()
 
+	
+
+class VinFile(VFile):
+
+	# init() and initfp() raise Error if the header is bad.
+	# init() raises whatever open() raises if the file can't be opened.
+
+	def init(self, filename):
+		if filename == '-':
+			return self.initfp(sys.stdin, filename)
+		return self.initfp(open(filename, 'r'), 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.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'
+		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'
+		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()
+		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
+		#
+		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 close(self):
+		self.fp.close()
+		self.fp = None
+
+	def rewind(self):
+		if self.hascache:
+			self.frameno = 0
+		else:
+			self.reopen()
+
+	def position(self):
+		if self.frameno >= len(self.framecache):
+			raise EOFError
+		self.fp.seek(self.framecache[self.frameno][0])
+
+	# 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 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
+
+	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)
+
+	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
+		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
 #
@@ -417,7 +428,7 @@
 # Notably it will accept almost any garbage and write it to the video
 # output file
 #
-class VoutFile:
+class VoutFile(VFile):
 	def init(self, filename):
 		if filename == '-':
 			return self.initfp(sys.stdout, filename)
@@ -434,21 +445,21 @@
 		self.offset = 0
 		self.chrompack = 0
 		self.headerwritten = 0
+		self.quiet = 0
+		self.magnify = 1
+		self.setconvcolor()
+		self.xorigin = self.yorigin = 0
 		return self
 
 	def close(self):
 		self.fp.close()
 		x = self.initfp(None, None)
 
-	def getinfo(self):
-		return (self.format, self.width, self.height, self.packfactor,\
-			  self.c0bits, self.c1bits, self.c2bits, self.offset, \
-			  self.chrompack)
-
 	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()
 
 	def writeheader(self):
 		self.headerwritten = 1
diff --git a/Demo/sgi/video/Vedit.py b/Demo/sgi/video/Vedit.py
new file mode 100755
index 0000000..fa8631a
--- /dev/null
+++ b/Demo/sgi/video/Vedit.py
@@ -0,0 +1,247 @@
+#! /ufs/guido/bin/sgi/python
+
+# Edit CMIF movies interactively -- copy one or more files to an output file
+
+
+# Possibilities:
+#
+# - convert between formats (grey, rgb, rgb8, ...)
+# - change size
+# - cut out a given area of the image
+# - change time base (a la Vtime)
+# - skip stretches of frames
+
+
+import sys
+import os
+import gl, GL, DEVICE
+import fl, FL
+import flp
+import Viewer
+import getopt
+import string
+
+
+def main():
+	qsize = 20
+	opts, args = getopt.getopt(sys.argv[1:], 'q:')
+	for o, a in opts:
+		if o == '-q':
+			qsize = string.atoi(a)
+	ed = Editor().init(qsize)
+	if args[0:]:
+		ed.open_input(args[0])
+	if args[1:]:
+		ed.open_output(args[1])
+	while 1:
+		dummy = fl.do_forms()
+
+
+class Editor:
+
+	def init(self, qsize):
+		self.qsize = qsize
+		self.vin = None
+		self.vout = None
+		self.ifile = ''
+		self.ofile = ''
+		formdef = flp.parse_form('VeditForm', 'form')
+		flp.create_full_form(self, formdef)
+		self.form.show_form(FL.PLACE_SIZE, FL.TRUE, 'Vedit')
+		fl.set_event_call_back(self.do_event)
+		return self
+
+	def do_event(self, (dev, val)):
+		if dev == DEVICE.REDRAW:
+			if self.vin:
+				self.vin.redraw(val)
+			if self.vout:
+				self.vout.redraw(val)
+
+
+	def iocheck(self):
+		self.msg('')
+		if self.vin == None and self.vout == None:
+			self.err('Please open input and output files first')
+			return 0
+		return self.icheck() and self.ocheck()
+
+	def icheck(self):
+		self.msg('')
+		if self.vin == None:
+			self.err('Please open an input file first')
+			return 0
+		return 1
+
+	def ocheck(self):
+		self.msg('')
+		if self.vout == None:
+			self.err('Please open an output file first')
+			return 0
+		return 1
+
+
+	def cb_in_new(self, args):
+		self.msg('')
+		hd, tl = os.path.split(self.ifile)
+		filename = fl.file_selector('Input video file', hd, '', tl)
+		if not filename: return
+		self.open_input(filename)
+
+	def cb_in_close(self, args):
+		self.msg('')
+		self.close_input()
+
+	def cb_in_skip(self, args):
+		if not self.icheck(): return
+		if not self.vin.get(): self.err('End of input file')
+		self.ishow()
+
+	def cb_in_back(self, args):
+		if not self.icheck(): return
+		if not self.vin.backup(): self.err('Input buffer exhausted')
+		self.ishow()
+
+	def cb_in_rewind(self, args):
+		if not self.icheck(): return
+		self.vin.rewind()
+		self.ishow()
+
+
+	def cb_copy(self, args):
+		if not self.iocheck(): return
+		data = self.vin.get()
+		if data:
+			if self.vout.getinfo() <> self.vin.getinfo():
+				print 'Copying info...'
+				self.vout.setinfo(self.vin.getinfo())
+			self.vout.put(data)
+			self.oshow()
+		self.ishow()
+
+	def cb_uncopy(self, args):
+		if not self.iocheck(): return
+		if not self.vout.backup():
+			self.err('Output buffer exhausted')
+			return
+		self.oshow()
+		if not self.vin.backup():
+			self.err('Input buffer exhausted')
+			return
+		self.ishow()
+
+
+	def cb_out_new(self, args):
+		self.msg('')
+		hd, tl = os.path.split(self.ofile)
+		filename = fl.file_selector('Output video file', hd, '', tl)
+		if not filename: return
+		self.open_output(filename)
+
+	def cb_out_close(self, args):
+		self.msg('')
+		self.close_output()
+
+	def cb_out_skip(self, arg):
+		if not self.ocheck(): return
+		if not self.vout.forward(): self.err('Output buffer exhausted')
+		self.oshow()
+		
+	def cb_out_back(self, args):
+		if not self.ocheck(): return
+		if not self.vout.backup(): self.err('Output buffer exhausted')
+		self.oshow()
+
+	def cb_out_rewind(self, args):
+		if not self.ocheck(): return
+		self.vout.rewind()
+		self.oshow()
+
+
+	def cb_quit(self, args):
+		self.close_input()
+		self.close_output()
+		sys.exit(0)
+
+
+	def open_input(self, filename):
+		self.ifile = filename
+		basename = os.path.split(filename)[1]
+		title = 'in: ' + basename
+		try:
+			vin = Viewer.InputViewer().init(filename, \
+				title, self.qsize)
+		except:
+			self.err('Can\'t open input file', filename)
+			return
+		self.close_input()
+		self.vin = vin
+		self.in_file.label = basename
+		self.ishow()
+
+	def close_input(self):
+		if self.vin:
+			self.msg('Closing input file...')
+			self.vin.close()
+		self.msg('')
+		self.vin = None
+		self.in_file.label = '(none)'
+		self.format('in')
+
+	def ishow(self):
+		self.vin.show()
+		self.format('in')
+
+	def open_output(self, filename):
+		self.ofile = filename
+		basename = os.path.split(filename)[1]
+		title = 'out: ' + basename
+		try:
+			vout = Viewer.OutputViewer().init(filename, \
+				title, self.qsize)
+		except:
+			self.err('Can\'t open output file', filename)
+			return
+		self.close_output()
+		self.vout = vout
+		self.out_file.label = basename
+		if self.vin:
+			self.vout.setinfo(self.vin.getinfo())
+			self.oshow()
+
+	def close_output(self):
+		if self.vout:
+			self.msg('Closing output file...')
+			self.vout.close()
+		self.msg('')
+		self.vout = None
+		self.out_file.label = '(none)'
+		self.format('out')
+
+	def oshow(self):
+		self.vout.show()
+		self.format('out')
+
+
+	def msg(self, *args):
+		str = string.strip(string.join(args))
+		self.msg_area.label = str
+
+	def err(self, *args):
+		gl.ringbell()
+		apply(self.msg, args)
+
+	def format(self, io):
+		v = getattr(self, 'v' + io)
+		if v == None:
+			left = right = pos = 0
+		else:
+			left, right = v.qsizes()
+			pos = v.tell()
+			left = pos - left
+			right = pos + right
+		getattr(self, io + '_info1').label = `left`
+		getattr(self, io + '_info2').label = `pos`
+		getattr(self, io + '_info3').label = `right`
+
+main()
diff --git a/Demo/sgi/video/VeditForm.fd b/Demo/sgi/video/VeditForm.fd
new file mode 100644
index 0000000..6bffa98
--- /dev/null
+++ b/Demo/sgi/video/VeditForm.fd
@@ -0,0 +1,360 @@
+Magic: 12321
+
+Internal Form Definition File
+    (do not change)
+
+Number of forms: 1
+
+=============== FORM ===============
+Name: form
+Width: 480.000000
+Height: 350.000000
+Number of Objects: 23
+
+--------------------
+class: 1
+type: 1
+box: 0.000000 0.000000 480.000000 350.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: 
+name: 
+callback: 
+argument: 
+
+--------------------
+class: 11
+type: 4
+box: 170.000000 110.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: -> Copy ->
+name: 
+callback: cb_copy
+argument: 0
+
+--------------------
+class: 11
+type: 4
+box: 10.000000 110.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Forward
+name: 
+callback: cb_in_skip
+argument: 0
+
+--------------------
+class: 11
+type: 0
+box: 10.000000 10.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Rewind input
+name: 
+callback: cb_in_rewind
+argument: 0
+
+--------------------
+class: 11
+type: 0
+box: 330.000000 10.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Reset output
+name: 
+callback: cb_out_rewind
+argument: 0
+
+--------------------
+class: 11
+type: 0
+box: 10.000000 260.000000 80.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Input file...
+name: 
+callback: cb_in_new
+argument: 0
+
+--------------------
+class: 11
+type: 0
+box: 330.000000 260.000000 80.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Output file...
+name: 
+callback: cb_out_new
+argument: 0
+
+--------------------
+class: 2
+type: 0
+box: 10.000000 210.000000 140.000000 40.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 11.000000
+lcol: 0
+label: (none)
+name: in_file
+callback: 
+argument: 
+
+--------------------
+class: 2
+type: 0
+box: 330.000000 210.000000 140.000000 40.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 11.000000
+lcol: 0
+label: (none)
+name: out_file
+callback: 
+argument: 
+
+--------------------
+class: 2
+type: 0
+box: 10.000000 160.000000 30.000000 30.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 8.000000
+lcol: 0
+label: 
+name: in_info1
+callback: 
+argument: 
+
+--------------------
+class: 11
+type: 0
+box: 170.000000 260.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Quit
+name: 
+callback: cb_quit
+argument: 0
+
+--------------------
+class: 11
+type: 4
+box: 330.000000 60.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Back
+name: 
+callback: cb_out_back
+argument: 0
+
+--------------------
+class: 11
+type: 4
+box: 10.000000 60.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Back
+name: 
+callback: cb_in_back
+argument: 0
+
+--------------------
+class: 11
+type: 4
+box: 330.000000 110.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Forward
+name: 
+callback: cb_out_skip
+argument: 0
+
+--------------------
+class: 11
+type: 4
+box: 170.000000 60.000000 140.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Uncopy
+name: 
+callback: cb_uncopy
+argument: 0
+
+--------------------
+class: 11
+type: 0
+box: 100.000000 260.000000 50.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Close
+name: 
+callback: cb_in_close
+argument: 0
+
+--------------------
+class: 11
+type: 0
+box: 420.000000 260.000000 50.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Close
+name: 
+callback: cb_out_close
+argument: 0
+
+--------------------
+class: 2
+type: 0
+box: 10.000000 310.000000 460.000000 30.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 11.000000
+lcol: 0
+label: CMIF Video Editor, by Guido van Rossum
+name: msg_area
+callback: 
+argument: 
+
+--------------------
+class: 2
+type: 0
+box: 50.000000 160.000000 60.000004 40.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 11.000000
+lcol: 0
+label: 
+name: in_info2
+callback: 
+argument: 
+
+--------------------
+class: 2
+type: 0
+box: 120.000000 160.000000 30.000000 30.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 8.000000
+lcol: 0
+label: 
+name: in_info3
+callback: 
+argument: 
+
+--------------------
+class: 2
+type: 0
+box: 330.000000 160.000000 30.000000 30.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 8.000000
+lcol: 0
+label: 
+name: out_info1
+callback: 
+argument: 
+
+--------------------
+class: 2
+type: 0
+box: 370.000000 160.000000 60.000004 40.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 11.000000
+lcol: 0
+label: 
+name: out_info2
+callback: 
+argument: 
+
+--------------------
+class: 2
+type: 0
+box: 440.000000 160.000000 30.000000 30.000000
+boxtype: 6
+colors: 47 47
+alignment: 2
+style: 0
+size: 8.000000
+lcol: 0
+label: 
+name: out_info3
+callback: 
+argument: 
+
+==============================
+create_the_forms
diff --git a/Demo/sgi/video/Viewer.py b/Demo/sgi/video/Viewer.py
new file mode 100755
index 0000000..6203562
--- /dev/null
+++ b/Demo/sgi/video/Viewer.py
@@ -0,0 +1,242 @@
+import gl, GL
+import VFile
+import os
+
+
+class InputViewer:
+
+	def init(self, filename, title, qsize):
+		try:
+			self.vin = VFile.VinFile().init(filename)
+		except (EOFError, VFile.Error):
+			raise IOError, 'bad video input file'
+		if not title:
+			title = os.path.split(filename)[1]
+		self.filename = filename
+		self.title = title
+		self.qsize = qsize
+		gl.foreground()
+		gl.prefsize(self.vin.width, self.vin.height)
+		self.wid = -1
+		self.reset()
+		return self
+
+	def close(self):
+		self.vin.close()
+		if self.wid > 0:
+			gl.winclose(self.wid)
+
+	def rewind(self):
+		self.vin.rewind()
+		self.reset()
+
+	def getinfo(self):
+		return self.vin.getinfo()
+
+	# Internal
+	def reset(self):
+		if self.wid > 0:
+			gl.winset(self.wid)
+			gl.clear()
+			self.vin.initcolormap()
+		self.queue = []
+		self.qindex = 0
+		self.lost = 0
+		self.lastt = 0
+		self.eofread = 0
+
+	# Internal
+	def fillq(self):
+		if self.qindex < len(self.queue) or self.eofread: return
+		try:
+			t, d, cd = self.vin.getnextframe()
+		except EOFError:
+			self.eofread = 1
+			return
+		dt = t - self.lastt
+		self.lastt = t
+		self.queue.append(dt, d, cd)
+		while len(self.queue) > self.qsize:
+			del self.queue[0]
+			self.qindex = self.qindex - 1
+			self.lost = self.lost + 1
+
+	def show(self):
+		if self.wid < 0:
+			gl.foreground()
+			gl.prefsize(self.vin.width, self.vin.height)
+			self.wid = gl.winopen(self.title)
+			gl.clear()
+			self.vin.initcolormap()
+		self.fillq()
+		gl.winset(self.wid)
+		if self.qindex >= len(self.queue):
+			gl.clear()
+			return
+		dt, d, cd = self.queue[self.qindex]
+		self.vin.showframe(d, cd)
+
+	def redraw(self, wid):
+		if wid == self.wid >= 0:
+			gl.winset(self.wid)
+			gl.reshapeviewport()
+			self.show()
+
+	def get(self):
+		if self.qindex >= len(self.queue):
+			self.fillq()
+			if self.eofread:
+				return None
+		item = self.queue[self.qindex]
+		self.qindex = self.qindex + 1
+		return item
+
+	def backup(self):
+		if self.qindex == 0:
+			return 0
+		self.qindex = self.qindex - 1
+		return 1
+
+	def tell(self):
+		return self.lost + self.qindex
+
+	def qsizes(self):
+		return self.qindex, len(self.queue) - self.qindex
+
+
+class OutputViewer:
+
+	def init(self, filename, title, qsize):
+		try:
+			self.vout = VFile.VoutFile().init(filename)
+		except (EOFError, VFile.Error):
+			raise IOError, 'bad video output file'
+		if not title:
+			title = os.path.split(filename)[1]
+		self.filename = filename
+		self.title = title
+		self.qsize = qsize
+		gl.foreground()
+		self.wid = -1
+		self.reset()
+		return self
+
+	def close(self):
+		while self.queue:
+			self.flushq()
+		self.vout.close()
+		if self.wid > 0:
+			gl.winclose(self.wid)
+
+	def rewind(self):
+		info = self.vout.getinfo()
+		self.vout.close()
+		self.vout = VFile.VoutFile().init(self.filename)
+		self.vout.setinfo(info)
+		self.reset()
+
+	def getinfo(self):
+		return self.vout.getinfo()
+
+	def setinfo(self, info):
+		if info == self.getinfo(): return # No change
+		self.vout.setinfo(info)
+		if self.wid > 0:
+			gl.winclose(self.wid)
+			self.wid = -1
+
+	# Internal
+	def reset(self):
+		if self.wid > 0:
+			gl.winset(self.wid)
+			gl.clear()
+			self.vout.initcolormap()
+		self.queue = []
+		self.spares = []
+		self.written = 0
+		self.lastt = 0
+
+	# Internal
+	def flushq(self):
+		if self.written == 0:
+			self.vout.writeheader()
+		dt, d, cd = self.queue[0]
+		self.lastt = self.lastt + dt
+		self.vout.writeframe(self.lastt, d, cd)
+		del self.queue[0]
+		self.written = self.written + 1
+
+	def show(self):
+		if self.wid < 0:
+			gl.foreground()
+			gl.prefsize(self.vout.width, self.vout.height)
+			self.wid = gl.winopen(self.title)
+			gl.clear()
+			self.vout.initcolormap()
+		gl.winset(self.wid)
+		if not self.queue:
+			gl.clear()
+			return
+		dt, d, cd = self.queue[-1]
+		self.vout.showframe(d, cd)
+
+	def redraw(self, wid):
+		if wid == self.wid >= 0:
+			gl.winset(self.wid)
+			gl.reshapeviewport()
+			self.show()
+
+	def backup(self):
+		if len(self.queue) < 1: return 0
+		self.spares.insert(0, self.queue[-1])
+		del self.queue[-1]
+		return 1
+
+	def forward(self):
+		if not self.spares: return 0
+		self.queue.append(self.spares[0])
+		del self.spares[0]
+		return 1
+
+	def put(self, item):
+		self.queue.append(item)
+		self.spares = []
+		while len(self.queue) > self.qsize:
+			self.flushq()
+
+	def tell(self):
+		return self.written + len(self.queue)
+
+	def qsizes(self):
+		return len(self.queue), len(self.spares)
+
+
+def test():
+	import sys
+	a = InputViewer().init(sys.argv[1], '')
+	b = OutputViewer().init(sys.argv[2], '')
+	b.setinfo(a.getinfo())
+	
+	while 1:
+		a.show()
+		data = a.get()
+		if data is None:
+			break
+		b.put(data)
+		b.show()
+
+	while a.backup():
+		data = a.get()
+		b.put(data)
+		b.show()
+		if a.backup(): a.show()
+	
+	while 1:
+		data = a.get()
+		if data is None:
+			break
+		b.put(data)
+		b.show()
+		a.show()
+
+	b.close()
diff --git a/Demo/sgi/video/Vplay.py b/Demo/sgi/video/Vplay.py
index 5ab623d..b8d06a1 100755
--- a/Demo/sgi/video/Vplay.py
+++ b/Demo/sgi/video/Vplay.py
@@ -200,13 +200,15 @@
 	vin.magnify = magnify
 
 	if threading:
+		MAXSIZE = 20 # Don't read ahead too much
 		import thread
-		queue = []
+		import Queue
+		queue = Queue.Queue().init(MAXSIZE)
 		stop = []
 		thread.start_new_thread(read_ahead, (vin, queue, stop))
 		# Get the read-ahead thread going
-		while len(queue) < 5 and None not in queue:
-			time.millisleep(10)
+		while queue.qsize() < MAXSIZE/2 and not stop:
+			time.millisleep(100)
 
 	tin = 0
 	told = 0
@@ -227,21 +229,18 @@
 				if debug: sys.stderr.write('\n')
 				if threading:
 					stop.append(None)
-					while len(stop) < 2:
-						time.millisleep(10)
+					while 1:
+						item = queue.get()
+						if item == None: break
 				return (dev != LEFTMOUSE)
 			if dev == REDRAW:
 				gl.reshapeviewport()
 				if data: vin.showframe(data, cdata)
 		if threading:
-			if not queue:
-				if debug: sys.stderr.write('.')
-				time.millisleep(10)
-				continue
-			q0 = queue[0]
-			if q0 == None: break
-			del queue[0]
-			tin, data, cdata = q0
+			if debug and queue.empty(): sys.stderr.write('.')
+			item = queue.get()
+			if item == None: break
+			tin, data, cdata = item
 		else:
 			try:
 				tin, size, csize = vin.getnextframeheader()
@@ -301,13 +300,13 @@
 
 def read_ahead(vin, queue, stop):
 	try:
-		while not stop: queue.append(vin.getnextframe())
+		while not stop: queue.put(vin.getnextframe())
 	except EOFError:
-		queue.append(None)
+		pass
+	queue.put(None)
 	stop.append(None)
 
 
-
 # Don't forget to call the main program
 
 try: