All combinations of video format and capture mode (cont/burst) now
work.  Reorganized a lot of the code, still not satisfied...
diff --git a/Demo/sgi/video/Vb.py b/Demo/sgi/video/Vb.py
index 6d4c4b5..2a46571 100755
--- a/Demo/sgi/video/Vb.py
+++ b/Demo/sgi/video/Vb.py
@@ -2,6 +2,8 @@
 
 # Video bag-of-tricks
 
+# XXX To do: audio; rationalize user interface; ...?
+
 import sys
 import getopt
 import string
@@ -33,9 +35,10 @@
 
 StopCapture = 'StopCapture'
 
-formats = ['rgb8', 'grey8', 'grey4', 'grey2', \
-	   'grey2_dith', 'mono_dith', 'mono_thresh']
-formatmap = {'rgb24': 'rgb', 'grey8': 'grey'}
+Labels = ['rgb8', 'grey8', 'grey4', 'grey2', \
+	  'grey2 dith', 'mono dith', 'mono thresh']
+Formats = ['rgb8', 'grey', 'grey4', 'grey2', \
+	   'grey2', 'mono', 'mono']
 
 class VideoBagOfTricks:
 
@@ -53,22 +56,13 @@
 		fl.set_event_call_back(self.do_event)
 		return self
 
-	def setwatch(self):
-		gl.winset(self.form.window)
-		gl.setcursor(WATCH, 0, 0)
-
-	def setarrow(self):
-		gl.winset(self.form.window)
-		gl.setcursor(ARROW, 0, 0)
-
 	def setdefaults(self):
+		self.mono_thresh = 128
 		self.format = 'rgb8'
 		self.c_format.clear_choice()
-		for label in formats:
+		for label in Labels:
 			self.c_format.addto_choice(label)
-		self.c_format.set_choice(1 + formats.index(self.format))
-		self.mono_thresh = 128
-		self.mono_use_thresh = 0
+		self.get_format()
 		self.b_drop.set_button(1)
 		self.b_burst.set_button(0)
 		self.in_rate.set_input('2')
@@ -77,9 +71,14 @@
 		self.in_file.set_input('film.video')
 
 	def openvideo(self):
-		self.video = sv.OpenVideo()
-		param = [SV.BROADCAST, 0]
-		self.video.GetParam(param)
+		try:
+			self.video = sv.OpenVideo()
+		except sv.error, msg:
+			print 'Error opening video:', msg
+			self.video = None
+			#sys.exit(1)
+		param = [SV.BROADCAST, SV.PAL]
+		if self.video: self.video.GetParam(param)
 		if param[1] == SV.PAL:
 			x = SV.PAL_XMAX
 			y = SV.PAL_YMAX
@@ -118,9 +117,12 @@
 
 	def settitle(self):
 		gl.winset(self.window)
-		gl.wintitle(self.maketitle())
+		x, y = gl.getsize()
+		title = 'Vb:' + self.in_file.get_input() + ' (%dx%d)' % (x, y)
+		gl.wintitle(title)
 
 	def bindvideo(self):
+		if not self.video: return
 		x, y = gl.getsize()
 		self.video.SetSize(x, y)
 		drop = self.b_drop.get_button()
@@ -128,7 +130,7 @@
 			param = [SV.FIELDDROP, 1, SV.GENLOCK, SV.GENLOCK_OFF]
 		else:
 			param = [SV.FIELDDROP, 0, SV.GENLOCK, SV.GENLOCK_ON]
-		if self.getformat()[:3] == 'rgb':
+		if self.rgb:
 			param = param+[SV.COLOR, SV.DEFAULT_COLOR, \
 				       SV.DITHER, 1, \
 				       SV.INPUT_BYPASS, 0]
@@ -150,35 +152,47 @@
 			self.rebindvideo()
 			self.settitle()
 
-	def cb_format(self, *args):
+	def get_format(self):
 		i = self.c_format.get_choice()
-		label = format = formats[i-1]
-		if '_' in format:
-			i = string.find(format, '_')
-			format = format[:i]
-		if formatmap.has_key(format):
-			format = formatmap[format]
+		label = Labels[i-1]
+		format = Formats[i-1]
 		self.format = format
 		#
-		if label == 'mono_thresh':
-			self.mono_use_thresh = 1
+		self.rgb = (format[:3] == 'rgb')
+		self.mono = (format == 'mono')
+		self.grey = (format[:4] == 'grey')
+		self.mono_use_thresh = (label == 'mono thresh')
+		s = format[4:]
+		if s:
+			self.greybits = string.atoi(s)
+		else:
+			self.greybits = 8
+		if label == 'grey2 dith':
+			self.greybits = -2
+		#
+		convertor = None
+		if self.grey:
+			if self.greybits == 2:
+				convertor = imageop.grey2grey2
+			elif self.greybits == 4:
+				convertor = imageop.grey2grey4
+			elif self.greybits == -2:
+				convertor = imageop.dither2grey2
+		self.convertor = convertor
+
+	def cb_format(self, *args):
+		self.get_format()
+		if self.mono_use_thresh:
 			s = `self.mono_thresh`
 			s = fl.show_input('Please enter mono threshold', s)
 			if s:
 				try:
 					self.mono_thresh = string.atoi(s)
 				except string.atoi_error:
-					fl.show_message( \
-						  'Bad input, using ' + \
-						  `self.mono_thresh`)
-		else:
-			self.mono_use_thresh = 0
-		#
+					fl.show_message('Bad input, using', \
+						  `self.mono_thresh`, '')
 		self.rebindvideo()
 
-	def getformat(self):
-		return self.format
-
 	def cb_rate(self, *args):
 		pass
 
@@ -222,39 +236,103 @@
 		gl.ringbell()
 
 	def cb_capture(self, *args):
+		if not self.video:
+			gl.ringbell()
+			return
+		if self.b_burst.get_button():
+			self.burst_capture()
+		else:
+			self.cont_capture()
+
+	def burst_capture(self):
 		self.setwatch()
-		self.g_main.hide_object()
-		self.cb_file() # Make sure filename is OK
-		filename = self.in_file.get_input()
-		format = self.getformat()
-		vout = VFile.VoutFile().init(filename)
-		vout.setformat(format)
 		gl.winset(self.window)
 		x, y = gl.getsize()
-		vout.setsize(x, y)
-		vout.writeheader()
-		convertor = None
-		if format[:4] == 'grey':
-			s = format[4:]
-			if s:
-				greybits = string.atoi(s)
-			else:
-				greybits = 8
-			# XXX Should get this from somewhere else?
-			if greybits == 2:
-				convertor = imageop.grey2grey2
-			elif greybits == 4:
-				convertor = imageop.grey2grey4
-			elif greybits == -2:
-				convertor = imageop.dither2grey2
-		mono = (format == 'mono')
 		vformat = SV.RGB8_FRAMES
-		qsize = 0
-		rate = eval(self.in_rate.get_input())
+		try:
+			nframes = string.atoi(self.in_nframes.get_input())
+		except string.atoi_error:
+			nframes = 0
+		if nframes == 0:
+			try:
+				maxmem = \
+				  float(eval(self.in_maxmem.get_input()))
+			except:
+				maxmem = 1.0
+			memsize = int(maxmem * 1024 * 1024)
+			nframes = calcnframes(x, y, \
+				  self.mono or self.grey, memsize)
+			print 'nframes =', nframes
+		rate = string.atoi(self.in_rate.get_input())
+		# XXX Should check ranges and not crash if non-integer
+		info = (vformat, x, y, nframes, rate)
+		try:
+			info2, data, bitvec = self.video.CaptureBurst(info)
+		except sv.error, msg:
+			fl.show_message('Capture error:', str(msg), '')
+			self.setarrow()
+			return
+		print info2
+		self.save_burst(info2, data, bitvec)
+		self.setarrow()
+
+	def save_burst(self, info, data, bitvec):
+		(vformat, x, y, nframes, rate) = info
+		self.open_file()
+		fieldsize = x*y/2
+		nskipped = 0
+		realframeno = 0
+		tpf = 1000 / 50.0     #XXXX
+		# Trying to find the pattern in frame skipping
+		okstretch = 0
+		skipstretch = 0
+		for frameno in range(0, nframes*2):
+			if frameno <> 0 and \
+				  bitvec[frameno] == bitvec[frameno-1]:
+				nskipped = nskipped + 1
+				if okstretch:
+					#print okstretch, 'ok',
+					okstretch = 0
+				skipstretch = skipstretch + 1
+				continue
+			if skipstretch:
+				#print skipstretch, 'skipped'
+				skipstretch = 0
+			okstretch = okstretch + 1
+			#
+			# Save field.
+			# XXXX Works only for fields and top-to-bottom
+			#
+			start = frameno*fieldsize
+			field = data[start:start+fieldsize]
+			realframeno = realframeno + 1
+			fn = int(realframeno*tpf)
+			if not self.write_frame(fn, field):
+				break
+		#print okstretch, 'ok',
+		#print skipstretch, 'skipped'
+		#print 'Skipped', nskipped, 'duplicate frames'
+		self.close_file()
+
+	def cont_capture(self):
+		self.setwatch()
+		self.g_main.hide_object()
+		self.open_file()
+		vformat = SV.RGB8_FRAMES
+		qsize = 1 # XXX Should be an option?
+		try:
+			rate = string.atoi(self.in_rate.get_input())
+		except string.atoi_error:
+			rate = 2
+		x, y = self.vout.getsize()
 		info = (vformat, x, y, qsize, rate)
 		ids = []
-		tpf = 50
-		self.video.InitContinuousCapture(info)
+		fps = 59.64 # Fields per second
+		# XXX (fps of Indigo monitor, not of PAL or NTSC!)
+		tpf = 1000.0 / fps # Time per field in msec
+		info2 = self.video.InitContinuousCapture(info)
+		if info2 <> info:
+			print 'Info mismatch: requested', info, 'got', info2
 		self.capturing = 1
 		self.g_stop.show_object()
 		self.setarrow()
@@ -273,37 +351,75 @@
 			data = cd.InterleaveFields(1)
 			cd.UnlockCaptureData()
 			t = id*tpf
-			if convertor:
-				data = convertor(data, len(data), 1)
-			elif mono:
-				if self.mono_use_thresh:
-					data = imageop.grey2mono(data, \
-						  len(data), 1,\
-						  self.mono_thresh)
-				else:
-					data = imageop.dither2mono(data, \
-						  len(data), 1)
-			try:
-				vout.writeframe(t, data, None)
-			except IOError, msg:
-				if msg == (0, 'Error 0'):
-					msg = 'disk full??'
-				fl.show_message('IOError: ' + str(msg))
+			if not self.write_frame(t, data):
 				break
 		self.setwatch()
 		self.g_stop.hide_object()
 		self.capturing = 0
-		vout.close()
 		self.video.EndContinuousCapture()
+		self.close_file()
 		self.g_main.show_object()
 		self.setarrow()
 
+	def open_file(self):
+		gl.winset(self.window)
+		x, y = gl.getsize()
+		self.cb_file() # Make sure filename is OK
+		filename = self.in_file.get_input()
+		vout = VFile.VoutFile().init(filename)
+		vout.setformat(self.format)
+		vout.setsize(x, y)
+		if self.b_burst.get_button():
+			vout.setpf((1, -2))
+		vout.writeheader()
+		self.vout = vout
+
+	def close_file(self):
+		try:
+			self.vout.close()
+		except IOError, msg:
+			if msg == (0, 'Error 0'):
+				msg = 'disk full??'
+			fl.show_message('IOError', str(msg), '')
+		del self.vout
+
+	def write_frame(self, t, data):
+		if self.convertor:
+			data = self.convertor(data, len(data), 1)
+		elif self.mono:
+			if self.mono_use_thresh:
+				data = imageop.grey2mono(data, \
+					  len(data), 1,\
+					  self.mono_thresh)
+			else:
+				data = imageop.dither2mono(data, \
+					  len(data), 1)
+		try:
+			self.vout.writeframe(t, data, None)
+		except IOError, msg:
+			if msg == (0, 'Error 0'):
+				msg = 'disk full??'
+			fl.show_message('IOError', str(msg), '')
+			return 0
+		return 1
+
 	def cb_quit(self, *args):
 		raise SystemExit, 0
 
-	def maketitle(self):
-		x, y = gl.getsize()
-		return 'Vb:' + self.in_file.get_input() + ' (%dx%d)' % (x, y)
+	def setwatch(self):
+		gl.winset(self.form.window)
+		gl.setcursor(WATCH, 0, 0)
+
+	def setarrow(self):
+		gl.winset(self.form.window)
+		gl.setcursor(ARROW, 0, 0)
+
+def calcnframes(x, y, grey, memsize):
+	pixels = x*y
+	pixels = pixels/2		# XXX always assume fields
+	if grey: n = memsize/pixels
+	else: n = memsize/(4*pixels)
+	return max(1, n)
 
 try:
 	main()