#!/usr/bin/env python3
# Video4Linux Capture Demo in python3
# Copyright (c) 2010-2024 Sensoray Co., Inc.
"""
Python demo for the Sensoray Model 2253, 953
"""


import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
import os, ctypes, glob, threading, subprocess, time, select, string, sys, socket
from v4l2 import *
from fcntl import *
from mmap import *


V4L2_PIX_FMT_MP4V = 0x5634504D
V4L2_PIX_FMT_MPEG4 = v4l2_fourcc('M', 'P', 'G', '4')
V4L2_PIX_FMT_H264 = 0x34363248
V4L2_PIX_FMT_MP42 = 0x3234504D
V4L2_PIX_FMT_MP2V = 0x5632504D
V4L2_PIX_FMT_MPEG2 = v4l2_fourcc('M', 'P', 'G', '2')
V4L2_MPEG_VIDEO_ENCODING_MPEG_4 = 3

debug = False
for arg in sys.argv:
	if arg == "-d":
		debug = True


def dprint(*args):
	if debug:
		for x in args:
			print(x,)
		print

def make_combo_callback1(fn, values):
	return lambda cb: fn(cb, values[cb.get_active()])

def make_combo_callback(fn, id, values):
	return lambda cb: fn(cb, id, values[cb.get_active()])

def format_k(scale, value):
	if value >= 1000000:
		return "%.0d,%03.0dK" % (value / 1000000, (value / 1000) % 1000)
	if value >= 100000:
		return "%.0dK" % (value / 1000)
	return "%d" % value

class MplayerThread(threading.Thread):
	def __init__(self, name, outfmt, width, height, norm, input, demo):
		threading.Thread.__init__(self)
		self.devname = name
		self.outfmt = outfmt
		self.width = width
		self.height = height
		self.norm = norm
		self.input = input
		self.action = "Previewing"
		self.demo = demo
	def run(self):
		cmd = ""
		if self.outfmt == V4L2_PIX_FMT_NV12:
			cmd += "outfmt=nv12:"
		elif self.outfmt == V4L2_PIX_FMT_YUYV:
			cmd += "outfmt=yuy2:"
		elif self.outfmt == V4L2_PIX_FMT_UYVY:
			cmd += "outfmt=uyvy:"
		elif self.outfmt == V4L2_PIX_FMT_RGB24:
			cmd += "outfmt=rgb24:"
		elif self.outfmt == V4L2_PIX_FMT_RGB565:
			cmd += "outfmt=rgb16:"
		elif self.outfmt != None: cmd += "outfmt=0x%x:" % self.outfmt
		if self.width != None: cmd += "width=%d:" % self.width
		if self.height != None: cmd += "height=%d:" % self.height
		if self.norm != None: cmd += "norm=%s:" % self.norm
		if self.devname != None: cmd += "device=%s:" % self.devname
		if self.input != None: cmd += "input=%s:" % self.input
		if cmd != "": cmd = " -tv " + cmd
		if debug: cmd += " -quiet"
		else: cmd += " -really-quiet"
		cmd = "mplayer tv://" + cmd
		dprint(cmd)
		try: self.p = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
		except: self.demo.message_box("Could not open mplayer")
		try: self.p.wait()
		except: pass
		self.demo.update_status(self.devname, "Status: Stopped")
	def stop(self):
		try: self.p.communicate("q".encode('utf-8'))
		except: pass

class ffmpegThread(threading.Thread):
	def __init__(self, name, outname, outfmt, width, height, norm, input, demo):
		threading.Thread.__init__(self)
		self.devname = name
		self.outname = outname
		self.outfmt = outfmt
		self.width = width
		self.height = height
		self.norm = norm
		self.input = input
		self.action = "Capturing AVI"
		self.demo = demo
	def run(self):
		cmd = "ffmpeg -s %dx%d" % (self.width, self.height)
		if self.outfmt == V4L2_PIX_FMT_MJPEG or self.outfmt == V4L2_PIX_FMT_JPEG:
			cmd += " -f mjpeg -analyzeduration 0"
		elif self.outfmt == V4L2_PIX_FMT_H264:
			cmd += " -f h264 -analyzeduration 0"
		elif self.outfmt == V4L2_PIX_FMT_MP4V or self.outfmt == V4L2_PIX_FMT_MPEG4:
			cmd += " -f m4v -analyzeduration 0 "
		elif self.outfmt == V4L2_PIX_FMT_NV12:
			cmd += " -f video4linux2 -pix_fmt nv12"
		elif self.outfmt == V4L2_PIX_FMT_MP42:
			cmd += " -f mp4 -analyzeduration 0 "
			
		if self.outfmt == V4L2_PIX_FMT_UYVY:
			cmd += " -f video4linux2 -pix_fmt uyvy422"
		elif self.outfmt == V4L2_PIX_FMT_YUYV:
			cmd += " -f video4linux2 -pix_fmt yuyv422"
		elif self.outfmt == V4L2_PIX_FMT_GREY:
			cmd += " -f video4linux2 -pix_fmt gray"
		if self.norm == "NTSC":
			cmd += " -r 29.97"
		elif self.norm == "PAL":
			cmd += " -r 25"

		cmd += " -i " + self.devname + " -vcodec copy -acodec copy -y " + self.outname
		dprint(cmd)
		status = "Status: Stopped"
		try: self.p = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
		except: status = "Could not open ffmpeg"
		try: self.p.wait()
		except: pass
		self.demo.update_status(self.devname, status)
	def stop(self):
		try: self.p.communicate("q".encode('utf-8'))
		except: pass


class OldCaptureThread(threading.Thread):
	def __init__(self, name, outname, outfmt, width, height, norm, input, oneshot, demo):
		threading.Thread.__init__(self)
		self.devname = name
		self.outname = outname
		self.outfmt = outfmt
		self.width = width
		self.height = height
		self.norm = norm
		self.input = input
		self.action = "Capturing"
		self.quit = False
		self.oneshot = oneshot
		self.demo = demo
	def run(self):
		self.do_capture()
		if self.oneshot:
			self.demo.update_status(self.devname, "Status: Saved snapshot " + self.outname)
		else:
			self.demo.update_status(self.devname, "Status: Stopped")
	
	def do_capture(self):
		try: vd = os.open(self.devname, os.O_RDWR)
		except:
			print("Device "+self.devname+" not available or does not support multiple open")
			return
		try: 
			outf = open(self.outname, "w")
		except:
			print("Unable to open output file")
			return
			
		format = v4l2_format()
		format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
		format.fmt.pix.pixelformat = self.outfmt
		format.fmt.pix.width = self.width
		format.fmt.pix.height = self.height
		format.fmt.pix.bytesperline = 0
		format.fmt.pix.field = V4L2_FIELD_ANY
		try: ioctl(vd, VIDIOC_S_FMT, format)
		except:
			print("VIDIOC_S_FMT failed")
			vd.close()
			return
			
		reqbufs = v4l2_requestbuffers()
		reqbufs.count = 4
		reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
		reqbufs.memory = V4L2_MEMORY_MMAP
		try: ioctl(vd, VIDIOC_REQBUFS, reqbufs)
		except IOError:
			print("VIDIOC_REQBUFS failed")
			vd.close()
			return
		lengths = []
		mmaps = []
		for i in range(0, reqbufs.count - 1):
			buf = v4l2_buffer()
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
			buf.memory = V4L2_MEMORY_MMAP
			buf.index = i
			try: ioctl(vd, VIDIOC_QUERYBUF, buf)
			except IOError: print("VIDIOC_QUERYBUF failed")
			lengths.append(buf.length)
			start = mmap(vd, buf.length, offset=buf.m.offset)
			mmaps.append(start)
			try: ioctl(vd, VIDIOC_QBUF, buf)
			except IOError: print("VIDIOC_QBUF failed")

		type = ctypes.c_int(V4L2_BUF_TYPE_VIDEO_CAPTURE)
		try: ioctl(vd, VIDIOC_STREAMON, type)
		except IOError: print("VIDIOC_STREAMON failed")
		dprint("Capturing started")
		
		poll = select.poll()
		poll.register(vd, select.POLLIN)
		while not self.quit:
			if poll.poll(5000) == []:
				print("Poll timed out")
				break
			buf = v4l2_buffer()
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
			buf.memory = V4L2_MEMORY_MMAP
			try: ioctl(vd, VIDIOC_DQBUF, buf)
			except IOError: 
				print("VIDIOC_DQBUF error")
				break
			mmaps[buf.index].seek(0)
			outf.write(mmaps[buf.index].read(buf.bytesused))
			try: ioctl(vd, VIDIOC_QBUF, buf)
			except IOError: print("VIDIOC_QBUF failed")
			if self.oneshot:
				break
		type = ctypes.c_int(V4L2_BUF_TYPE_VIDEO_CAPTURE)
		try: ioctl(vd, VIDIOC_STREAMOFF, type)
		except IOError: print("VIDIOC_STREAMOFF failed")
		os.close(vd)
		outf.close()
		dprint("Capturing stopped")
		
	def stop(self):
		self.quit = True


class CaptureThread(threading.Thread):
	def __init__(self, name, outname, outfmt, videnc, width, height, norm, input, oneshot, demo, ip = None, port = None):
		threading.Thread.__init__(self)
		self.devname = name
		self.outname = outname
		self.outfmt = outfmt
		self.videnc = videnc
		self.width = width
		self.height = height
		self.norm = norm
		self.input = input
		self.action = 'Capturing'
		self.quit = False
		self.oneshot = oneshot
		self.demo = demo
		self.sock = None
		if ip:
			self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
			self.sock.connect((ip, int(port)))
			self.action = 'Streaming'
			self.outname = '-'
			
	def run(self):
		cmd = [sys.path[0] + "/capture"]
		cmd.append('-d')
		cmd.append(self.devname)
		cmd.append('-o')
		cmd.append(self.outname)
		cmd.append('-f')
		if self.oneshot:
			cmd.append('1')
		else:
			cmd.append('0')
		cmd.append('-s')
		cmd.append('%dx%d' % (self.width, self.height))
		if self.norm == "PAL":
			cmd.append('-p')

		if self.outfmt == V4L2_PIX_FMT_NV12:
			cmd.append('-n')
		elif self.outfmt == V4L2_PIX_FMT_YUYV:
			cmd.append('-y')
		elif self.outfmt == V4L2_PIX_FMT_UYVY:
			cmd.append('-u')
		elif self.outfmt == V4L2_PIX_FMT_GREY:
			cmd.append('-8')
		elif self.outfmt == V4L2_PIX_FMT_MP4V or self.outfmt == V4L2_PIX_FMT_MPEG4:
			cmd.append('-4')
		elif self.outfmt == V4L2_PIX_FMT_H264:
			cmd.append('-x')
		elif self.outfmt == V4L2_PIX_FMT_MP2V or self.outfmt == V4L2_PIX_FMT_MPEG2:
			cmd.append('-2')
		elif self.outfmt == V4L2_PIX_FMT_MJPEG or self.outfmt == V4L2_PIX_FMT_JPEG:
			cmd.append('-J')
		elif self.outfmt == V4L2_PIX_FMT_MP42:
			if self.sock:
				cmd.append('-z')
			else:
				cmd.append('-m')
		elif self.outfmt == V4L2_PIX_FMT_MPEG:
			cmd.append('-t')
		#elif self.outfmt == V4L2_PIX_FMT_MPEG:
		#	cmd.append('-T')
		if self.outfmt == V4L2_PIX_FMT_MP42 or self.outfmt == V4L2_PIX_FMT_MPEG:
			if self.videnc == V4L2_MPEG_VIDEO_ENCODING_MPEG_2:
				cmd.append('-2')
			elif self.videnc == V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC:
				cmd.append('-x')
			elif self.videnc == V4L2_MPEG_VIDEO_ENCODING_MPEG_4:
				cmd.append('-4')
		dprint(' '.join(cmd))
		print(' '.join(cmd))

		status = "Status: Stopped"
		if self.sock:
			pr, pw = os.pipe()
			poll = select.poll()
			poll.register(pr, select.POLLIN)
			try:
				self.process = subprocess.Popen(cmd, stdout=pw)
			except:
				self.demo.update_status(self.devname, 
					"Could not open capture program - check sdk installation")
				return
			while self.process.returncode == None:
				self.process.poll()
				if poll.poll(100) == []:
					continue
				data = os.read(pr, 1400)
				try:
					self.sock.send(data)
				except:
					print("Socket send failed")
					status = "Status: Socket send failed"
					self.process.terminate()
					break
			try: self.process.wait()
			except: pass
			os.close(pr)
			os.close(pw)
			self.sock.close()
		else: 
			try:
				self.process = subprocess.Popen(cmd)
			except:
				self.demo.update_status(self.devname, 
					"Could not open capture program - check sdk installation")
				return
			try: self.process.wait()
			except: pass
		if self.process.returncode > 0:
			status = "Status: Capture Error"
		self.demo.update_status(self.devname, status)
	def stop(self):
		try: self.process.terminate()
		except: pass
		
		

class PlaybackThread(threading.Thread):
	def __init__(self, name, norm, filename, demo):
		threading.Thread.__init__(self)
		self.devname = name
		self.filename = filename
		self.norm = norm
		self.action = "Playing"
		self.quit = False
		self.demo = demo
	def run(self):
		cmd = [sys.path[0] + '/playback']
		cmd.append('-d')
		cmd.append(self.devname)
		if self.norm == "PAL":
			cmd.append('-p')
		cmd.append(self.filename)
		dprint(' '.join(cmd))
		status = "Status: Playback error"
		try:
			self.process = subprocess.Popen(cmd)
			try: self.process.wait()
			except: pass
			if self.process.returncode <= 0:
				status = "Status: Stopped"
		except: status = "Could not open playback program - check sdk installation"
		self.demo.update_status(self.devname, status)
	def stop(self):
		try: self.process.terminate()
		except: pass


class Demo:
	def delete_event(self, widget, event, data=None):
		return False

	def destroy(self, widget, data=None):
		Gtk.main_quit()
		if self.vd != None:
			self.vd.close()

	def set_dev(self, widget, dev):
		if self.vd != None:
			self.vd.close()
		try: self.vd = open(dev, "rb+", buffering=0)
		except: self.vd = None
		
		# clear the window and reenumerate the widgets
		self.window.remove(self.window.get_child())
		self.window.add(self.setup())

	def set_dim(self, widget, data):
		self.width = data[0]
		self.height = data[1]
		#dprint("set_dim", self.width, self.height)
		format = v4l2_format()
		format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
		try: ioctl(self.vd, VIDIOC_G_FMT, format)
		except IOError: return
		format.fmt.pix.width = self.width
		format.fmt.pix.height = self.height
		format.fmt.pix.bytesperline = 0
		dprint("new dim is %dx%d" % (self.width, self.height))
		try: 
			ioctl(self.vd, VIDIOC_S_FMT, format)
		except IOError:
			print("Unable to set format")

	def set_crop(self, widget, data):
		#dprint("set_crop", self.width, self.height)
		crop = v4l2_crop()
		crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
		crop.c.left = data[0]
		crop.c.top = data[1]
		crop.c.width = data[2]
		crop.c.height = data[3]
		try: 
			ioctl(self.vd, VIDIOC_S_CROP, crop)
		except IOError:
			print("Unable to set crop")

	def set_format(self, widget, data=None):
		format = v4l2_format()
		format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
		try: ioctl(self.vd, VIDIOC_G_FMT, format)
		except IOError: return
		format.fmt.pix.pixelformat = data
		format.fmt.pix.field = V4L2_FIELD_ANY
		format.fmt.pix.bytesperline = 0
		dprint("new format is %x" % (data))
		try: 
			ioctl(self.vd, VIDIOC_S_FMT, format)
			self.outfmt = data
		except IOError:
			print("Unable to set format")
		# clear the window and reenumerate the widgets
		self.window.remove(self.window.get_child())
		self.window.add(self.setup())

	def set_input(self, widget, data=None):
		dprint("new input is %d" % (data))
		input = ctypes.c_int(data)
		self.input = int(data)
		try: ioctl(self.vd, VIDIOC_S_INPUT, input)
		except IOError: return

	def set_output(self, widget, data=None):
		dprint("new output is %d" % (data))
		output = ctypes.c_int(data)
		self.output = int(data)
		try: ioctl(self.vd, VIDIOC_S_OUTPUT, output)
		except IOError: return

	def set_audio_input(self, widget, data=None):
		dprint("new audio input is %d" % (data))
		audio = v4l2_audio()
		try: ioctl(self.vd, VIDIOC_G_AUDIO, audio)
		except IOError: return
		audio.index = data
		try: ioctl(self.vd, VIDIOC_S_AUDIO, audio)
		except IOError: return

	def set_audio_mode(self, widget, data=None):
		dprint("new audio mode is %d" % (data))
		audio = v4l2_audio()
		try: ioctl(self.vd, VIDIOC_G_AUDIO, audio)
		except IOError: return
		audio.mode = data
		try: ioctl(self.vd, VIDIOC_S_AUDIO, audio)
		except IOError: return

	def set_audio_output(self, widget, data=None):
		dprint("new audio input is %d" % (data))
		audio = v4l2_audioout()
		audio.index = data
		try: ioctl(self.vd, VIDIOC_S_AUDOUT, audio)
		except IOError: return

	def set_frame_interval(self, widget, index):
		frmivalenum = v4l2_frmivalenum()
		frmivalenum.index = index
		frmivalenum.pixel_format = self.outfmt
		frmivalenum.width = self.width
		frmivalenum.height = self.height
		try: ioctl(self.vd, VIDIOC_ENUM_FRAMEINTERVALS, frmivalenum)
		except IOError: return
		dprint("new frame interval index is %d %d/%d" % (index, 
			frmivalenum.discrete.numerator,
			frmivalenum.discrete.denominator))
		cp = v4l2_capability()
		try: ioctl(self.vd, VIDIOC_QUERYCAP, cp)
		except IOError: return
		streamparm = v4l2_streamparm()
		if cp.capabilities & V4L2_CAP_DEVICE_CAPS:
			cp.capabilities = cp.device_caps
		if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
			streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
			streamparm.parm.capture.timeperframe.numerator = frmivalenum.discrete.numerator
			streamparm.parm.capture.timeperframe.denominator = frmivalenum.discrete.denominator
		elif cp.capabilities & V4L2_CAP_VIDEO_OUTPUT:
			streamparm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT
			streamparm.parm.output.timeperframe.numerator = frmivalenum.discrete.numerator
			streamparm.parm.output.timeperframe.denominator = frmivalenum.discrete.denominator
		try:
			ioctl(self.vd, VIDIOC_S_PARM, streamparm)
		except IOError: return

	def set_std(self, widget, data=None):
		dprint("new standard is %d" % (data))
		std = v4l2_std_id(data)
		if (std.value & V4L2_STD_NTSC):
			self.norm = "NTSC"
		elif (std.value & V4L2_STD_PAL):
			self.norm = "PAL"
		try: ioctl(self.vd, VIDIOC_S_STD, std)
		except IOError: print("unable to set standard")
		self.window.remove(self.window.get_child())
		self.window.add(self.setup())
	
	def set_ctrl(self, widget, id, value):
		if V4L2_CTRL_ID2CLASS(id) == V4L2_CTRL_CLASS_USER:
			ctrl = v4l2_control()
			ctrl.id = id
			ctrl.value = value
			dprint("new ctrl %x is %x" % (id, ctrl.value))
			try: ioctl(self.vd, VIDIOC_S_CTRL, ctrl)
			except IOError: print("unable to set control")
		else:
			ctrl = v4l2_ext_control()
			ctrl.id = id
			ctrl.value = value
			ctrls = v4l2_ext_controls()
			ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(id)
			ctrls.count = 1
			ctrls.controls = ctypes.pointer(ctrl)
			dprint("new ext ctrl %x is %x" % (id, ctrl.value))
			try: ioctl(self.vd, VIDIOC_S_EXT_CTRLS, ctrls)
			except IOError: print("unable to set ext control")
		return ctrl.value

	def get_ctrl(self, cid):
		ctrl = v4l2_control()
		ctrl.id = cid
		try: ioctl(self.vd, VIDIOC_G_CTRL, ctrl)
		except IOError: print("unable to get control")
		return ctrl.value

	def set_str_ctrl(self, widget, id, entry=None):
		if entry == None:
			entry = widget
		ctrl = v4l2_ext_control()
		ctrl.id = id
		cstr = ctypes.c_char_p(entry.get_text().encode('utf-8'))
		ctrl.reserved = ctypes.cast(cstr, ctypes.c_void_p)
		ctrls = v4l2_ext_controls()
		ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(id)
		ctrls.count = 1
		ctrls.controls = ctypes.pointer(ctrl)
		dprint("new ext ctrl %x is %x" % (ctrl.id, ctrl.reserved))
		try: ioctl(self.vd, VIDIOC_S_EXT_CTRLS, ctrls)
		except IOError: print("unable to set ext control")

	def set_opt(self, widget, opt, value):
		dprint("set_active %d" % (value))
		opt.set_active(value)
		opt.get_menu().get_active().activate()
	
	def set_toggle(self, widget, id):
		self.set_ctrl(widget, id, int(widget.get_active()))
		
	def set_audmode(self, widget, id):
		audio = v4l2_audio()
		audio.index = 0
		try: ioctl(self.vd, VIDIOC_G_AUDIO, audio)
		except IOError: return
		if widget.get_active():
			audio.mode |= V4L2_AUDMODE_AVL
		else:
			audio.mode &= ~V4L2_AUDMODE_AVL
		try: ioctl(self.vd, VIDIOC_S_AUDIO, audio)
		except IOError: print("Unable to set audio mode")


	def set_jpegcomp(self, widget, adj):
		jpegcomp = v4l2_jpegcompression()
		jpegcomp.quality = int(widget.value)
		dprint("new jpeg quality is", jpegcomp.quality)
		try: ioctl(self.vd, VIDIOC_S_JPEGCOMP, jpegcomp)
		except IOError:
			print("unable to set jpeg quality")
			try: 
				ioctl(self.vd, VIDIOC_G_JPEGCOMP, jpegcomp)
				adj.value = jpegcomp.quality
			except IOError:
				print("unable to get jpeg quality")
	def set_crop(self, widget, val):
		crop = v4l2_crop()
		crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
		crop.c.left = val[0]
		crop.c.top = val[1]
		crop.c.width = val[2]
		crop.c.height = val[3]
		try: ioctl(self.vd, VIDIOC_S_CROP, crop)
		except IOError:
			print("unable to set crop")
		
	def message_box(self, msg):
		msg = Gtk.MessageDialog(parent=self.window,
			modal=True,
			message_type=Gtk.MessageType.INFO,
			buttons=Gtk.ButtonsType.CLOSE,
			text=msg)
		msg.show()
		msg.run()
		msg.destroy()
		
	def message_box_yes_no(self, msg):
		msg = Gtk.MessageDialog(parent=self.window,
			modal=True,
			message_type=Gtk.MessageType.INFO,
			buttons=Gtk.ButtonsType.YES_NO,
			text=msg)
		msg.show()
		result = msg.run()
		msg.destroy()
		return result == Gtk.ResponseType.YES
		
	
	def is_device_busy(self):
		for thread in self.threads:
			if thread.devname == self.vd.name:
				if thread.is_alive():
					self.message_box("The device is busy. ("+thread.action+")")
					return True
				self.threads.remove(thread)
				break
		return False
	
	def take_snapshot(self, widget):
		if self.is_device_busy():
			return

		# Generate a unique filename
		i = 0
		while True:
			name = "out_%d.jpg" % i
			try: os.stat(name)
			except: break
			i += 1
		thread = CaptureThread(self.vd.name, name, V4L2_PIX_FMT_JPEG, -1, self.width, self.height, self.norm, self.input, True, self)
		self.threads.append(thread)
		thread.start()

	def launch_mplayer(self, widget):
		if self.is_device_busy():
			return
		if self.outfmt == V4L2_PIX_FMT_MP4V \
		or self.outfmt == V4L2_PIX_FMT_MPEG4 \
		or self.outfmt == V4L2_PIX_FMT_MP2V \
		or self.outfmt == V4L2_PIX_FMT_MPEG2 \
		or self.outfmt == V4L2_PIX_FMT_MP42 \
		or self.outfmt == V4L2_PIX_FMT_MPEG:
			if not self.message_box_yes_no("The format %s is not recommended for preview.\n\nPreview anyway?" % (self.outfmt_name)):
				return
		thread = MplayerThread(self.vd.name, self.outfmt, self.width, self.height, self.norm, self.input, self)
		self.threads.append(thread)
		thread.start()
		self.status.set_text("Status: Previewing")
		return

	def get_videnc(self):
		ctrl = v4l2_ext_control()
		ctrl.id = V4L2_CID_MPEG_VIDEO_ENCODING
		ctrls = v4l2_ext_controls()
		ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(ctrl.id)
		ctrls.count = 1
		ctrls.controls = ctypes.pointer(ctrl)
		try: 
			ioctl(self.vd, VIDIOC_G_EXT_CTRLS, ctrls)
		except IOError: print("unable to get ext control")
		return ctrl.value

	def launch_capture(self, widget):
		if self.is_device_busy():
			return
		name = "output"
		videnc = self.get_videnc()
		if self.outfmt == V4L2_PIX_FMT_NV12:
			name += ".nv12"
		elif self.outfmt == V4L2_PIX_FMT_YUYV:
			name += ".yuyv"
		elif self.outfmt == V4L2_PIX_FMT_UYVY:
			name += ".uyvy"
		elif self.outfmt == V4L2_PIX_FMT_GREY:
			name += ".grey"
		elif self.outfmt == V4L2_PIX_FMT_MP4V or self.outfmt == V4L2_PIX_FMT_MPEG4:
			name += ".m4v"
		elif self.outfmt == V4L2_PIX_FMT_H264:
			name += ".h264"
		elif self.outfmt == V4L2_PIX_FMT_MPEG:
			name += ".ts"
		elif self.outfmt == V4L2_PIX_FMT_MP2V or self.outfmt == V4L2_PIX_FMT_MPEG2:
			name += ".m2v"
		elif self.outfmt == V4L2_PIX_FMT_MJPEG or self.outfmt == V4L2_PIX_FMT_JPEG:
			name += ".mjpg"
		elif self.outfmt == V4L2_PIX_FMT_MP42:
			name += ".mp4"
		else:
			name += ".dat"
		save = Gtk.FileChooserDialog(title="Save Capture As...",
			parent=self.window, action=Gtk.FileChooserAction.SAVE)
		save.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT)
		save.set_default_response(1)
		save.set_do_overwrite_confirmation(True)
		save.set_current_name(name)
		if self.default_folder:
			save.set_current_folder(self.default_folder)
		save.show()
		if save.run() != Gtk.ResponseType.ACCEPT:
			save.destroy()
			return
		self.default_folder = save.get_current_folder()
		thread = CaptureThread(self.vd.name, save.get_filename(), self.outfmt, videnc, self.width, self.height, self.norm, self.input, False, self)
		save.destroy()
		self.threads.append(thread)
		thread.start()
		self.status.set_text("Status: Capturing")
		return

	def launch_stream(self, widget):
		if self.is_device_busy():
			return
		videnc = self.get_videnc()
		dialog = Gtk.Dialog(title="UDP Stream...", 
                	transient_for=self.window, 
			modal=True,
			destroy_with_parent=True)
		dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
		label=Gtk.Label(label="Address")
		label.show()
		dialog.vbox.pack_start(label, False, False, 0)
		ip = Gtk.Entry()
		if self.default_stream_ip:
			ip.set_text(self.default_stream_ip)
		else:
			ip.set_text('127.0.0.1')
		ip.show()
		dialog.vbox.pack_start(ip, False, False, 0)
		label=Gtk.Label(label="UDP Port")
		label.show()
		dialog.vbox.pack_start(label, False, False, 0)
		port = Gtk.Entry()
		if self.default_stream_port:
			port.set_text(self.default_stream_port)
		else:
			port.set_text('1234')
		port.show()
		dialog.vbox.pack_start(port, False, False, 0)
		dialog.show()
		if dialog.run() != Gtk.ResponseType.ACCEPT:
			dialog.destroy()
			return
		self.default_stream_ip = ip.get_text()
		self.default_stream_port = port.get_text()
		thread = CaptureThread(self.vd.name, '-', self.outfmt, videnc, self.width, self.height, self.norm, self.input, False, self, ip.get_text(), port.get_text())
		dialog.destroy()
		self.threads.append(thread)
		self.status.set_text("Status: Streaming")
		thread.start()
		return

	def launch_capture_avi(self, widget):
		if self.is_device_busy():
			return
		name = "output"
		if self.outfmt == V4L2_PIX_FMT_NV12:
			name += "_nv12.avi"
		elif self.outfmt == V4L2_PIX_FMT_YUYV:
			name += "_yuyv.avi"
		elif self.outfmt == V4L2_PIX_FMT_UYVY:
			name += "_uyvy.avi"
		elif self.outfmt == V4L2_PIX_FMT_GREY:
			name += "_grey.avi"
		elif self.outfmt == V4L2_PIX_FMT_MP4V or self.outfmt == V4L2_PIX_FMT_MPEG4:
			name += "_mpeg4.avi"
		elif self.outfmt == V4L2_PIX_FMT_MP2V or self.outfmt == V4L2_PIX_FMT_MPEG2:
			name += "_mpeg2.avi"
		elif self.outfmt == V4L2_PIX_FMT_H264:
			name += "_h264.avi"
		elif self.outfmt == V4L2_PIX_FMT_MJPEG or self.outfmt == V4L2_PIX_FMT_JPEG:
			name += "_mjpeg.avi"
		elif self.outfmt == V4L2_PIX_FMT_MP42:
			name += ".avi"
			if not self.message_box_yes_no("The MP4 fragmented format is not recommended for capturing to AVI. Capture anyway?"):
				return
		else:
			name += ".avi"
		save = Gtk.FileChooserDialog(title="Save Capture As...", 
			parent=self.window, action=Gtk.FileChooserAction.SAVE)
		save.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT)
		save.set_default_response(1)
		save.set_do_overwrite_confirmation(True)
		save.set_current_name(name)
		save.show()
		if save.run() != Gtk.ResponseType.ACCEPT:
			save.destroy()
			return
		thread = ffmpegThread(self.vd.name, save.get_filename(), self.outfmt, self.width, self.height, self.norm, self.input, self)
		save.destroy()
		self.threads.append(thread)
		thread.start()
		self.status.set_text("Status: Capturing")
		return

	def launch_playback(self, widget):
		if self.is_device_busy():
			return
		load = Gtk.FileChooserDialog(title="Choose a file to playback:",
			parent=self.window, action=Gtk.FileChooserAction.OPEN)
		save.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT)
		filter = Gtk.FileFilter()
		filter.add_pattern("*.h264")
		filter.add_pattern("*.264")
		filter.add_pattern("*.m4v")
		filter.add_pattern("*.m2v")
		filter.add_pattern("*.mpeg4")
		filter.add_pattern("*.mp4")
		filter.add_pattern("*.mp4f")
		filter.add_pattern("*.ts")
		#filter.add_pattern("*.mjpg")
		#filter.add_pattern("*.mjpeg")
		load.set_filter(filter)
		if self.default_folder:
			load.set_current_folder(self.default_folder)
		load.show()
		if load.run() != GTK.ResponseType.ACCEPT:
			load.destroy()
			return
		self.default_folder = load.get_current_folder()
		thread = PlaybackThread(self.vd.name, self.norm, load.get_filename(), self)
		load.destroy()
		self.threads.append(thread)
		thread.start()
		self.status.set_text("Status: Playing")
		return
		

	def stop_thread(self, widget):
		for thread in self.threads:
			if thread.devname == self.vd.name:
				if thread.is_alive():
					thread.stop()
				else:
					self.threads.remove(thread)
				break
		self.status.set_text("Status: Stopped")

	def stop_all_threads(self):
		for thread in self.threads:
			if (thread.is_alive()):
				thread.stop()
	def get_status_string(self):
		for thread in self.threads:
			if thread.devname == self.vd.name:
				if (thread.is_alive()):
					return "Status: "+thread.action
		return "Status: Stopped"
	def update_status(self, name, msg):
		if name == self.vd.name:
			self.status.set_text(msg)
		
	def __init__(self):
		self.threads = []
		self.window = Gtk.Window(title = "V4L2 Capture Demo")
		self.window.connect("delete_event", self.delete_event)
		self.window.connect("destroy", self.destroy)
		self.window.set_border_width(10)
		self.default_stream_ip = None
		self.default_stream_port = None
		self.default_folder = os.getcwd()
		# make sure window is wide enough, otherwise scrollbars
		# for brightness, contrast, hue and saturation are squished.
		self.window.set_default_size(700,100)
		# open the device
		self.vd = None
		matches = glob.glob("/dev/video[0-9]*")
		if matches:
			matches.sort()
		for dev in matches:
			try:
				self.vd = open(dev, "rb+", buffering=0)
				cp = v4l2_capability()
				ioctl(self.vd, VIDIOC_QUERYCAP, cp)
				if cp.capabilities & V4L2_CAP_DEVICE_CAPS:
					cp.capabilities = cp.device_caps
				if cp.capabilities & V4L2_CAP_META_CAPTURE:
					self.vd.close()
					continue   # don't open meta devices
				break
			except e:
				continue
		
		self.window.add(self.setup())
		
		self.window.show()

		
	def setup(self):
		vbox = Gtk.VBox(homogeneous=False, spacing=10)
		
		
		# get the driver name
		
		hbox = Gtk.HBox(homogeneous=False, spacing=10)
		label=Gtk.Label(label="Device Name:")
		hbox.pack_start(label, False, False, 0)
		label.show()

		opt = Gtk.ComboBoxText()
		menudefault = 0
		i = 0
		matches = glob.glob("/dev/video[0-9]*")
		if matches:
			matches.sort()
		for dev in matches:
			opt.append_text(dev)
			if self.vd != None and dev == self.vd.name:
				menudefault = i
			i += 1
		opt.set_active(menudefault)
		opt.connect("changed", lambda cb: 
			self.set_dev(cb, cb.get_active_text()))
		hbox.pack_start(opt, False, False, 0)
		opt.show()
		vbox.pack_start(hbox, False, False, 0)
		hbox.show()

		quitbox = Gtk.HBox(homogeneous=False, spacing=10)

		if self.vd == None:
			hbox = Gtk.HBox(homogeneous=False, spacing=10)
			label=Gtk.Label(label="Device failed to open, please try again")
			hbox.pack_start(label, False, False, 0)
			label.show()
			#label.set_alignment(0,0)
			label.set_xalign(0.0)
			vbox.pack_start(hbox, False, False, 0)
			hbox.show()
			
			vbox.show()
		else:
			# get the driver name

			hbox = Gtk.HBox(homogeneous=False, spacing=10)
			label=Gtk.Label(label="Driver Name:")
			hbox.pack_start(label, False, False, 0)
			label.show()

			cp = v4l2_capability()
			ioctl(self.vd, VIDIOC_QUERYCAP, cp)
			if cp.capabilities & V4L2_CAP_DEVICE_CAPS:
				cp.capabilities = cp.device_caps

			label=Gtk.Label(label=cp.driver.decode('utf-8'))
			hbox.pack_start(label, False, False, 0)
			label.show()

			label=Gtk.Label(label="Version:")
			hbox.pack_start(label, False, False, 0)
			label.show()
			label=Gtk.Label(label="%d.%d.%d" % (
				(cp.version >> 16) & 0xff, 
				(cp.version >> 8) & 0xff,
				cp.version & 0xff))
			hbox.pack_start(label, False, False, 0)
			label.show()
			vbox.pack_start(hbox, False, False, 0)
			hbox.show()

			hbox = Gtk.HBox(homogeneous=False, spacing=10)
			label=Gtk.Label(label="Card Name:")
			hbox.pack_start(label, False, False, 0)
			label.show()

			label=Gtk.Label(label=cp.card.decode('utf-8'))
			hbox.pack_start(label, False, False, 0)
			label.show()
			vbox.pack_start(hbox, False, False, 0)
			hbox.show()

			hbox = Gtk.HBox(homogeneous=False, spacing=10)
			label=Gtk.Label(label="Bus Info:")
			hbox.pack_start(label, False, False, 0)
			label.show()

			label=Gtk.Label(label=cp.bus_info.decode('utf-8'))
			hbox.pack_start(label, False, False, 0)
			label.show()
			vbox.pack_start(hbox, False, False, 0)
			hbox.show()


			if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
				# enumerate video inputs
				hbox = Gtk.HBox(homogeneous=False, spacing=10)

				label=Gtk.Label(label="Capture Input:")
				hbox.pack_start(label, False, False, 0)
				label.show()

				input = v4l2_input()
				input.index = 0
				opt = Gtk.ComboBoxText()
				values = []

				while 1:
					try: ioctl(self.vd, VIDIOC_ENUMINPUT, input)
					except IOError: break
					dprint("enumerated input: %d '%s'" % (input.index, input.name.decode('utf-8')))
					opt.append_text(input.name.decode('utf-8'))
					values.append(input.index)
					input.index += 1

				hbox.pack_start(opt, False, False, 0)

				curinput = ctypes.c_int()
				try: ioctl(self.vd, VIDIOC_G_INPUT, curinput)
				except: pass
				self.input = curinput.value

				opt.set_active(self.input)
				opt.connect("changed", make_combo_callback1(self.set_input, values))
				opt.show()

				vbox.pack_start(hbox, False, False, 0)
				hbox.show()
			elif cp.capabilities & V4L2_CAP_VIDEO_OUTPUT:
				# enumerate video outputs
				hbox = Gtk.HBox(homogeneous=False, spacing=10)

				label=Gtk.Label(label="Video Output:")
				hbox.pack_start(label, False, False, 0)
				label.show()

				output = v4l2_output()
				output.index = 0
				opt = Gtk.ComboBoxText()
				values = []

				while 1:
					try: ioctl(self.vd, VIDIOC_ENUMOUTPUT, output)
					except IOError: break
					dprint("enumerated output: %d '%s'" % (output.index, output.name.decode('utf-8')))
					opt.append_text(output.name.decode('utf-8'))
					values.append(output.index)
					output.index += 1

				hbox.pack_start(opt, False, False, 0)

				curoutput = ctypes.c_int()
				try: ioctl(self.vd, VIDIOC_G_OUTPUT, curoutput)
				except: pass
				self.output = curoutput.value

				opt.set_active(self.output)
				opt.connect("changed", make_combo_callback1(self.set_output, values))
				opt.show()

				vbox.pack_start(hbox, False, False, 0)
				hbox.show()

			# create a option menu for video frame sizes

			if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
				format = v4l2_format()
				format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
				try: ioctl(self.vd, VIDIOC_G_FMT, format)
				except: pass

				dprint("current format is 0x%08x" % format.fmt.pix.pixelformat)
				self.outfmt = format.fmt.pix.pixelformat
				self.width = format.fmt.pix.width
				self.height = format.fmt.pix.height
				dprint("current size is %dx%d" % (self.width, self.height))

				hbox = Gtk.HBox(homogeneous=False, spacing=10)
				label=Gtk.Label(label="Size:")
				hbox.pack_start(label, False, False, 0)
				label.show()

				opt = Gtk.ComboBoxText()
				menudefault = 0
				i = 0
				values = [
					[720,576, "D1 PAL"],
					[704,576, "D1 PAL "],
					[720,480, "D1 NTSC "],
					[704,480, "D1 NTSC"],
					[640,480, "VGA"],
					[352,288, "CIF"],
					[320,240, "SIF"],
					[176,144, "QCIF"],
					[160,128, "QSIF"],
					]
				for dim in values:
					opt.append_text("%dx%d %s" % (dim[0],dim[1],dim[2]))
					if self.width == dim[0] and self.height == dim[1]:
						menudefault = i
					i += 1

				opt.set_active(menudefault)
				opt.connect("changed", make_combo_callback1(self.set_dim, values))
				
				hbox.pack_start(opt, False, False, 0)
				opt.show()

				vbox.pack_start(hbox, False, False, 0)
				hbox.show()


				crop = v4l2_crop()
				crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
				try: ioctl(self.vd, VIDIOC_G_CROP, crop)
				except: pass
				dprint("current crop is %d,%d %dx%d" % (crop.c.left, crop.c.top, crop.c.width, crop.c.height))

				hbox = Gtk.HBox(homogeneous=False, spacing=10)
				label=Gtk.Label(label="Crop:")
				hbox.pack_start(label, False, False, 0)
				label.show()

				opt = Gtk.ComboBoxText()
				menudefault = 0
				i = 0
				values = [
					[0,0,720,576],
					[8,0,704,576],
					[10,0,704,576],
					[0,0,554,576],
					[0,0,720,480],
					[8,0,704,480],
					]
				for dim in values:
					opt.append_text("%d,%d %dx%d" % (dim[0],dim[1],dim[2],dim[3]))
					if crop.c.left == dim[0] and crop.c.top == dim[1] and crop.c.width == dim[2] and crop.c.height == dim[3]:
						menudefault = i
					i += 1
				opt.set_active(menudefault)
				opt.connect("changed", make_combo_callback1(self.set_crop, values))
				
				hbox.pack_start(opt, False, False, 0)
				opt.show()

				vbox.pack_start(hbox, False, False, 0)
				hbox.show()



			# enumerate video standards

			hbox = Gtk.HBox(homogeneous=False, spacing=10)

			label=Gtk.Label(label="Video Standard:")
			hbox.pack_start(label, False, False, 0)
			label.show()

			std = v4l2_standard()
			std.index = 0

			curstd = v4l2_std_id()
			try: ioctl(self.vd, VIDIOC_G_STD, curstd)
			except: pass
			dprint("current std is %x" % (curstd.value))
			if (curstd.value & V4L2_STD_NTSC):
				self.norm = "NTSC"
			elif (curstd.value & V4L2_STD_PAL):
				self.norm = "PAL"
			else:
				self.norm = None

			if self.norm != None:
				opt = Gtk.ComboBoxText()
				menudefault = 0
				menuindex = 0
				values = []

				while 1:
					try: ioctl(self.vd, VIDIOC_ENUMSTD, std)
					except IOError: break
					name = std.name.decode('utf-8')
					dprint("enumerated std: %d '%s' %x" % (std.index, name, std.id))
					if name == "NTSC" or name == "PAL":
						if std.id == curstd.value: menudefault = menuindex
						opt.append_text(name)
						values.append(std.id)
						menuindex += 1
					std.index += 1

				hbox.pack_start(opt, False, False, 0)
				opt.set_active(menudefault)
				opt.connect("changed", make_combo_callback1(self.set_std, values))
				opt.show()

				vbox.pack_start(hbox, False, False, 0)
				hbox.show()


			# enumerate the capture formats

			if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
				hbox = Gtk.HBox(homogeneous=False, spacing=10)

				label=Gtk.Label(label="Format:")
				hbox.pack_start(label, False, False, 0)
				label.show()


				fmtdesc = v4l2_fmtdesc()
				fmtdesc.index = 0
				fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE

				opt = Gtk.ComboBoxText()
				menudefault = 0
				jpegseen = False
				menuindex = 0
				values = []
				while 1:
					try: ioctl(self.vd, VIDIOC_ENUM_FMT, fmtdesc)
					except IOError: break
					dprint("enumerated format: %d %x '%s'" % (fmtdesc.index, fmtdesc.pixelformat, fmtdesc.description.decode('utf-8')))
					if fmtdesc.pixelformat == format.fmt.pix.pixelformat:
						menudefault = menuindex
						self.outfmt_name = fmtdesc.description.decode('utf-8')
					fmtdesc.index += 1
					if fmtdesc.pixelformat == V4L2_PIX_FMT_JPEG or fmtdesc.pixelformat == V4L2_PIX_FMT_MJPEG:
						if jpegseen:
							continue
						jpegseen = True
					opt.append_text(fmtdesc.description.decode('utf-8'))
					values.append(fmtdesc.pixelformat)
					menuindex += 1
				hbox.pack_start(opt, False, False, 0)
				opt.set_active(menudefault)
				opt.connect("changed", make_combo_callback1(self.set_format, values))
				opt.show()

				vbox.pack_start(hbox, False, False, 0)
				hbox.show()

			# enumerate controls

			notebook = Gtk.Notebook()
			
			cbox = Gtk.VBox(homogeneous=False, spacing=0)

			queryctrl = v4l2_queryctrl()
			ctrl_id = 0
			use_next_ctrl = True

			while True:
				if use_next_ctrl:
					ctrl_id |= V4L2_CTRL_FLAG_NEXT_CTRL
				else:
					ctrl_id += 1
				queryctrl.id = ctrl_id
				try:
					ioctl(self.vd, VIDIOC_QUERYCTRL, queryctrl)
					if use_next_ctrl:
						ctrl_id = queryctrl.id
				except IOError:
					if ctrl_id == V4L2_CTRL_FLAG_NEXT_CTRL:
						# next controls are not supported
						use_next_ctrl = False
						ctrl_id = V4L2_CID_BASE

						cbox = Gtk.VBox(homogeneous=False, spacing=0)
						sw = Gtk.ScrolledWindow()
						#sw.set_overlay_scrolling(True)
						sw.add(cbox)
						notebook.append_page(sw, Gtk.Label(label="Controls"))
						sw.show()
						cbox.show()
						cbox.set_spacing(-15)
						continue
					elif use_next_ctrl:
						break
					elif ctrl_id >= V4L2_CID_BASE and ctrl_id < V4L2_CID_LASTP1:
						continue
					elif ctrl_id == V4L2_CID_LASTP1:
						ctrl_id = V4L2_CID_PRIVATE_BASE
						continue
					break


				if queryctrl.type == V4L2_CTRL_TYPE_CTRL_CLASS:
					cbox = Gtk.VBox(homogeneous=False, spacing=0)
					sw = Gtk.ScrolledWindow()
					#sw.set_overlay_scrolling(True)
					sw.add(cbox)
					notebook.append_page(sw, Gtk.Label(label=queryctrl.name.decode('utf-8')))
					sw.show()
					cbox.show()
					cbox.set_spacing(-15)
					

					#hbox = Gtk.HBox(homogeneous=False, spacing=10)
					#label=Gtk.Label(queryctrl.name + ":")
					#label.set_markup("<u>" + queryctrl.name + "</u>:")
					#hbox.pack_start(label, False, False, 0)
					#label.show()
					#cbox.pack_start(hbox, False, False, 0)
					#hbox.show()
					continue
				if queryctrl.flags & V4L2_CTRL_FLAG_DISABLED:
					continue

				dprint("enumerated control: '%s' type=%d" % (queryctrl.name.decode('utf-8'), queryctrl.type))
				hbox = Gtk.HBox(homogeneous=False, spacing=0)
				hbox.set_spacing(-10)
				label = Gtk.Label(label=queryctrl.name.decode('utf-8') + ":")
				hbox.pack_start(label, False, False, 10)
				label.show()

				# Read the default value

				if queryctrl.type != V4L2_CTRL_TYPE_STRING:
					ctrl = v4l2_control()
					ctrl.id = queryctrl.id
					ctrl.value = queryctrl.default_value
					try: ioctl(self.vd, VIDIOC_G_CTRL, ctrl)
					except: print("unable to get control")
					default_value = ctrl.value
				else:
					ctrl = v4l2_ext_control()
					ctrl.id = queryctrl.id
					ctrl.string = ctypes.cast(ctypes.create_string_buffer(queryctrl.maximum), ctypes.c_char_p)
					ctrls = v4l2_ext_controls()
					ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(ctrl.id)
					ctrls.count = 1
					ctrls.controls = ctypes.pointer(ctrl)
					try:
						ioctl(self.vd, VIDIOC_G_EXT_CTRLS, ctrl, True)
					except: print("unable to get ext control")
					default_value = None #ctrl.string
					if default_value == None:
						default_value = ""

				if queryctrl.type == V4L2_CTRL_TYPE_BOOLEAN:
					chk = Gtk.CheckButton(label="Enabled")
					chk.set_active(default_value)
					hbox.pack_start(chk, False, False, 10);
					chk.show()
					
					space = Gtk.Label(label="")
					hbox.pack_start(space, True, True, 0)
					space.show()
					if queryctrl.flags & V4L2_CTRL_FLAG_READ_ONLY:
						chk.set_sensitive(False)
						button = Gtk.Button(label="Read")
						button.connect("clicked",
							lambda button, chk, cid:
								chk.set_active(self.get_ctrl(cid)),
							chk, queryctrl.id)
					else:
						chk.connect("toggled", self.set_toggle, queryctrl.id)
						button = Gtk.Button(label="Default")
						button.connect("clicked",
							lambda button, chk, active:
								chk.set_active(active),
							chk, queryctrl.default_value)
					hbox.pack_start(button, False, False, 10)
					button.show()

				elif queryctrl.type == V4L2_CTRL_TYPE_MENU:
					opt = Gtk.ComboBoxText()
					querymenu = v4l2_querymenu()
					querymenu.id = queryctrl.id
					querymenu.index = queryctrl.minimum
					menucurrent = 0
					menudefault = 0
					values = []
					while querymenu.index <= queryctrl.maximum:
						try: ioctl(self.vd, VIDIOC_QUERYMENU, querymenu)
						except: break
						name = querymenu.name.decode('utf-8')
						dprint("menu item: '%s'" % (name))
						if name != "":
							values.append(querymenu.index)
							opt.append_text(name)
							if querymenu.index < default_value:
								menucurrent += 1
							if querymenu.index < queryctrl.default_value:
								menudefault += 1
						querymenu.index += 1
					opt.connect("changed", make_combo_callback(self.set_ctrl, queryctrl.id, values))
					opt.set_active(menucurrent)
					
					hbox.pack_start(opt, False, False, 10)
					opt.show()
					space = Gtk.Label(label="")
					hbox.pack_start(space, True, True, 10)
					space.show()

					if queryctrl.flags & V4L2_CTRL_FLAG_READ_ONLY:
						querymenu.set_sensitive(False)
					else:
						button = Gtk.Button(label="Default")
						hbox.pack_start(button, False, False, 10)
						button.connect("clicked", self.set_opt, opt, menudefault)
						button.show()
						
				elif queryctrl.flags & V4L2_CTRL_FLAG_READ_ONLY:
					label=Gtk.Label(label=default_value)
					hbox.pack_start(label, False, False, 10)
					label.show()
					space = Gtk.Label(label="")
					hbox.pack_start(space, True, True, 10)
					space.show()
					button = Gtk.Button(label="Read")
					button.connect("clicked",
						lambda button, label, cid:
							label.set_text("%d" % self.get_ctrl(cid)),
						label, queryctrl.id)
					hbox.pack_start(button, False, False, 10)
					button.show()

				elif queryctrl.type == V4L2_CTRL_TYPE_STRING:
					#entry = Gtk.Entry(queryctrl.maximum)
					entry = Gtk.Entry()
					entry.set_text(default_value)
					hbox.pack_start(entry, True, True, 10)
					entry.connect("activate", self.set_str_ctrl, queryctrl.id)
					entry.show()

					button = Gtk.Button(label="Set")
					hbox.pack_start(button, False, False, 10)
					button.connect("clicked", self.set_str_ctrl, queryctrl.id, entry)
					button.show()

				else:
					adj = Gtk.Adjustment(value=default_value,
						lower=queryctrl.minimum,
						upper=queryctrl.maximum,
						step_increment=queryctrl.step,
						page_increment=queryctrl.step)
					adj.connect("value_changed", 
						lambda adj, id:
							adj.set_value(self.set_ctrl(adj, id, int(adj.get_value()))),
						queryctrl.id)
					
					hscale = Gtk.HScale(adjustment=adj)
					hscale.set_digits(0)
					hscale.set_value_pos(Gtk.PositionType.LEFT)
					hscale.connect("format-value", format_k)
					#hscale.connect("change-value", lambda x,y,z: True)  # disable mouse wheel
					hbox.pack_start(hscale, True, True, 10)
					hscale.show()

					button = Gtk.Button(label="Default")
					hbox.pack_start(button, False, False, 10)
					button.connect("clicked",
						lambda button, adj, value:
							adj.set_value(value),
						adj, queryctrl.default_value)
					button.show()
					
				cbox.pack_start(hbox, False, False, 10)
				hbox.show()

			# jpeg quality
			jpegcomp = v4l2_jpegcompression()
			try:
				ioctl(self.vd, VIDIOC_G_JPEGCOMP, jpegcomp)
			except:
				jpegcomp = None
			if jpegcomp != None:
				hbox = Gtk.HBox(homogeneous=False, spacing=10)
				label=Gtk.Label(label="JPEG Quality:")
				hbox.pack_start(label, False, False, 10)
				label.show()
				adj = Gtk.Adjustment(value=jpegcomp.quality, lower=1, upper=100, 
					step_increment=1, page_increment=1)
				adj.connect("value_changed", self.set_jpegcomp, adj)
				hscale = Gtk.HScale(adjustment=adj)
				hscale.set_digits(0)
				hscale.set_value_pos(Gtk.PositionType.LEFT)
				#hscale.connect("change-value", lambda x,y,z: True)  # disable mouse wheel
				hbox.pack_start(hscale, True, True, 10)
				hscale.show()

				#vbox.pack_start(hbox, False, False, 0)
				jbox=Gtk.VBox(homogeneous=False, spacing=10)
				jbox.pack_start(hbox, False, False, 10)
				jbox.show()
				notebook.append_page(jbox, Gtk.Label(label="JPEG Controls"))
				hbox.show()
				
				
			# frame intervals
			if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
				frmivalenum = v4l2_frmivalenum()
				frmivalenum.index = 0
				frmivalenum.pixel_format = self.outfmt
				frmivalenum.width = self.width
				frmivalenum.height = self.height
				streamparm = v4l2_streamparm()
				if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
					streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
				elif cp.capabilities & V4L2_CAP_VIDEO_OUTPUT:
					streamparm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT
				try:
					ioctl(self.vd, VIDIOC_ENUM_FRAMEINTERVALS, frmivalenum)
					ioctl(self.vd, VIDIOC_G_PARM, streamparm)
				except:
					frmivalenum = None
				if frmivalenum != None:
					hbox = Gtk.HBox(homogeneous=False, spacing=10)
					label=Gtk.Label(label="Frame Rate:")
					hbox.pack_start(label, False, False, 0)
					label.show()

					opt = Gtk.ComboBoxText()
					menudefault = 0
					values = []
					if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
						timeperframe = streamparm.parm.capture.timeperframe
					elif cp.capabilities & V4L2_CAP_VIDEO_OUTPUT:
						timeperframe = streamparm.parm.output.timeperframe
					while True:
						dprint("enumerated interval: %d %d/%d" % (frmivalenum.index, frmivalenum.discrete.numerator, frmivalenum.discrete.denominator))
						opt.append_text("%.2f fps"%(frmivalenum.discrete.denominator * 1.0 / frmivalenum.discrete.numerator))
						values.append(frmivalenum.index)
						if frmivalenum.discrete.numerator == timeperframe.numerator \
						and frmivalenum.discrete.denominator == timeperframe.denominator:
							menudefault = frmivalenum.index
						frmivalenum.index += 1
						try: ioctl(self.vd, VIDIOC_ENUM_FRAMEINTERVALS, frmivalenum)
						except IOError: break
					hbox.pack_start(opt, False, False, 0)
					opt.set_active(menudefault)
					opt.connect("changed", make_combo_callback1(self.set_frame_interval, values))
					opt.show()

					vbox.pack_start(hbox, False, False, 0)
					hbox.show()
			# crop cap
			try:
				cropcap = v4l2_cropcap()
				ioctl(self.vd, VIDIOC_CROPCAP, cropcap)
				dprint("cropcap supported")
				crop = v4l2_crop()
				crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE
				ioctl(self.vd, VIDIOC_G_CROP, crop)
				dprint("g_crop: %d,%d %dx%d" % (crop.c.left, crop.c.top, crop.c.width, crop.c.height))
				
				hbox = Gtk.HBox(homogeneous=False, spacing=10)
				label=Gtk.Label(label="Crop:")
				hbox.pack_start(label, False, False, 0)
				label.show()
				
				opt = Gtk.ComboBoxText()
				menudefault = 1
				values = []
				for w,h in [(720,480),(704,480),(720,576),(704,576),(640,400),(540,360),(528,422),(464,368),(360,240),(256,192),(224,168),(208,156),(172,132),(160,120)]:
					l = (720 - w) / 2
					if h == 576: 
						t = (576 - h) / 2
					else:
						t = (480 - h) / 2
					opt.append_text("%dx%d" % (w,h))
					values.append([l, t, w, h])
					if w == crop.c.width and h == crop.c.height:
						menudefault = len(menu.get_children())-1
				hbox.pack_start(opt, False, False, 0)
				opt.set_active(menudefault)
				opt.connect("changed", make_combo_callback1(self.set_crop, values))
				opt.show()

				vbox.pack_start(hbox, False, False, 0)
				hbox.show()
			
			except: pass
			
			
			# audio
			if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
				audio = v4l2_audio()
				audio.index = 0
				try: ioctl(self.vd, VIDIOC_ENUMAUDIO, audio)
				except IOError: audio = None
				if audio != None:
					hbox = Gtk.HBox(homogeneous=False, spacing=10)
					label=Gtk.Label(label="Audio Input:")
					hbox.pack_start(label, False, False, 0)
					label.show()
					has_avl = False;

					opt = Gtk.ComboBoxText()
					values = []
					while True:
						audio_name = audio.name.decode('utf-8')
						dprint("enumerated audio: %d '%s'" % (audio.index, audio_name))
						opt.append_text(audio_name)
						values.append(audio.index)
						audio.index += 1
						if audio.capability & V4L2_AUDCAP_AVL:
							has_avl = True
						try: ioctl(self.vd, VIDIOC_ENUMAUDIO, audio)
						except IOError: break
					hbox.pack_start(opt, False, False, 0)
					try: ioctl(self.vd, VIDIOC_G_AUDIO, audio)
					except IOError: print("VIDIOC_G_AUDIO failed")
					opt.set_active(audio.index)
					opt.connect("changed", make_combo_callback1(self.set_audio_input, values))
					opt.show()
					if has_avl:
						chk = Gtk.CheckButton(label="Auto Volume Level")
						chk.set_active(audio.mode & V4L2_AUDMODE_AVL)
						chk.connect("toggled", self.set_audmode, chk)
						hbox.pack_start(chk, False, False, 0);
						chk.show()

					vbox.pack_start(hbox, False, False, 0)
					hbox.show()
			elif cp.capabilities & V4L2_CAP_VIDEO_OUTPUT:
				audio = v4l2_audioout()
				audio.index = 0
				try: ioctl(self.vd, VIDIOC_ENUMAUDOUT, audio)
				except IOError: audio = None
				if audio != None:
					hbox = Gtk.HBox(homogeneous=False, spacing=10)
					label=Gtk.Label(label="Audio Output:")
					hbox.pack_start(label, False, False, 0)
					label.show()

					opt = Gtk.ComboBoxText()
					values = []
					while True:
						audio_name = audio.name.decode('utf-8')
						dprint("enumerated audio: %d '%s'" % (audio.index, audio_name))
						opt.append_text(audio_name)
						values.append(audio.index)
						audio.index += 1
						try: ioctl(self.vd, VIDIOC_ENUMAUDOUT, audio)
						except IOError: break
					hbox.pack_start(opt, False, False, 0)
					try: ioctl(self.vd, VIDIOC_G_AUDOUT, audio)
					except IOError: print("VIDIOC_G_AUDOUT failed")
					opt.set_active(audio.index)
					opt.connect("changed", make_combo_callback1(self.set_audio_output, values))
					opt.show()

					vbox.pack_start(hbox, False, False, 0)
					hbox.show()

			hrule = Gtk.HSeparator()
			hrule.show()
			vbox.pack_end(hrule, False, False, 0)
			
			hbox = Gtk.HBox(homogeneous=False, spacing=20)
			hbox.pack_start(vbox, False, True, 0)
			vbox.show()

			#vrule = Gtk.VSeparator()
			#vrule.show()
			#hbox.pack_start(vrule, False, False, 0)
			
			#hbox.pack_start(cbox, True, True, 0)
			#cbox.show()
			hbox.pack_start(notebook, True, True, 0)
			notebook.show()
			notebook.set_current_page(0)
		
			vbox = Gtk.VBox(homogeneous=False, spacing=10)
			vbox.pack_start(hbox, True, True, 0)
			hbox.show()
			
			if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE:
				if jpegseen:
					button = Gtk.Button(label="Snapshot")
					button.connect("clicked", self.take_snapshot)
					button.show()
					quitbox.pack_start(button, False, False, 0)

				mplayer = Gtk.Button(label="Preview")
				mplayer.connect("clicked", self.launch_mplayer)
				mplayer.show()
				quitbox.pack_start(mplayer, False, False, 0)

				capture = Gtk.Button(label="Save")
				capture.connect("clicked", self.launch_capture)
				capture.show()
				quitbox.pack_start(capture, False, False, 0)

				#capture = Gtk.Button(label="Capture to AVI")
				#capture.connect("clicked", self.launch_capture_avi)
				#capture.show()
				#vbox.pack_start(capture, False, False, 0)
				stream = Gtk.Button(label="Stream")
				stream.connect("clicked", self.launch_stream)
				stream.show()
				quitbox.pack_start(stream, False, False, 0)

				button = Gtk.Button(label="Stop")
				button.connect("clicked", self.stop_thread)
				button.show()
				quitbox.pack_start(button, False, False, 0)

			elif cp.capabilities & V4L2_CAP_VIDEO_OUTPUT:
				
				playback = Gtk.Button(label="Play")
				playback.connect("clicked", self.launch_playback)
				playback.show()
				quitbox.pack_start(playback, False, False, 0)

				button = Gtk.Button(label="Stop")
				button.connect("clicked", self.stop_thread)
				button.show()
				quitbox.pack_start(button, False, False, 0)
			
		
		
		if self.vd != None:
			statusFrame = Gtk.Frame()
			self.status = Gtk.Label(label=self.get_status_string())
			statusFrame.add(self.status)
			statusFrame.show()
			#self.status.set_alignment(0.0, 0.5)
			self.status.set_xalign(0.0)
			self.status.set_margin_start(5)
			self.status.show()
			quitbox.pack_start(statusFrame, True, True, 0)
		else:
			button = Gtk.Button(label="Refresh")
			button.connect("clicked", self.set_dev, "/dev/video0")
			button.show()
			quitbox.pack_start(button, False, False, 0)

		button = Gtk.Button(label="Quit")
		button.connect_object("clicked", Gtk.Widget.destroy, self.window)
		button.show()
		quitbox.pack_start(button, False, False, 0)
		
		vbox.pack_start(quitbox, False, True, 0)
		quitbox.show()
		
		vbox.show()
		return vbox

	def main(self):
		Gtk.main()
		self.stop_all_threads()

if __name__ == "__main__":
	demo = Demo()
	demo.main()
