From 143bb377bd85d5437d5063775bb21e93ff048edf Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Thu, 25 Dec 2014 19:39:35 +0100 Subject: [PATCH 01/34] add cam capture with gui --- cap.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100755 cap.py diff --git a/cap.py b/cap.py new file mode 100755 index 0000000..48b2d44 --- /dev/null +++ b/cap.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +from Image import frombytes, open as fromfile +from ImageTk import PhotoImage +from ImageChops import invert +from select import select +from v4l2capture import Video_device +from time import time +from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP +from threading import Thread + +class Cap(Frame): + def __init__(self): + self.root = Tk() + Frame.__init__(self, self.root) + self.pack() + self.createWidgets() + + def createWidgets(self): + self.image = fromfile('image.png') + #self.canvas=Canvas(root, width=1280, height=720, ) + #self.canvas.create_image(1280, 720, image=PhotoImage(self.image)) + #self.canvas.pack(side=TOP, expand=True, fill=BOTH) + + self.canvas = Label(self) + self.photo = PhotoImage(self.image) + self.canvas['image'] = self.photo + self.canvas.pack() + self.take = Button(self, text="take", fg="red", command=self.start_view) + self.take.pack(side="bottom") + #self.QUIT = Button(self, text="QUIT", fg="red", command=root.destroy) + + def start_view(self): + Thread(target=self.update_picture).start() + + def single_shot(self): + video = Video_device("/dev/video0") + try: + size_x, size_y = video.set_format(2592, 1944) + mode = 'RGB' + #size_x, size_y, mode = video.get_format() # YCbCr + video.create_buffers(1) + video.queue_all_buffers() + video.start() + select((video, ), (), ()) + data = video.read() + image = frombytes(mode, (size_x, size_y), data) + #image = invert(image) + image.save("scanned.jpg") + video.stop() + finally: + video.close() + + def update_picture(self, delta=3.0): + video = Video_device("/dev/video0") + try: + size_x, size_y = video.set_format(640, 480) + mode = 'RGB' + #size_x, size_y, mode = video.get_format() # YCbCr + video.create_buffers(30) + video.queue_all_buffers() + video.start() + stop_time = time() + delta + while stop_time >= time(): + select((video,), (), ()) + data = video.read_and_queue() + self.image = frombytes(mode, (size_x, size_y), data) + #self.image = invert(self.image) + self.photo = PhotoImage(self.image) + self.canvas['image'] = self.photo + video.stop() + finally: + video.close() + + def video_take(self, delta=3.0): + video = Video_device("/dev/video0") + try: + size_x, size_y = video.set_format(640, 480, fourcc='MJPG') + video.create_buffers(30) + video.queue_all_buffers() + video.start() + stop_time = time() + delta + with open('video.mjpg', 'wb') as f: + while stop_time >= time(): + select((video,), (), ()) + data = video.read_and_queue() + f.write(data) + video.stop() + finally: + video.close() + +app = Cap() +app.mainloop() +exit(0) From 997395f0e93db09c79f7b7c526fa1ef5032b1b35 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Sun, 21 Dec 2014 16:35:08 +0100 Subject: [PATCH 02/34] use canvas --- cap.py | 97 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 43 insertions(+), 54 deletions(-) diff --git a/cap.py b/cap.py index 48b2d44..aeed46b 100755 --- a/cap.py +++ b/cap.py @@ -6,87 +6,76 @@ from v4l2capture import Video_device from time import time from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP -from threading import Thread class Cap(Frame): def __init__(self): self.root = Tk() Frame.__init__(self, self.root) self.pack() - self.createWidgets() - - def createWidgets(self): self.image = fromfile('image.png') - #self.canvas=Canvas(root, width=1280, height=720, ) - #self.canvas.create_image(1280, 720, image=PhotoImage(self.image)) - #self.canvas.pack(side=TOP, expand=True, fill=BOTH) - - self.canvas = Label(self) self.photo = PhotoImage(self.image) - self.canvas['image'] = self.photo - self.canvas.pack() + self.canvas = Canvas(self, width=640, height=480, ) + self.canvas.create_image(320, 240, image=self.photo) + self.canvas.pack() #self.canvas.pack(side=TOP, expand=True, fill=BOTH) self.take = Button(self, text="take", fg="red", command=self.start_view) self.take.pack(side="bottom") #self.QUIT = Button(self, text="QUIT", fg="red", command=root.destroy) + self.video = None def start_view(self): - Thread(target=self.update_picture).start() + if self.video: + self.video.stop() + self.video.close() + self.video = None + else: + self.video = Video_device("/dev/video0") + self.video.set_format(640, 480) + self.video.create_buffers(30) + self.video.queue_all_buffers() + self.video.start() + self.root.after(0, self.live_view) + + def live_view(self, delta=3.0): + if self.video: + #size_x, size_y, mode = self.video.get_format() # YCbCr + select((self.video,), (), ()) + data = self.video.read_and_queue() + self.image = frombytes('RGB', (640, 480), data) + #self.image = invert(self.image) + self.photo = PhotoImage(self.image) + self.canvas.create_image(320, 240, image=self.photo) + self.root.after(1, self.live_view) def single_shot(self): - video = Video_device("/dev/video0") - try: - size_x, size_y = video.set_format(2592, 1944) + size_x, size_y = self.video.set_format(2592, 1944) mode = 'RGB' - #size_x, size_y, mode = video.get_format() # YCbCr - video.create_buffers(1) - video.queue_all_buffers() - video.start() - select((video, ), (), ()) - data = video.read() + #size_x, size_y, mode = self.video.get_format() # YCbCr + self.video.create_buffers(1) + self.video.queue_all_buffers() + self.video.start() + select((self.video, ), (), ()) + data = self.video.read() image = frombytes(mode, (size_x, size_y), data) #image = invert(image) image.save("scanned.jpg") - video.stop() - finally: - video.close() - - def update_picture(self, delta=3.0): - video = Video_device("/dev/video0") - try: - size_x, size_y = video.set_format(640, 480) - mode = 'RGB' - #size_x, size_y, mode = video.get_format() # YCbCr - video.create_buffers(30) - video.queue_all_buffers() - video.start() - stop_time = time() + delta - while stop_time >= time(): - select((video,), (), ()) - data = video.read_and_queue() - self.image = frombytes(mode, (size_x, size_y), data) - #self.image = invert(self.image) - self.photo = PhotoImage(self.image) - self.canvas['image'] = self.photo - video.stop() - finally: - video.close() + self.video.stop() def video_take(self, delta=3.0): - video = Video_device("/dev/video0") + self.video = Video_device("/dev/video0") try: - size_x, size_y = video.set_format(640, 480, fourcc='MJPG') - video.create_buffers(30) - video.queue_all_buffers() - video.start() + size_x, size_y = self.video.set_format(640, 480, fourcc='MJPG') + self.video.create_buffers(30) + self.video.queue_all_buffers() + self.video.start() stop_time = time() + delta with open('video.mjpg', 'wb') as f: while stop_time >= time(): - select((video,), (), ()) - data = video.read_and_queue() + select((self.video,), (), ()) + data = self.video.read_and_queue() f.write(data) - video.stop() + self.video.stop() finally: - video.close() + self.video.close() app = Cap() app.mainloop() From 9013cc07fb0020f63aae790a7d3a036a52afdb04 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Sun, 21 Dec 2014 18:28:38 +0100 Subject: [PATCH 03/34] make work --- cap.py | 107 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 46 deletions(-) diff --git a/cap.py b/cap.py index aeed46b..2dbe39f 100755 --- a/cap.py +++ b/cap.py @@ -6,76 +6,91 @@ from v4l2capture import Video_device from time import time from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP +from os.path import exists +from evdev import InputDevice + +# TODO: +# - determine serial +# - get v4l properties (sizes & fps) +# - set v4l properties (contrast, hue, sat, ..) +# - get event from usb dev class Cap(Frame): def __init__(self): + self.serial = 0 + self.invert = True + self.videodevice = '/dev/video1' self.root = Tk() + self.root.bind('', self.stop_video) Frame.__init__(self, self.root) self.pack() + self.canvas = Canvas(self, width=640, height=480, ) + self.canvas.pack() + self.take = Button(self, text='take!', fg='red', command=self.single_shot) + self.take.pack(side='bottom') + self.video = None + self.start_video() + + def set_pauseimage(self): self.image = fromfile('image.png') self.photo = PhotoImage(self.image) - self.canvas = Canvas(self, width=640, height=480, ) self.canvas.create_image(320, 240, image=self.photo) - self.canvas.pack() #self.canvas.pack(side=TOP, expand=True, fill=BOTH) - self.take = Button(self, text="take", fg="red", command=self.start_view) - self.take.pack(side="bottom") - #self.QUIT = Button(self, text="QUIT", fg="red", command=root.destroy) - self.video = None - def start_view(self): - if self.video: + def stop_video(self, *args): + if self.video is not None: self.video.stop() self.video.close() self.video = None - else: - self.video = Video_device("/dev/video0") - self.video.set_format(640, 480) - self.video.create_buffers(30) - self.video.queue_all_buffers() - self.video.start() - self.root.after(0, self.live_view) + + def start_video(self): + if self.video is not None: + self.stop_video() + self.video = Video_device(self.videodevice) + self.video.set_format(640, 480) + self.video.create_buffers(30) + self.video.queue_all_buffers() + self.video.start() + #width, height, mode = self.video.get_format() # YCbCr + self.root.after(1, self.live_view) def live_view(self, delta=3.0): - if self.video: - #size_x, size_y, mode = self.video.get_format() # YCbCr + if self.video is not None: select((self.video,), (), ()) data = self.video.read_and_queue() self.image = frombytes('RGB', (640, 480), data) - #self.image = invert(self.image) + if self.invert: + self.image = invert(self.image) self.photo = PhotoImage(self.image) self.canvas.create_image(320, 240, image=self.photo) self.root.after(1, self.live_view) def single_shot(self): - size_x, size_y = self.video.set_format(2592, 1944) - mode = 'RGB' - #size_x, size_y, mode = self.video.get_format() # YCbCr - self.video.create_buffers(1) - self.video.queue_all_buffers() - self.video.start() - select((self.video, ), (), ()) - data = self.video.read() - image = frombytes(mode, (size_x, size_y), data) - #image = invert(image) - image.save("scanned.jpg") - self.video.stop() + def go(): + self.video = Video_device(self.videodevice) + try: + width, height = self.video.set_format(2592, 1944) + mode = 'RGB' + self.video.create_buffers(1) + self.video.queue_all_buffers() + self.video.start() + select((self.video, ), (), ()) + data = self.video.read() + image = frombytes(mode, (width, height), data) + if self.invert: + image = invert(image) + filename = 'scanned.{}.jpg'.format(self.serial) + self.serial += 1 + image.save(filename) + print filename, 'saved' + self.video.stop() + finally: + self.video.close() + self.video = None + self.root.after(10, self.start_video) + self.stop_video() + self.set_pauseimage() + self.root.after(10, go) - def video_take(self, delta=3.0): - self.video = Video_device("/dev/video0") - try: - size_x, size_y = self.video.set_format(640, 480, fourcc='MJPG') - self.video.create_buffers(30) - self.video.queue_all_buffers() - self.video.start() - stop_time = time() + delta - with open('video.mjpg', 'wb') as f: - while stop_time >= time(): - select((self.video,), (), ()) - data = self.video.read_and_queue() - f.write(data) - self.video.stop() - finally: - self.video.close() app = Cap() app.mainloop() From b22e2d743942255b0028510c468833cab42d8289 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Mon, 22 Dec 2014 00:07:40 +0100 Subject: [PATCH 04/34] improve scanning, add keys --- cap.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/cap.py b/cap.py index 2dbe39f..cdc5f1a 100755 --- a/cap.py +++ b/cap.py @@ -1,41 +1,82 @@ #!/usr/bin/env python from Image import frombytes, open as fromfile from ImageTk import PhotoImage -from ImageChops import invert +from ImageOps import invert, autocontrast, grayscale from select import select from v4l2capture import Video_device from time import time from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP from os.path import exists -from evdev import InputDevice # TODO: -# - determine serial # - get v4l properties (sizes & fps) # - set v4l properties (contrast, hue, sat, ..) # - get event from usb dev +def ascii_increment(val): + a = ord('a') + i = (ord(val[0]) - a) * 26 + (ord(val[1]) - a) + i += 1 + return chr(a + i / 26) + chr(a + i % 26) + class Cap(Frame): def __init__(self): + self.role = 'aa' self.serial = 0 self.invert = True + self.bw = True + self.ac = True self.videodevice = '/dev/video1' + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + while exists(self.filename): + self.serial += 1 + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) self.root = Tk() self.root.bind('', self.stop_video) Frame.__init__(self, self.root) + self.root.bind("", self.single_shot) + self.root.bind("", self.single_shot) + self.root.bind("q", self.quit) self.pack() self.canvas = Canvas(self, width=640, height=480, ) - self.canvas.pack() - self.take = Button(self, text='take!', fg='red', command=self.single_shot) - self.take.pack(side='bottom') + self.canvas.pack(side='top') + self.resetrole = Button(self, text='First role', command=self.first_role) + self.resetrole.pack(side='left') + self.fnl = Label(self, text=self.filename) + self.fnl.pack(side='left') + self.nextrole = Button(self, text='Next role', command=self.inc_role) + self.nextrole.pack(side='left') + self.take = Button(self, text='Take!', command=self.single_shot) + self.take.pack(side='right') self.video = None self.start_video() + def first_role(self): + self.serial = 0 + self.role = 'aa' + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + while exists(self.filename): + self.serial += 1 + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + self.fnl['text'] = self.filename + + def inc_role(self): + self.serial = 0 + self.role = ascii_increment(self.role) + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + while exists(self.filename): + self.serial += 1 + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + self.fnl['text'] = self.filename + def set_pauseimage(self): self.image = fromfile('image.png') self.photo = PhotoImage(self.image) self.canvas.create_image(320, 240, image=self.photo) + def quit(self, event): + self.root.destroy() + def stop_video(self, *args): if self.video is not None: self.video.stop() @@ -60,28 +101,37 @@ def live_view(self, delta=3.0): self.image = frombytes('RGB', (640, 480), data) if self.invert: self.image = invert(self.image) + if self.bw: + self.image = grayscale(self.image) + if self.ac: + self.image = autocontrast(self.image) self.photo = PhotoImage(self.image) self.canvas.create_image(320, 240, image=self.photo) self.root.after(1, self.live_view) - def single_shot(self): + def single_shot(self, *args): def go(): self.video = Video_device(self.videodevice) try: width, height = self.video.set_format(2592, 1944) mode = 'RGB' - self.video.create_buffers(1) + self.video.create_buffers(7) self.video.queue_all_buffers() self.video.start() - select((self.video, ), (), ()) - data = self.video.read() + for n in range(7): # wait for auto + select((self.video, ), (), ()) + data = self.video.read_and_queue() image = frombytes(mode, (width, height), data) if self.invert: image = invert(image) - filename = 'scanned.{}.jpg'.format(self.serial) + if self.bw: + image = grayscale(image) + if self.ac: + image = autocontrast(image) + image.save(self.filename) self.serial += 1 - image.save(filename) - print filename, 'saved' + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + self.fnl['text'] = self.filename self.video.stop() finally: self.video.close() From 9934614f2ac883586e26824acdd0cac26b7bfee3 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Mon, 22 Dec 2014 00:35:00 +0100 Subject: [PATCH 05/34] add comments, rename --- cap.py => filmroller.py | 49 +++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) rename cap.py => filmroller.py (73%) diff --git a/cap.py b/filmroller.py similarity index 73% rename from cap.py rename to filmroller.py index cdc5f1a..e28241b 100755 --- a/cap.py +++ b/filmroller.py @@ -8,12 +8,32 @@ from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP from os.path import exists -# TODO: -# - get v4l properties (sizes & fps) -# - set v4l properties (contrast, hue, sat, ..) -# - get event from usb dev +''' +webcam liveview and single picture capture program. this program shows a +picture in low resolution. when triggered (by 'space', 'enter' or the button) +it switches the webcam to a high resolution to take a picture and stores it. +after that it switches back to the liveview mode in low resulution. + +the filename for storage is counted up by pic no and role (which is aa, ab, +ac...). it tries to determine the next pic no to not overwrite existing files. +is stores the scans in the current directory. + +this program is good to be used with scanners for analog film rolls where you +manually position the picture with a live view and scan in highest possible +resolution. + +it needs tkinter, pil image, imageops and imagetk and famous v4l2capture. + +TODO: +- get v4l properties (sizes & fps) +- remove hardcoded stuff +- set v4l properties (contrast, hue, sat, ..) +- get event from usb dev +- reduce redundant code +''' def ascii_increment(val): + ' count aa, ab, ac ... ' a = ord('a') i = (ord(val[0]) - a) * 26 + (ord(val[1]) - a) i += 1 @@ -21,6 +41,7 @@ def ascii_increment(val): class Cap(Frame): def __init__(self): + 'set defaults, create widgets, bind callbacks, start live view' self.role = 'aa' self.serial = 0 self.invert = True @@ -32,6 +53,7 @@ def __init__(self): self.serial += 1 self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) self.root = Tk() + self.root.title('filmroller - ' + self.filename) self.root.bind('', self.stop_video) Frame.__init__(self, self.root) self.root.bind("", self.single_shot) @@ -52,6 +74,7 @@ def __init__(self): self.start_video() def first_role(self): + ' jump back to first role ' self.serial = 0 self.role = 'aa' self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) @@ -59,8 +82,10 @@ def first_role(self): self.serial += 1 self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) self.fnl['text'] = self.filename + self.root.title('filmroller - ' + self.filename) def inc_role(self): + ' increment to next role ' self.serial = 0 self.role = ascii_increment(self.role) self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) @@ -68,22 +93,27 @@ def inc_role(self): self.serial += 1 self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) self.fnl['text'] = self.filename + self.root.title('filmroller - ' + self.filename) def set_pauseimage(self): + ' show pause image (during shot) ' self.image = fromfile('image.png') self.photo = PhotoImage(self.image) self.canvas.create_image(320, 240, image=self.photo) def quit(self, event): + ' quit program ' self.root.destroy() def stop_video(self, *args): + ' stop video and release device ' if self.video is not None: self.video.stop() self.video.close() self.video = None def start_video(self): + ' init video and start live view ' if self.video is not None: self.stop_video() self.video = Video_device(self.videodevice) @@ -95,6 +125,7 @@ def start_video(self): self.root.after(1, self.live_view) def live_view(self, delta=3.0): + ' show single pic live view and ask tk to call us again later ' if self.video is not None: select((self.video,), (), ()) data = self.video.read_and_queue() @@ -110,6 +141,7 @@ def live_view(self, delta=3.0): self.root.after(1, self.live_view) def single_shot(self, *args): + ' do a high res single shot and store it ' def go(): self.video = Video_device(self.videodevice) try: @@ -132,6 +164,7 @@ def go(): self.serial += 1 self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) self.fnl['text'] = self.filename + self.root.title('filmroller - ' + self.filename) self.video.stop() finally: self.video.close() @@ -142,6 +175,8 @@ def go(): self.root.after(10, go) -app = Cap() -app.mainloop() -exit(0) +def main(): + app = Cap() + app.mainloop() + +main() From b0318f5af1b69ae9fedec8cee4816c4827e2b4ed Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Mon, 22 Dec 2014 01:17:00 +0100 Subject: [PATCH 06/34] check existense always --- filmroller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/filmroller.py b/filmroller.py index e28241b..39eafe4 100755 --- a/filmroller.py +++ b/filmroller.py @@ -47,7 +47,7 @@ def __init__(self): self.invert = True self.bw = True self.ac = True - self.videodevice = '/dev/video1' + self.videodevice = '/dev/video0' self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) while exists(self.filename): self.serial += 1 @@ -163,6 +163,9 @@ def go(): image.save(self.filename) self.serial += 1 self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + while exists(self.filename): + self.serial += 1 + self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) self.fnl['text'] = self.filename self.root.title('filmroller - ' + self.filename) self.video.stop() From 7909d74c57cebc5c11857bc61e1974f2c4743848 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Mon, 22 Dec 2014 01:17:19 +0100 Subject: [PATCH 07/34] add pause image --- image.png | Bin 0 -> 20092 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 image.png diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..eed4583cbbb913a761a37f127c4ed80a56e0a302 GIT binary patch literal 20092 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 Date: Mon, 22 Dec 2014 10:30:08 +0100 Subject: [PATCH 08/34] rename&reorder --- image.png => filmroller.pause.png | Bin filmroller.py | 50 +++++++++++++----------------- 2 files changed, 22 insertions(+), 28 deletions(-) rename image.png => filmroller.pause.png (100%) diff --git a/image.png b/filmroller.pause.png similarity index 100% rename from image.png rename to filmroller.pause.png diff --git a/filmroller.py b/filmroller.py index 39eafe4..a2c5b27 100755 --- a/filmroller.py +++ b/filmroller.py @@ -41,46 +41,46 @@ def ascii_increment(val): class Cap(Frame): def __init__(self): - 'set defaults, create widgets, bind callbacks, start live view' - self.role = 'aa' - self.serial = 0 + ' set defaults, create widgets, bind callbacks, start live view ' + # config: self.invert = True self.bw = True self.ac = True - self.videodevice = '/dev/video0' - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) - while exists(self.filename): - self.serial += 1 - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + self.videodevice = '/dev/video1' + # go! self.root = Tk() - self.root.title('filmroller - ' + self.filename) self.root.bind('', self.stop_video) - Frame.__init__(self, self.root) self.root.bind("", self.single_shot) self.root.bind("", self.single_shot) self.root.bind("q", self.quit) + Frame.__init__(self, self.root) self.pack() self.canvas = Canvas(self, width=640, height=480, ) self.canvas.pack(side='top') self.resetrole = Button(self, text='First role', command=self.first_role) self.resetrole.pack(side='left') - self.fnl = Label(self, text=self.filename) + self.fnl = Label(self) self.fnl.pack(side='left') self.nextrole = Button(self, text='Next role', command=self.inc_role) self.nextrole.pack(side='left') self.take = Button(self, text='Take!', command=self.single_shot) self.take.pack(side='right') + self.first_role() self.video = None self.start_video() def first_role(self): ' jump back to first role ' - self.serial = 0 self.role = 'aa' + self.serial = 0 + self.inc_picture() + + def inc_picture(self): self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) while exists(self.filename): self.serial += 1 self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + self.root.title('filmroller - ' + self.filename) self.fnl['text'] = self.filename self.root.title('filmroller - ' + self.filename) @@ -88,16 +88,11 @@ def inc_role(self): ' increment to next role ' self.serial = 0 self.role = ascii_increment(self.role) - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) - while exists(self.filename): - self.serial += 1 - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) - self.fnl['text'] = self.filename - self.root.title('filmroller - ' + self.filename) + self.inc_picture() def set_pauseimage(self): ' show pause image (during shot) ' - self.image = fromfile('image.png') + self.image = fromfile('filmroller.pause.png') self.photo = PhotoImage(self.image) self.canvas.create_image(320, 240, image=self.photo) @@ -118,6 +113,9 @@ def start_video(self): self.stop_video() self.video = Video_device(self.videodevice) self.video.set_format(640, 480) + self.video.set_auto_white_balance(True) + self.video.set_exposure_auto(True) + #self.video.set_focus_auto(True) self.video.create_buffers(30) self.video.queue_all_buffers() self.video.start() @@ -146,14 +144,16 @@ def go(): self.video = Video_device(self.videodevice) try: width, height = self.video.set_format(2592, 1944) - mode = 'RGB' + self.video.set_auto_white_balance(True) + self.video.set_exposure_auto(True) + #self.video.set_focus_auto(True) self.video.create_buffers(7) self.video.queue_all_buffers() self.video.start() for n in range(7): # wait for auto select((self.video, ), (), ()) data = self.video.read_and_queue() - image = frombytes(mode, (width, height), data) + image = frombytes('RGB', (width, height), data) if self.invert: image = invert(image) if self.bw: @@ -161,13 +161,7 @@ def go(): if self.ac: image = autocontrast(image) image.save(self.filename) - self.serial += 1 - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) - while exists(self.filename): - self.serial += 1 - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) - self.fnl['text'] = self.filename - self.root.title('filmroller - ' + self.filename) + self.inc_picture() self.video.stop() finally: self.video.close() From 97234ceeba966f6b43ff07a87f43b0f22ba4fc1b Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Thu, 25 Dec 2014 19:19:25 +0100 Subject: [PATCH 09/34] add controls --- filmroller.py | 90 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/filmroller.py b/filmroller.py index a2c5b27..190d3e6 100755 --- a/filmroller.py +++ b/filmroller.py @@ -1,12 +1,13 @@ #!/usr/bin/env python +from select import select +from time import time +from os.path import exists +from os import listdir from Image import frombytes, open as fromfile from ImageTk import PhotoImage from ImageOps import invert, autocontrast, grayscale -from select import select +from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP, Checkbutton, IntVar, OptionMenu, StringVar from v4l2capture import Video_device -from time import time -from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP -from os.path import exists ''' webcam liveview and single picture capture program. this program shows a @@ -25,7 +26,7 @@ it needs tkinter, pil image, imageops and imagetk and famous v4l2capture. TODO: -- get v4l properties (sizes & fps) +- get v4l properties (sizes & fps: http://git.linuxtv.org/cgit.cgi/v4l-utils.git/tree/utils/v4l2-ctl/v4l2-ctl-vidcap.cpp) - remove hardcoded stuff - set v4l properties (contrast, hue, sat, ..) - get event from usb dev @@ -42,21 +43,35 @@ def ascii_increment(val): class Cap(Frame): def __init__(self): ' set defaults, create widgets, bind callbacks, start live view ' - # config: - self.invert = True - self.bw = True - self.ac = True - self.videodevice = '/dev/video1' # go! self.root = Tk() self.root.bind('', self.stop_video) self.root.bind("", self.single_shot) self.root.bind("", self.single_shot) self.root.bind("q", self.quit) + # config: + self.invert = IntVar() + self.invert.set(1) + self.bw = IntVar() + self.bw.set(0) + self.ac = IntVar() + self.ac.set(1) + self.videodevice = StringVar() + dev_names = sorted(['/dev/{}'.format(x) for x in listdir("/dev") if x.startswith("video")]) + self.videodevice.set(dev_names[-1]) + # Frame.__init__(self, self.root) self.pack() self.canvas = Canvas(self, width=640, height=480, ) self.canvas.pack(side='top') + self.xt = Checkbutton(self, text='Invert', variable=self.invert) + self.xt.pack(side='left') + self.xb = Checkbutton(self, text='B/W', variable=self.bw) + self.xb.pack(side='left') + self.xa = Checkbutton(self, text='Auto', variable=self.ac) + self.xa.pack(side='left') + self.xv = OptionMenu(self, self.videodevice, *dev_names, command=self.start_video) + self.xv.pack(side='left') self.resetrole = Button(self, text='First role', command=self.first_role) self.resetrole.pack(side='left') self.fnl = Label(self) @@ -107,20 +122,26 @@ def stop_video(self, *args): self.video.close() self.video = None - def start_video(self): + def restart_video(self, *args): + self.stop_video() + self.root.after(1, self.start_video) + + def start_video(self, *args): ' init video and start live view ' - if self.video is not None: - self.stop_video() - self.video = Video_device(self.videodevice) - self.video.set_format(640, 480) - self.video.set_auto_white_balance(True) - self.video.set_exposure_auto(True) - #self.video.set_focus_auto(True) - self.video.create_buffers(30) - self.video.queue_all_buffers() - self.video.start() - #width, height, mode = self.video.get_format() # YCbCr - self.root.after(1, self.live_view) + if self.video is None: + self.video = Video_device(self.videodevice.get()) + self.video.set_format(640, 480) + try: self.video.set_auto_white_balance(True) + except: pass + try: self.video.set_exposure_auto(True) + except: pass + try: self.video.set_focus_auto(True) + except: pass + self.video.create_buffers(30) + self.video.queue_all_buffers() + self.video.start() + #width, height, mode = self.video.get_format() # YCbCr + self.root.after(1, self.live_view) def live_view(self, delta=3.0): ' show single pic live view and ask tk to call us again later ' @@ -128,11 +149,11 @@ def live_view(self, delta=3.0): select((self.video,), (), ()) data = self.video.read_and_queue() self.image = frombytes('RGB', (640, 480), data) - if self.invert: + if self.invert.get(): self.image = invert(self.image) - if self.bw: + if self.bw.get(): self.image = grayscale(self.image) - if self.ac: + if self.ac.get(): self.image = autocontrast(self.image) self.photo = PhotoImage(self.image) self.canvas.create_image(320, 240, image=self.photo) @@ -141,12 +162,15 @@ def live_view(self, delta=3.0): def single_shot(self, *args): ' do a high res single shot and store it ' def go(): - self.video = Video_device(self.videodevice) + self.video = Video_device(self.videodevice.get()) try: width, height = self.video.set_format(2592, 1944) - self.video.set_auto_white_balance(True) - self.video.set_exposure_auto(True) - #self.video.set_focus_auto(True) + try: self.video.set_auto_white_balance(True) + except: pass + try: self.video.set_exposure_auto(True) + except: pass + try: self.video.set_focus_auto(True) + except: pass self.video.create_buffers(7) self.video.queue_all_buffers() self.video.start() @@ -154,11 +178,11 @@ def go(): select((self.video, ), (), ()) data = self.video.read_and_queue() image = frombytes('RGB', (width, height), data) - if self.invert: + if self.invert.get(): image = invert(image) - if self.bw: + if self.bw.get(): image = grayscale(image) - if self.ac: + if self.ac.get(): image = autocontrast(image) image.save(self.filename) self.inc_picture() From 2b2025d1e70156f0abf3455e6f5304def827a7e9 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Tue, 30 Dec 2014 11:01:34 +0100 Subject: [PATCH 10/34] use new names --- filmroller.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/filmroller.py b/filmroller.py index 190d3e6..656ec32 100755 --- a/filmroller.py +++ b/filmroller.py @@ -26,7 +26,6 @@ it needs tkinter, pil image, imageops and imagetk and famous v4l2capture. TODO: -- get v4l properties (sizes & fps: http://git.linuxtv.org/cgit.cgi/v4l-utils.git/tree/utils/v4l2-ctl/v4l2-ctl-vidcap.cpp) - remove hardcoded stuff - set v4l properties (contrast, hue, sat, ..) - get event from usb dev @@ -50,6 +49,7 @@ def __init__(self): self.root.bind("", self.single_shot) self.root.bind("q", self.quit) # config: + self.video = None self.invert = IntVar() self.invert.set(1) self.bw = IntVar() @@ -81,7 +81,6 @@ def __init__(self): self.take = Button(self, text='Take!', command=self.single_shot) self.take.pack(side='right') self.first_role() - self.video = None self.start_video() def first_role(self): @@ -108,8 +107,9 @@ def inc_role(self): def set_pauseimage(self): ' show pause image (during shot) ' self.image = fromfile('filmroller.pause.png') + self.image.thumbnail((self.previewsize['size_x'], self.previewsize['size_y'], ), ) self.photo = PhotoImage(self.image) - self.canvas.create_image(320, 240, image=self.photo) + self.canvas.create_image(self.previewsize['size_x']/2, self.previewsize['size_y']/2, image=self.photo) def quit(self, event): ' quit program ' @@ -130,7 +130,11 @@ def start_video(self, *args): ' init video and start live view ' if self.video is None: self.video = Video_device(self.videodevice.get()) - self.video.set_format(640, 480) + _, _, self.fourcc = self.video.get_format() + caps = sorted(self.video.get_framesizes(self.fourcc), cmp=lambda a, b: cmp(a['size_x']*a['size_y'], b['size_x']*b['size_y'])) + self.previewsize, self.highressize = caps[0], caps[-1] + self.previewsize['size_x'], self.previewsize['size_y'] = self.video.set_format( + self.previewsize['size_x'], self.previewsize['size_y'], 0, 'MJPEG') try: self.video.set_auto_white_balance(True) except: pass try: self.video.set_exposure_auto(True) @@ -140,7 +144,6 @@ def start_video(self, *args): self.video.create_buffers(30) self.video.queue_all_buffers() self.video.start() - #width, height, mode = self.video.get_format() # YCbCr self.root.after(1, self.live_view) def live_view(self, delta=3.0): @@ -148,7 +151,7 @@ def live_view(self, delta=3.0): if self.video is not None: select((self.video,), (), ()) data = self.video.read_and_queue() - self.image = frombytes('RGB', (640, 480), data) + self.image = frombytes('RGB', (self.previewsize['size_x'], self.previewsize['size_y']), data) if self.invert.get(): self.image = invert(self.image) if self.bw.get(): @@ -156,7 +159,7 @@ def live_view(self, delta=3.0): if self.ac.get(): self.image = autocontrast(self.image) self.photo = PhotoImage(self.image) - self.canvas.create_image(320, 240, image=self.photo) + self.canvas.create_image(self.previewsize['size_x']/2, self.previewsize['size_y']/2, image=self.photo) self.root.after(1, self.live_view) def single_shot(self, *args): @@ -164,7 +167,8 @@ def single_shot(self, *args): def go(): self.video = Video_device(self.videodevice.get()) try: - width, height = self.video.set_format(2592, 1944) + self.highressize['size_x'], self.highressize['size_y'] = self.video.set_format( + self.highressize['size_x'], self.highressize['size_y'], 0, 'MJPEG') try: self.video.set_auto_white_balance(True) except: pass try: self.video.set_exposure_auto(True) @@ -177,7 +181,7 @@ def go(): for n in range(7): # wait for auto select((self.video, ), (), ()) data = self.video.read_and_queue() - image = frombytes('RGB', (width, height), data) + image = frombytes('RGB', (self.highressize['size_x'], self.highressize['size_y'], ), data) if self.invert.get(): image = invert(image) if self.bw.get(): From dfe95a96ba4ce6a84edd287c148d89607f677789 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 14:13:42 +0100 Subject: [PATCH 11/34] fix change video device menu --- filmroller.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/filmroller.py b/filmroller.py index 656ec32..bfd4359 100755 --- a/filmroller.py +++ b/filmroller.py @@ -23,10 +23,11 @@ manually position the picture with a live view and scan in highest possible resolution. -it needs tkinter, pil image, imageops and imagetk and famous v4l2capture. +it needs tkinter (see http://effbot.org/tkinterbook/tkinter-index.htm), pil +image, imageops and imagetk and the famous v4l2capture. TODO: -- remove hardcoded stuff +- remove hardcoded config - set v4l properties (contrast, hue, sat, ..) - get event from usb dev - reduce redundant code @@ -70,7 +71,7 @@ def __init__(self): self.xb.pack(side='left') self.xa = Checkbutton(self, text='Auto', variable=self.ac) self.xa.pack(side='left') - self.xv = OptionMenu(self, self.videodevice, *dev_names, command=self.start_video) + self.xv = OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video) self.xv.pack(side='left') self.resetrole = Button(self, text='First role', command=self.first_role) self.resetrole.pack(side='left') @@ -145,11 +146,14 @@ def start_video(self, *args): self.video.queue_all_buffers() self.video.start() self.root.after(1, self.live_view) + #self.canvas.width=640 + #self.canvas.height=480 + #self.canvas.pack(side='top') def live_view(self, delta=3.0): ' show single pic live view and ask tk to call us again later ' if self.video is not None: - select((self.video,), (), ()) + select((self.video, ), (), ()) data = self.video.read_and_queue() self.image = frombytes('RGB', (self.previewsize['size_x'], self.previewsize['size_y']), data) if self.invert.get(): @@ -194,10 +198,10 @@ def go(): finally: self.video.close() self.video = None - self.root.after(10, self.start_video) + self.root.after(1, self.start_video) self.stop_video() self.set_pauseimage() - self.root.after(10, go) + self.root.after(1, go) def main(): From 2945f9caa06e3446baafa9cadf52a452b6dee069 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 14:49:21 +0100 Subject: [PATCH 12/34] add persistance config --- filmroller.py | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/filmroller.py b/filmroller.py index bfd4359..a81bc37 100755 --- a/filmroller.py +++ b/filmroller.py @@ -3,10 +3,11 @@ from time import time from os.path import exists from os import listdir +from ConfigParser import RawConfigParser from Image import frombytes, open as fromfile from ImageTk import PhotoImage from ImageOps import invert, autocontrast, grayscale -from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP, Checkbutton, IntVar, OptionMenu, StringVar +from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP, Checkbutton, OptionMenu, StringVar, BooleanVar from v4l2capture import Video_device ''' @@ -50,16 +51,24 @@ def __init__(self): self.root.bind("", self.single_shot) self.root.bind("q", self.quit) # config: + self.config = RawConfigParser() + self.config.read('filmroller.conf') + if not self.config.has_section('global'): + self.config.add_section('global') self.video = None - self.invert = IntVar() - self.invert.set(1) - self.bw = IntVar() - self.bw.set(0) - self.ac = IntVar() - self.ac.set(1) - self.videodevice = StringVar() + self.invert = BooleanVar(name='invert') + self.invert.set(self.config_get('invert', True)) + self.invert.trace("w", self.configure) + self.bw = BooleanVar(name='bw') + self.bw.set(self.config_get('bw', False)) + self.bw.trace("w", self.configure) + self.auto = BooleanVar(name='auto') + self.auto.set(self.config_get('auto', True)) + self.auto.trace("w", self.configure) + self.videodevice = StringVar(name='videodevice') dev_names = sorted(['/dev/{}'.format(x) for x in listdir("/dev") if x.startswith("video")]) - self.videodevice.set(dev_names[-1]) + self.videodevice.set(self.config_get('videodevice', dev_names[-1])) + self.videodevice.trace("w", self.configure) # Frame.__init__(self, self.root) self.pack() @@ -69,7 +78,7 @@ def __init__(self): self.xt.pack(side='left') self.xb = Checkbutton(self, text='B/W', variable=self.bw) self.xb.pack(side='left') - self.xa = Checkbutton(self, text='Auto', variable=self.ac) + self.xa = Checkbutton(self, text='Auto', variable=self.auto) self.xa.pack(side='left') self.xv = OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video) self.xv.pack(side='left') @@ -84,6 +93,20 @@ def __init__(self): self.first_role() self.start_video() + def config_get(self, name, default): + if not self.config.has_option('global', name): + return default + if isinstance(default, bool): + return self.config.getboolean('global', name) + else: + return self.config.get('global', name) + + def configure(self, name, mode, cbname): + if cbname == 'w': + value = getattr(self, name).get() + self.config.set('global', name, str(value)) + self.config.write(open('filmroller.conf', 'w')) + def first_role(self): ' jump back to first role ' self.role = 'aa' @@ -160,7 +183,7 @@ def live_view(self, delta=3.0): self.image = invert(self.image) if self.bw.get(): self.image = grayscale(self.image) - if self.ac.get(): + if self.auto.get(): self.image = autocontrast(self.image) self.photo = PhotoImage(self.image) self.canvas.create_image(self.previewsize['size_x']/2, self.previewsize['size_y']/2, image=self.photo) @@ -190,7 +213,7 @@ def go(): image = invert(image) if self.bw.get(): image = grayscale(image) - if self.ac.get(): + if self.auto.get(): image = autocontrast(image) image.save(self.filename) self.inc_picture() From bb2afc52fcd032b8da3fea6f771c0a384d87f453 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 14:55:57 +0100 Subject: [PATCH 13/34] adjust comments & doc --- filmroller.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/filmroller.py b/filmroller.py index a81bc37..e40fbf0 100755 --- a/filmroller.py +++ b/filmroller.py @@ -28,7 +28,6 @@ image, imageops and imagetk and the famous v4l2capture. TODO: -- remove hardcoded config - set v4l properties (contrast, hue, sat, ..) - get event from usb dev - reduce redundant code @@ -94,6 +93,7 @@ def __init__(self): self.start_video() def config_get(self, name, default): + ' read a configuration entry, fallback to default if not already stored ' if not self.config.has_option('global', name): return default if isinstance(default, bool): @@ -102,6 +102,7 @@ def config_get(self, name, default): return self.config.get('global', name) def configure(self, name, mode, cbname): + ' change a configuration entry ' if cbname == 'w': value = getattr(self, name).get() self.config.set('global', name, str(value)) @@ -114,6 +115,7 @@ def first_role(self): self.inc_picture() def inc_picture(self): + ' increment the picture number, jump over existing files ' self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) while exists(self.filename): self.serial += 1 @@ -147,6 +149,7 @@ def stop_video(self, *args): self.video = None def restart_video(self, *args): + ' restart video (if device changes or hangs) ' self.stop_video() self.root.after(1, self.start_video) @@ -228,6 +231,7 @@ def go(): def main(): + ' main start point of the program ' app = Cap() app.mainloop() From ee62fac51ffb2d9acf767d11363aeb8394f48dc2 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 15:08:12 +0100 Subject: [PATCH 14/34] fix startup --- filmroller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/filmroller.py b/filmroller.py index e40fbf0..80038ce 100755 --- a/filmroller.py +++ b/filmroller.py @@ -235,4 +235,7 @@ def main(): app = Cap() app.mainloop() -main() +if __name__ == '__main__': + from sys import argv + main(*argv[1:]) +# vim:tw=0:nowrap From 8dd459ed8905bad649bd431895326a65fa84d726 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 15:21:01 +0100 Subject: [PATCH 15/34] adapt README.md --- README => README.md | 6 ++++++ 1 file changed, 6 insertions(+) rename README => README.md (85%) diff --git a/README b/README.md similarity index 85% rename from README rename to README.md index 26f5501..f91259d 100644 --- a/README +++ b/README.md @@ -39,11 +39,17 @@ Example See capture_picture.py, capture_picture_delayed.py and list_devices.py. +The program filmroller.py provided a gui to take captures from a webcam. It +switches resolution between lowes (for a live view) and highes (for the +snapshot). It is quite useful for use with slide and negative film scanners. + Change log ========== (see git log for latest changes) +(2014-12-26) - Added framesize and frameinterval getters. + 1.4 (2011-03-18) - Added support for YUV420 output. 1.3 (2010-07-21) - Added set of capabilities to the return value of From 2504966e2fbc83e7c7945dee2b30479cebe4054e Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Tue, 30 Dec 2014 11:18:24 +0100 Subject: [PATCH 16/34] run indent on c code --- v4l2capture.c | 960 ++++++++++++++++++++++++-------------------------- 1 file changed, 470 insertions(+), 490 deletions(-) diff --git a/v4l2capture.c b/v4l2capture.c index 0b266aa..404db66 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -17,27 +17,26 @@ #include #ifdef USE_LIBV4L -#include +# include #else -#include -#define v4l2_close close -#define v4l2_ioctl ioctl -#define v4l2_mmap mmap -#define v4l2_munmap munmap -#define v4l2_open open +# include +# define v4l2_close close +# define v4l2_ioctl ioctl +# define v4l2_mmap mmap +# define v4l2_munmap munmap +# define v4l2_open open #endif #ifndef Py_TYPE - #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +# define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) #endif -#define ASSERT_OPEN if(self->fd < 0) \ - { \ - PyErr_SetString(PyExc_ValueError, \ - "I/O operation on closed file"); \ - return NULL; \ - } +#define ASSERT_OPEN if(self->fd < 0) { \ + PyErr_SetString(PyExc_ValueError, \ + "I/O operation on closed file"); \ + return NULL; \ + } #define CLEAR(x) memset(&(x), 0, sizeof(x)) @@ -47,8 +46,7 @@ struct buffer { }; typedef struct { - PyObject_HEAD - int fd; + PyObject_HEAD int fd; struct buffer *buffers; int buffer_count; } Video_device; @@ -59,111 +57,104 @@ struct capability { }; static struct capability capabilities[] = { - { V4L2_CAP_ASYNCIO, "asyncio" }, - { V4L2_CAP_AUDIO, "audio" }, - { V4L2_CAP_HW_FREQ_SEEK, "hw_freq_seek" }, - { V4L2_CAP_RADIO, "radio" }, - { V4L2_CAP_RDS_CAPTURE, "rds_capture" }, - { V4L2_CAP_READWRITE, "readwrite" }, - { V4L2_CAP_SLICED_VBI_CAPTURE, "sliced_vbi_capture" }, - { V4L2_CAP_SLICED_VBI_OUTPUT, "sliced_vbi_output" }, - { V4L2_CAP_STREAMING, "streaming" }, - { V4L2_CAP_TUNER, "tuner" }, - { V4L2_CAP_VBI_CAPTURE, "vbi_capture" }, - { V4L2_CAP_VBI_OUTPUT, "vbi_output" }, - { V4L2_CAP_VIDEO_CAPTURE, "video_capture" }, - { V4L2_CAP_VIDEO_OUTPUT, "video_output" }, - { V4L2_CAP_VIDEO_OUTPUT_OVERLAY, "video_output_overlay" }, - { V4L2_CAP_VIDEO_OVERLAY, "video_overlay" } + {V4L2_CAP_ASYNCIO, "asyncio"}, + {V4L2_CAP_AUDIO, "audio"}, + {V4L2_CAP_HW_FREQ_SEEK, "hw_freq_seek"}, + {V4L2_CAP_RADIO, "radio"}, + {V4L2_CAP_RDS_CAPTURE, "rds_capture"}, + {V4L2_CAP_READWRITE, "readwrite"}, + {V4L2_CAP_SLICED_VBI_CAPTURE, "sliced_vbi_capture"}, + {V4L2_CAP_SLICED_VBI_OUTPUT, "sliced_vbi_output"}, + {V4L2_CAP_STREAMING, "streaming"}, + {V4L2_CAP_TUNER, "tuner"}, + {V4L2_CAP_VBI_CAPTURE, "vbi_capture"}, + {V4L2_CAP_VBI_OUTPUT, "vbi_output"}, + {V4L2_CAP_VIDEO_CAPTURE, "video_capture"}, + {V4L2_CAP_VIDEO_OUTPUT, "video_output"}, + {V4L2_CAP_VIDEO_OUTPUT_OVERLAY, "video_output_overlay"}, + {V4L2_CAP_VIDEO_OVERLAY, "video_overlay"} }; -static int my_ioctl(int fd, int request, void *arg) -{ +static int my_ioctl( + int fd, + int request, + void *arg) { // Retry ioctl until it returns without being interrupted. - for(;;) - { - int result = v4l2_ioctl(fd, request, arg); + for (;;) { + int result = v4l2_ioctl(fd, request, arg); - if(!result) - { - return 0; - } + if (!result) { + return 0; + } - if(errno != EINTR) - { - PyErr_SetFromErrno(PyExc_IOError); - return 1; - } + if (errno != EINTR) { + PyErr_SetFromErrno(PyExc_IOError); + return 1; } + } } -static void Video_device_unmap(Video_device *self) -{ +static void Video_device_unmap( + Video_device * self) { int i; - for(i = 0; i < self->buffer_count; i++) - { - v4l2_munmap(self->buffers[i].start, self->buffers[i].length); - } + for (i = 0; i < self->buffer_count; i++) { + v4l2_munmap(self->buffers[i].start, self->buffers[i].length); + } } -static void Video_device_dealloc(Video_device *self) -{ - if(self->fd >= 0) - { - if(self->buffers) - { - Video_device_unmap(self); - } - - v4l2_close(self->fd); +static void Video_device_dealloc( + Video_device * self) { + if (self->fd >= 0) { + if (self->buffers) { + Video_device_unmap(self); } - Py_TYPE(self)->tp_free((PyObject *)self); + v4l2_close(self->fd); + } + + Py_TYPE(self)->tp_free((PyObject *) self); } -static int Video_device_init(Video_device *self, PyObject *args, - PyObject *kwargs) -{ +static int Video_device_init( + Video_device * self, + PyObject * args, + PyObject * kwargs) { const char *device_path; - if(!PyArg_ParseTuple(args, "s", &device_path)) - { - return -1; - } + if (!PyArg_ParseTuple(args, "s", &device_path)) { + return -1; + } int fd = v4l2_open(device_path, O_RDWR | O_NONBLOCK); - if(fd < 0) - { - PyErr_SetFromErrnoWithFilename(PyExc_IOError, (char *)device_path); - return -1; - } + if (fd < 0) { + PyErr_SetFromErrnoWithFilename(PyExc_IOError, (char *) device_path); + return -1; + } self->fd = fd; self->buffers = NULL; return 0; } -static PyObject *Video_device_close(Video_device *self) -{ - if(self->fd >= 0) - { - if(self->buffers) - { - Video_device_unmap(self); - } - - v4l2_close(self->fd); - self->fd = -1; +static PyObject *Video_device_close( + Video_device * self) { + if (self->fd >= 0) { + if (self->buffers) { + Video_device_unmap(self); } + v4l2_close(self->fd); + self->fd = -1; + } + Py_RETURN_NONE; } -static PyObject *Video_device_fileno(Video_device *self) -{ +static PyObject *Video_device_fileno( + Video_device * self) { ASSERT_OPEN; #if PY_MAJOR_VERSION < 3 return PyInt_FromLong(self->fd); @@ -172,59 +163,56 @@ static PyObject *Video_device_fileno(Video_device *self) #endif } -static PyObject *Video_device_get_info(Video_device *self) -{ +static PyObject *Video_device_get_info( + Video_device * self) { ASSERT_OPEN; struct v4l2_capability caps; - if(my_ioctl(self->fd, VIDIOC_QUERYCAP, &caps)) - { - return NULL; - } + if (my_ioctl(self->fd, VIDIOC_QUERYCAP, &caps)) { + return NULL; + } PyObject *set = PySet_New(NULL); - if(!set) - { - return NULL; - } + if (!set) { + return NULL; + } struct capability *capability = capabilities; - while((void *)capability < (void *)capabilities + sizeof(capabilities)) - { - if(caps.capabilities & capability->id) - { + while ((void *) capability < (void *) capabilities + sizeof(capabilities)) { + if (caps.capabilities & capability->id) { #if PY_MAJOR_VERSION < 3 - PyObject *s = PyString_FromString(capability->name); + PyObject *s = PyString_FromString(capability->name); #else - PyObject *s = PyBytes_FromString(capability->name); + PyObject *s = PyBytes_FromString(capability->name); #endif - if(!s) - { - Py_DECREF(set); - return NULL; - } - - PySet_Add(set, s); - } + if (!s) { + Py_DECREF(set); + return NULL; + } - capability++; + PySet_Add(set, s); } + capability++; + } + return Py_BuildValue("sssO", caps.driver, caps.card, caps.bus_info, set); } -static PyObject *Video_device_set_format(Video_device *self, PyObject *args, PyObject *keywds) -{ +static PyObject *Video_device_set_format( + Video_device * self, + PyObject * args, + PyObject * keywds) { int size_x; int size_y; int yuv420 = 0; int fourcc; const char *fourcc_str; int fourcc_len = 0; - static char *kwlist [] = { + static char *kwlist[] = { "size_x", "size_y", "yuv420", @@ -232,17 +220,19 @@ static PyObject *Video_device_set_format(Video_device *self, PyObject *args, PyO NULL }; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "ii|is#", kwlist, &size_x, &size_y, &yuv420, &fourcc_str, &fourcc_len)) - { - return NULL; - } + if (!PyArg_ParseTupleAndKeywords + (args, keywds, "ii|is#", kwlist, &size_x, &size_y, &yuv420, &fourcc_str, + &fourcc_len)) { + return NULL; + } struct v4l2_format format; CLEAR(format); format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - /* Get the current format */ - if(my_ioctl(self->fd, VIDIOC_G_FMT, &format)) - { + /* + Get the current format + */ + if (my_ioctl(self->fd, VIDIOC_G_FMT, &format)) { return NULL; } @@ -256,9 +246,7 @@ static PyObject *Video_device_set_format(Video_device *self, PyObject *args, PyO if (fourcc_len == 4) { fourcc = v4l2_fourcc(fourcc_str[0], - fourcc_str[1], - fourcc_str[2], - fourcc_str[3]); + fourcc_str[1], fourcc_str[2], fourcc_str[3]); format.fmt.pix.pixelformat = fourcc; format.fmt.pix.field = V4L2_FIELD_ANY; } @@ -268,574 +256,565 @@ static PyObject *Video_device_set_format(Video_device *self, PyObject *args, PyO format.fmt.pix.height = size_y; format.fmt.pix.bytesperline = 0; - if(my_ioctl(self->fd, VIDIOC_S_FMT, &format)) - { - return NULL; - } - + if (my_ioctl(self->fd, VIDIOC_S_FMT, &format)) { + return NULL; + } + return Py_BuildValue("ii", format.fmt.pix.width, format.fmt.pix.height); } -static PyObject *Video_device_set_fps(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_set_fps( + Video_device * self, + PyObject * args) { int fps; - if(!PyArg_ParseTuple(args, "i", &fps)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "i", &fps)) { + return NULL; + } struct v4l2_streamparm setfps; CLEAR(setfps); setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; setfps.parm.capture.timeperframe.numerator = 1; setfps.parm.capture.timeperframe.denominator = fps; - if(my_ioctl(self->fd, VIDIOC_S_PARM, &setfps)){ - return NULL; + if (my_ioctl(self->fd, VIDIOC_S_PARM, &setfps)) { + return NULL; } - return Py_BuildValue("i",setfps.parm.capture.timeperframe.denominator); + return Py_BuildValue("i", setfps.parm.capture.timeperframe.denominator); } -static void get_fourcc_str(char *fourcc_str, int fourcc) -{ +static void get_fourcc_str( + char *fourcc_str, + int fourcc) { if (fourcc_str == NULL) - return; - fourcc_str[0] = (char)(fourcc & 0xFF); - fourcc_str[1] = (char)((fourcc >> 8) & 0xFF); - fourcc_str[2] = (char)((fourcc >> 16) & 0xFF); - fourcc_str[3] = (char)((fourcc >> 24) & 0xFF); + return; + fourcc_str[0] = (char) (fourcc & 0xFF); + fourcc_str[1] = (char) ((fourcc >> 8) & 0xFF); + fourcc_str[2] = (char) ((fourcc >> 16) & 0xFF); + fourcc_str[3] = (char) ((fourcc >> 24) & 0xFF); fourcc_str[4] = 0; } -static PyObject *Video_device_get_format(Video_device *self) -{ +static PyObject *Video_device_get_format( + Video_device * self) { struct v4l2_format format; CLEAR(format); format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - /* Get the current format */ - if(my_ioctl(self->fd, VIDIOC_G_FMT, &format)) - { - return NULL; - } + /* + Get the current format + */ + if (my_ioctl(self->fd, VIDIOC_G_FMT, &format)) { + return NULL; + } char current_fourcc[5]; get_fourcc_str(current_fourcc, format.fmt.pix.pixelformat); - return Py_BuildValue("iis", format.fmt.pix.width, format.fmt.pix.height, current_fourcc); + return Py_BuildValue("iis", format.fmt.pix.width, format.fmt.pix.height, + current_fourcc); } -static PyObject *Video_device_get_fourcc(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_get_fourcc( + Video_device * self, + PyObject * args) { char *fourcc_str; int size; int fourcc; - if (!PyArg_ParseTuple(args, "s#", &fourcc_str, &size)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "s#", &fourcc_str, &size)) { + return NULL; + } - if(size < 4) - { - return NULL; - } + if (size < 4) { + return NULL; + } fourcc = v4l2_fourcc(fourcc_str[0], - fourcc_str[1], - fourcc_str[2], - fourcc_str[3]); + fourcc_str[1], fourcc_str[2], fourcc_str[3]); return Py_BuildValue("i", fourcc); } -static PyObject *Video_device_set_auto_white_balance(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_set_auto_white_balance( + Video_device * self, + PyObject * args) { #ifdef V4L2_CID_AUTO_WHITE_BALANCE int autowb; - if(!PyArg_ParseTuple(args, "i", &autowb)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "i", &autowb)) { + return NULL; + } struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_AUTO_WHITE_BALANCE; + ctrl.id = V4L2_CID_AUTO_WHITE_BALANCE; ctrl.value = autowb; - if(my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)){ - return NULL; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_get_auto_white_balance(Video_device *self) -{ +static PyObject *Video_device_get_auto_white_balance( + Video_device * self) { #ifdef V4L2_CID_AUTO_WHITE_BALANCE struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_AUTO_WHITE_BALANCE; - if(my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)){ - return NULL; + ctrl.id = V4L2_CID_AUTO_WHITE_BALANCE; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_set_white_balance_temperature(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_set_white_balance_temperature( + Video_device * self, + PyObject * args) { #ifdef V4L2_CID_WHITE_BALANCE_TEMPERATURE int wb; - if(!PyArg_ParseTuple(args, "i", &wb)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "i", &wb)) { + return NULL; + } struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE; + ctrl.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE; ctrl.value = wb; - if(my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)){ - return NULL; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_get_white_balance_temperature(Video_device *self) -{ +static PyObject *Video_device_get_white_balance_temperature( + Video_device * self) { #ifdef V4L2_CID_WHITE_BALANCE_TEMPERATURE struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE; - if(my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)){ - return NULL; + ctrl.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_set_exposure_absolute(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_set_exposure_absolute( + Video_device * self, + PyObject * args) { #ifdef V4L2_CID_EXPOSURE_ABSOLUTE int exposure; - if(!PyArg_ParseTuple(args, "i", &exposure)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "i", &exposure)) { + return NULL; + } struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE; + ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE; ctrl.value = exposure; - if(my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)){ - return NULL; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_get_exposure_absolute(Video_device *self) -{ +static PyObject *Video_device_get_exposure_absolute( + Video_device * self) { #ifdef V4L2_CID_EXPOSURE_ABSOLUTE struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE; - if(my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)){ - return NULL; + ctrl.id = V4L2_CID_EXPOSURE_ABSOLUTE; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_set_exposure_auto(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_set_exposure_auto( + Video_device * self, + PyObject * args) { #ifdef V4L2_CID_EXPOSURE_AUTO int autoexposure; - if(!PyArg_ParseTuple(args, "i", &autoexposure)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "i", &autoexposure)) { + return NULL; + } struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_EXPOSURE_AUTO; + ctrl.id = V4L2_CID_EXPOSURE_AUTO; ctrl.value = autoexposure; - if(my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)){ - return NULL; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_get_exposure_auto(Video_device *self) -{ +static PyObject *Video_device_get_exposure_auto( + Video_device * self) { #ifdef V4L2_CID_EXPOSURE_AUTO struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_EXPOSURE_AUTO; - if(my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)){ - return NULL; + ctrl.id = V4L2_CID_EXPOSURE_AUTO; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_set_focus_auto(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_set_focus_auto( + Video_device * self, + PyObject * args) { #ifdef V4L2_CID_FOCUS_AUTO int autofocus; - if(!PyArg_ParseTuple(args, "i", &autofocus)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "i", &autofocus)) { + return NULL; + } struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_FOCUS_AUTO; + ctrl.id = V4L2_CID_FOCUS_AUTO; ctrl.value = autofocus; - if(my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)){ - return NULL; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_get_focus_auto(Video_device *self) -{ +static PyObject *Video_device_get_focus_auto( + Video_device * self) { #ifdef V4L2_CID_FOCUS_AUTO struct v4l2_control ctrl; CLEAR(ctrl); - ctrl.id = V4L2_CID_FOCUS_AUTO; - if(my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)){ - return NULL; + ctrl.id = V4L2_CID_FOCUS_AUTO; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; } - return Py_BuildValue("i",ctrl.value); + return Py_BuildValue("i", ctrl.value); #else return NULL; #endif } -static PyObject *Video_device_start(Video_device *self) -{ +static PyObject *Video_device_start( + Video_device * self) { ASSERT_OPEN; enum v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if(my_ioctl(self->fd, VIDIOC_STREAMON, &type)) - { - return NULL; - } + if (my_ioctl(self->fd, VIDIOC_STREAMON, &type)) { + return NULL; + } Py_RETURN_NONE; } -static PyObject *Video_device_stop(Video_device *self) -{ +static PyObject *Video_device_stop( + Video_device * self) { ASSERT_OPEN; enum v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if(my_ioctl(self->fd, VIDIOC_STREAMOFF, &type)) - { - return NULL; - } + if (my_ioctl(self->fd, VIDIOC_STREAMOFF, &type)) { + return NULL; + } Py_RETURN_NONE; } -static PyObject *Video_device_create_buffers(Video_device *self, PyObject *args) -{ +static PyObject *Video_device_create_buffers( + Video_device * self, + PyObject * args) { int buffer_count; - if(!PyArg_ParseTuple(args, "I", &buffer_count)) - { - return NULL; - } + if (!PyArg_ParseTuple(args, "I", &buffer_count)) { + return NULL; + } ASSERT_OPEN; - if(self->buffers) - { - PyErr_SetString(PyExc_ValueError, "Buffers are already created"); - return NULL; - } + if (self->buffers) { + PyErr_SetString(PyExc_ValueError, "Buffers are already created"); + return NULL; + } struct v4l2_requestbuffers reqbuf; reqbuf.count = buffer_count; reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP; - if(my_ioctl(self->fd, VIDIOC_REQBUFS, &reqbuf)) - { - return NULL; - } + if (my_ioctl(self->fd, VIDIOC_REQBUFS, &reqbuf)) { + return NULL; + } - if(!reqbuf.count) - { - PyErr_SetString(PyExc_IOError, "Not enough buffer memory"); - return NULL; - } + if (!reqbuf.count) { + PyErr_SetString(PyExc_IOError, "Not enough buffer memory"); + return NULL; + } self->buffers = malloc(reqbuf.count * sizeof(struct buffer)); - if(!self->buffers) - { - PyErr_NoMemory(); + if (!self->buffers) { + PyErr_NoMemory(); + return NULL; + } + + int i; + + for (i = 0; i < reqbuf.count; i++) { + struct v4l2_buffer buffer; + buffer.index = i; + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + + if (my_ioctl(self->fd, VIDIOC_QUERYBUF, &buffer)) { return NULL; } - int i; + self->buffers[i].length = buffer.length; + self->buffers[i].start = v4l2_mmap(NULL, buffer.length, + PROT_READ | PROT_WRITE, MAP_SHARED, + self->fd, buffer.m.offset); - for(i = 0; i < reqbuf.count; i++) - { - struct v4l2_buffer buffer; - buffer.index = i; - buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buffer.memory = V4L2_MEMORY_MMAP; - - if(my_ioctl(self->fd, VIDIOC_QUERYBUF, &buffer)) - { - return NULL; - } - - self->buffers[i].length = buffer.length; - self->buffers[i].start = v4l2_mmap(NULL, buffer.length, - PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, buffer.m.offset); - - if(self->buffers[i].start == MAP_FAILED) - { - PyErr_SetFromErrno(PyExc_IOError); - return NULL; - } + if (self->buffers[i].start == MAP_FAILED) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; } + } self->buffer_count = i; Py_RETURN_NONE; } -static PyObject *Video_device_queue_all_buffers(Video_device *self) -{ - if(!self->buffers) - { - ASSERT_OPEN; - PyErr_SetString(PyExc_ValueError, "Buffers have not been created"); - return NULL; - } +static PyObject *Video_device_queue_all_buffers( + Video_device * self) { + if (!self->buffers) { + ASSERT_OPEN; + PyErr_SetString(PyExc_ValueError, "Buffers have not been created"); + return NULL; + } int i; int buffer_count = self->buffer_count; - for(i = 0; i < buffer_count; i++) - { - struct v4l2_buffer buffer; - buffer.index = i; - buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buffer.memory = V4L2_MEMORY_MMAP; - - if(my_ioctl(self->fd, VIDIOC_QBUF, &buffer)) - { - return NULL; - } + for (i = 0; i < buffer_count; i++) { + struct v4l2_buffer buffer; + buffer.index = i; + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + + if (my_ioctl(self->fd, VIDIOC_QBUF, &buffer)) { + return NULL; } + } Py_RETURN_NONE; } -static PyObject *Video_device_read_internal(Video_device *self, int queue) -{ - if(!self->buffers) - { - ASSERT_OPEN; - PyErr_SetString(PyExc_ValueError, "Buffers have not been created"); - return NULL; - } +static PyObject *Video_device_read_internal( + Video_device * self, + int queue) { + if (!self->buffers) { + ASSERT_OPEN; + PyErr_SetString(PyExc_ValueError, "Buffers have not been created"); + return NULL; + } struct v4l2_buffer buffer; buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buffer.memory = V4L2_MEMORY_MMAP; - if(my_ioctl(self->fd, VIDIOC_DQBUF, &buffer)) - { - return NULL; - } + if (my_ioctl(self->fd, VIDIOC_DQBUF, &buffer)) { + return NULL; + } #ifdef USE_LIBV4L -#if PY_MAJOR_VERSION < 3 +# if PY_MAJOR_VERSION < 3 PyObject *result = PyString_FromStringAndSize( -#else +# else PyObject *result = PyBytes_FromStringAndSize( -#endif - self->buffers[buffer.index].start, buffer.bytesused); +# endif + self->buffers[buffer.index]. + start, buffer.bytesused); - if(!result) - { - return NULL; - } + if (!result) { + return NULL; + } #else // Convert buffer from YUYV to RGB. // For the byte order, see: http://v4l2spec.bytesex.org/spec/r4339.htm // For the color conversion, see: http://v4l2spec.bytesex.org/spec/x2123.htm int length = buffer.bytesused * 6 / 4; -#if PY_MAJOR_VERSION < 3 +# if PY_MAJOR_VERSION < 3 PyObject *result = PyString_FromStringAndSize(NULL, length); -#else +# else PyObject *result = PyBytes_FromStringAndSize(NULL, length); -#endif +# endif - if(!result) - { - return NULL; - } + if (!result) { + return NULL; + } char *rgb = PyString_AS_STRING(result); char *rgb_max = rgb + length; unsigned char *yuyv = self->buffers[buffer.index].start; -#define CLAMP(c) ((c) <= 0 ? 0 : (c) >= 65025 ? 255 : (c) >> 8) - while(rgb < rgb_max) - { - int u = yuyv[1] - 128; - int v = yuyv[3] - 128; - int uv = 100 * u + 208 * v; - u *= 516; - v *= 409; - - int y = 298 * (yuyv[0] - 16); - rgb[0] = CLAMP(y + v); - rgb[1] = CLAMP(y - uv); - rgb[2] = CLAMP(y + u); - - y = 298 * (yuyv[2] - 16); - rgb[3] = CLAMP(y + v); - rgb[4] = CLAMP(y - uv); - rgb[5] = CLAMP(y + u); - - rgb += 6; - yuyv += 4; - } -#undef CLAMP +# define CLAMP(c) ((c) <= 0 ? 0 : (c) >= 65025 ? 255 : (c) >> 8) + while (rgb < rgb_max) { + int u = yuyv[1] - 128; + int v = yuyv[3] - 128; + int uv = 100 * u + 208 * v; + u *= 516; + v *= 409; + + int y = 298 * (yuyv[0] - 16); + rgb[0] = CLAMP(y + v); + rgb[1] = CLAMP(y - uv); + rgb[2] = CLAMP(y + u); + + y = 298 * (yuyv[2] - 16); + rgb[3] = CLAMP(y + v); + rgb[4] = CLAMP(y - uv); + rgb[5] = CLAMP(y + u); + + rgb += 6; + yuyv += 4; + } +# undef CLAMP #endif - if(queue && my_ioctl(self->fd, VIDIOC_QBUF, &buffer)) - { - return NULL; - } + if (queue && my_ioctl(self->fd, VIDIOC_QBUF, &buffer)) { + return NULL; + } return result; } -static PyObject *Video_device_read(Video_device *self) -{ +static PyObject *Video_device_read( + Video_device * self) { return Video_device_read_internal(self, 0); } -static PyObject *Video_device_read_and_queue(Video_device *self) -{ +static PyObject *Video_device_read_and_queue( + Video_device * self) { return Video_device_read_internal(self, 1); } static PyMethodDef Video_device_methods[] = { - {"close", (PyCFunction)Video_device_close, METH_NOARGS, - "close()\n\n" - "Close video device. Subsequent calls to other methods will fail."}, - {"fileno", (PyCFunction)Video_device_fileno, METH_NOARGS, - "fileno() -> integer \"file descriptor\".\n\n" - "This enables video devices to be passed select.select for waiting " - "until a frame is available for reading."}, - {"get_info", (PyCFunction)Video_device_get_info, METH_NOARGS, - "get_info() -> driver, card, bus_info, capabilities\n\n" - "Returns three strings with information about the video device, and one " - "set containing strings identifying the capabilities of the video " - "device."}, - {"get_fourcc", (PyCFunction)Video_device_get_fourcc, METH_VARARGS, - "get_fourcc(fourcc_string) -> fourcc_int\n\n" - "Return the fourcc string encoded as int."}, - {"get_format", (PyCFunction)Video_device_get_format, METH_NOARGS, - "get_format() -> size_x, size_y, fourcc\n\n" - "Request the current video format."}, - {"set_format", (PyCFunction)Video_device_set_format, METH_VARARGS|METH_KEYWORDS, - "set_format(size_x, size_y, yuv420 = 0, fourcc='MJPEG') -> size_x, size_y\n\n" - "Request the video device to set image size and format. The device may " - "choose another size than requested and will return its choice. The " - "image format will be RGB24 if yuv420 is zero (default) or YUV420 if " - "yuv420 is 1, if fourcc keyword is set that will be the fourcc pixel format used."}, - {"set_fps", (PyCFunction)Video_device_set_fps, METH_VARARGS, - "set_fps(fps) -> fps \n\n" - "Request the video device to set frame per seconds.The device may " - "choose another frame rate than requested and will return its choice. " }, - {"set_auto_white_balance", (PyCFunction)Video_device_set_auto_white_balance, METH_VARARGS, - "set_auto_white_balance(autowb) -> autowb \n\n" - "Request the video device to set auto white balance to value. The device may " - "choose another value than requested and will return its choice. " }, - {"get_auto_white_balance", (PyCFunction)Video_device_get_auto_white_balance, METH_NOARGS, - "get_auto_white_balance() -> autowb \n\n" - "Request the video device to get auto white balance value. " }, - {"set_white_balance_temperature", (PyCFunction)Video_device_set_white_balance_temperature, METH_VARARGS, - "set_white_balance_temperature(temp) -> temp \n\n" - "Request the video device to set white balance tempature to value. The device may " - "choose another value than requested and will return its choice. " }, - {"get_white_balance_temperature", (PyCFunction)Video_device_get_white_balance_temperature, METH_NOARGS, - "get_white_balance_temperature() -> temp \n\n" - "Request the video device to get white balance temperature value. " }, - {"set_exposure_auto", (PyCFunction)Video_device_set_exposure_auto, METH_VARARGS, - "set_exposure_auto(autoexp) -> autoexp \n\n" - "Request the video device to set auto exposure to value. The device may " - "choose another value than requested and will return its choice. " }, - {"get_exposure_auto", (PyCFunction)Video_device_get_exposure_auto, METH_NOARGS, - "get_exposure_auto() -> autoexp \n\n" - "Request the video device to get auto exposure value. " }, - {"set_exposure_absolute", (PyCFunction)Video_device_set_exposure_absolute, METH_VARARGS, - "set_exposure_absolute(exptime) -> exptime \n\n" - "Request the video device to set exposure time to value. The device may " - "choose another value than requested and will return its choice. " }, - {"get_exposure_absolute", (PyCFunction)Video_device_get_exposure_absolute, METH_NOARGS, - "get_exposure_absolute() -> exptime \n\n" - "Request the video device to get exposure time value. " }, - {"set_focus_auto", (PyCFunction)Video_device_set_focus_auto, METH_VARARGS, - "set_auto_focus_auto(autofocus) -> autofocus \n\n" - "Request the video device to set auto focuse on or off. The device may " - "choose another value than requested and will return its choice. " }, - {"get_focus_auto", (PyCFunction)Video_device_get_focus_auto, METH_NOARGS, - "get_focus_auto() -> autofocus \n\n" - "Request the video device to get auto focus value. " }, - {"start", (PyCFunction)Video_device_start, METH_NOARGS, - "start()\n\n" - "Start video capture."}, - {"stop", (PyCFunction)Video_device_stop, METH_NOARGS, - "stop()\n\n" - "Stop video capture."}, - {"create_buffers", (PyCFunction)Video_device_create_buffers, METH_VARARGS, - "create_buffers(count)\n\n" - "Create buffers used for capturing image data. Can only be called once " - "for each video device object."}, - {"queue_all_buffers", (PyCFunction)Video_device_queue_all_buffers, - METH_NOARGS, - "queue_all_buffers()\n\n" - "Let the video device fill all buffers created."}, - {"read", (PyCFunction)Video_device_read, METH_NOARGS, - "read() -> string\n\n" - "Reads image data from a buffer that has been filled by the video " - "device. The image data is in RGB och YUV420 format as decided by " - "'set_format'. The buffer is removed from the queue. Fails if no buffer " - "is filled. Use select.select to check for filled buffers."}, - {"read_and_queue", (PyCFunction)Video_device_read_and_queue, METH_NOARGS, - "read_and_queue()\n\n" - "Same as 'read', but adds the buffer back to the queue so the video " - "device can fill it again."}, + {"close", (PyCFunction) Video_device_close, METH_NOARGS, + "close()\n\n" + "Close video device. Subsequent calls to other methods will fail."}, + {"fileno", (PyCFunction) Video_device_fileno, METH_NOARGS, + "fileno() -> integer \"file descriptor\".\n\n" + "This enables video devices to be passed select.select for waiting " + "until a frame is available for reading."}, + {"get_info", (PyCFunction) Video_device_get_info, METH_NOARGS, + "get_info() -> driver, card, bus_info, capabilities\n\n" + "Returns three strings with information about the video device, and one " + "set containing strings identifying the capabilities of the video " + "device."}, + {"get_fourcc", (PyCFunction) Video_device_get_fourcc, METH_VARARGS, + "get_fourcc(fourcc_string) -> fourcc_int\n\n" + "Return the fourcc string encoded as int."}, + {"get_format", (PyCFunction) Video_device_get_format, METH_NOARGS, + "get_format() -> size_x, size_y, fourcc\n\n" + "Request the current video format."}, + {"set_format", (PyCFunction) Video_device_set_format, + METH_VARARGS | METH_KEYWORDS, + "set_format(size_x, size_y, yuv420 = 0, fourcc='MJPEG') -> size_x, size_y\n\n" + "Request the video device to set image size and format. The device may " + "choose another size than requested and will return its choice. The " + "image format will be RGB24 if yuv420 is zero (default) or YUV420 if " + "yuv420 is 1, if fourcc keyword is set that will be the fourcc pixel format used."}, + {"set_fps", (PyCFunction) Video_device_set_fps, METH_VARARGS, + "set_fps(fps) -> fps \n\n" + "Request the video device to set frame per seconds.The device may " + "choose another frame rate than requested and will return its choice. "}, + {"set_auto_white_balance", + (PyCFunction) Video_device_set_auto_white_balance, METH_VARARGS, + "set_auto_white_balance(autowb) -> autowb \n\n" + "Request the video device to set auto white balance to value. The device may " + "choose another value than requested and will return its choice. "}, + {"get_auto_white_balance", + (PyCFunction) Video_device_get_auto_white_balance, METH_NOARGS, + "get_auto_white_balance() -> autowb \n\n" + "Request the video device to get auto white balance value. "}, + {"set_white_balance_temperature", + (PyCFunction) Video_device_set_white_balance_temperature, METH_VARARGS, + "set_white_balance_temperature(temp) -> temp \n\n" + "Request the video device to set white balance tempature to value. The device may " + "choose another value than requested and will return its choice. "}, + {"get_white_balance_temperature", + (PyCFunction) Video_device_get_white_balance_temperature, METH_NOARGS, + "get_white_balance_temperature() -> temp \n\n" + "Request the video device to get white balance temperature value. "}, + {"set_exposure_auto", (PyCFunction) Video_device_set_exposure_auto, + METH_VARARGS, + "set_exposure_auto(autoexp) -> autoexp \n\n" + "Request the video device to set auto exposure to value. The device may " + "choose another value than requested and will return its choice. "}, + {"get_exposure_auto", (PyCFunction) Video_device_get_exposure_auto, + METH_NOARGS, + "get_exposure_auto() -> autoexp \n\n" + "Request the video device to get auto exposure value. "}, + {"set_exposure_absolute", (PyCFunction) Video_device_set_exposure_absolute, + METH_VARARGS, + "set_exposure_absolute(exptime) -> exptime \n\n" + "Request the video device to set exposure time to value. The device may " + "choose another value than requested and will return its choice. "}, + {"get_exposure_absolute", (PyCFunction) Video_device_get_exposure_absolute, + METH_NOARGS, + "get_exposure_absolute() -> exptime \n\n" + "Request the video device to get exposure time value. "}, + {"set_focus_auto", (PyCFunction) Video_device_set_focus_auto, METH_VARARGS, + "set_auto_focus_auto(autofocus) -> autofocus \n\n" + "Request the video device to set auto focuse on or off. The device may " + "choose another value than requested and will return its choice. "}, + {"get_focus_auto", (PyCFunction) Video_device_get_focus_auto, METH_NOARGS, + "get_focus_auto() -> autofocus \n\n" + "Request the video device to get auto focus value. "}, + {"start", (PyCFunction) Video_device_start, METH_NOARGS, + "start()\n\n" "Start video capture."}, + {"stop", (PyCFunction) Video_device_stop, METH_NOARGS, + "stop()\n\n" "Stop video capture."}, + {"create_buffers", (PyCFunction) Video_device_create_buffers, METH_VARARGS, + "create_buffers(count)\n\n" + "Create buffers used for capturing image data. Can only be called once " + "for each video device object."}, + {"queue_all_buffers", (PyCFunction) Video_device_queue_all_buffers, + METH_NOARGS, + "queue_all_buffers()\n\n" + "Let the video device fill all buffers created."}, + {"read", (PyCFunction) Video_device_read, METH_NOARGS, + "read() -> string\n\n" + "Reads image data from a buffer that has been filled by the video " + "device. The image data is in RGB och YUV420 format as decided by " + "'set_format'. The buffer is removed from the queue. Fails if no buffer " + "is filled. Use select.select to check for filled buffers."}, + {"read_and_queue", (PyCFunction) Video_device_read_and_queue, METH_NOARGS, + "read_and_queue()\n\n" + "Same as 'read', but adds the buffer back to the queue so the video " + "device can fill it again."}, {NULL} }; @@ -845,13 +824,13 @@ static PyTypeObject Video_device_type = { #else PyVarObject_HEAD_INIT(NULL, 0) #endif - "v4l2capture.Video_device", sizeof(Video_device), 0, - (destructor)Video_device_dealloc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, Py_TPFLAGS_DEFAULT, "Video_device(path)\n\nOpens the video device at " - "the given path and returns an object that can capture images. The " - "constructor and all methods except close may raise IOError.", 0, 0, 0, - 0, 0, 0, Video_device_methods, 0, 0, 0, 0, 0, 0, 0, - (initproc)Video_device_init + "v4l2capture.Video_device", sizeof(Video_device), 0, + (destructor) Video_device_dealloc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, Py_TPFLAGS_DEFAULT, "Video_device(path)\n\nOpens the video device at " + "the given path and returns an object that can capture images. The " + "constructor and all methods except close may raise IOError.", 0, 0, 0, + 0, 0, 0, Video_device_methods, 0, 0, 0, 0, 0, 0, 0, + (initproc) Video_device_init }; static PyMethodDef module_methods[] = { @@ -859,27 +838,28 @@ static PyMethodDef module_methods[] = { }; #if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC initv4l2capture(void) +PyMODINIT_FUNC initv4l2capture( + void) #else -PyMODINIT_FUNC PyInit_v4l2capture(void) +PyMODINIT_FUNC PyInit_v4l2capture( + void) #endif { Video_device_type.tp_new = PyType_GenericNew; - if(PyType_Ready(&Video_device_type) < 0) - { + if (PyType_Ready(&Video_device_type) < 0) { #if PY_MAJOR_VERSION < 3 - return; + return; #else - return NULL; + return NULL; #endif - } + } PyObject *module; #if PY_MAJOR_VERSION < 3 module = Py_InitModule3("v4l2capture", module_methods, - "Capture video with video4linux2."); + "Capture video with video4linux2."); #else static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, @@ -895,17 +875,17 @@ PyMODINIT_FUNC PyInit_v4l2capture(void) module = PyModule_Create(&moduledef); #endif - if(!module) - { + if (!module) { #if PY_MAJOR_VERSION < 3 - return; + return; #else - return NULL; + return NULL; #endif - } + } Py_INCREF(&Video_device_type); - PyModule_AddObject(module, "Video_device", (PyObject *)&Video_device_type); + PyModule_AddObject(module, "Video_device", + (PyObject *) & Video_device_type); #if PY_MAJOR_VERSION >= 3 return module; #endif From a5d6e01ed8adf68bd7f379a950b09ce6955fe128 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Tue, 30 Dec 2014 11:19:07 +0100 Subject: [PATCH 17/34] add getter for frame size & interval --- v4l2capture.c | 166 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 151 insertions(+), 15 deletions(-) diff --git a/v4l2capture.c b/v4l2capture.c index 404db66..f3abe71 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -9,8 +9,6 @@ // purpose, without any conditions, unless such conditions are // required by law. -#define USE_LIBV4L - #include #include #include @@ -31,6 +29,9 @@ # define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) #endif +#if PY_MAJOR_VERSION < 3 +# define PyLong_FromLong PyInt_FromLong +#endif #define ASSERT_OPEN if(self->fd < 0) { \ PyErr_SetString(PyExc_ValueError, \ @@ -80,19 +81,15 @@ static int my_ioctl( int request, void *arg) { // Retry ioctl until it returns without being interrupted. - - for (;;) { - int result = v4l2_ioctl(fd, request, arg); - - if (!result) { - return 0; - } - - if (errno != EINTR) { + int result = -1; + while (result < 0) { + result = v4l2_ioctl(fd, request, arg); + if (result < 0 && errno != EINTR) { PyErr_SetFromErrno(PyExc_IOError); return 1; } } + return 0; } static void Video_device_unmap( @@ -156,11 +153,7 @@ static PyObject *Video_device_close( static PyObject *Video_device_fileno( Video_device * self) { ASSERT_OPEN; -#if PY_MAJOR_VERSION < 3 - return PyInt_FromLong(self->fd); -#else return PyLong_FromLong(self->fd); -#endif } static PyObject *Video_device_get_info( @@ -479,6 +472,142 @@ static PyObject *Video_device_get_exposure_auto( #endif } +static PyObject *Video_device_get_framesizes( + Video_device * self, + PyObject * args) { + struct v4l2_frmsizeenum frmsize; + CLEAR(frmsize); + char *fourcc_str; + int size; + if (!PyArg_ParseTuple(args, "s#", &fourcc_str, &size)) { + return NULL; + } + if (size != 4) { + return NULL; + } + frmsize.pixel_format = v4l2_fourcc(fourcc_str[0], + fourcc_str[1], + fourcc_str[2], fourcc_str[3]); + frmsize.index = 0; + PyObject *ret = PyList_New(0); + while (!my_ioctl(self->fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)) { + PyObject *cap = PyDict_New(); + switch (frmsize.type) { + case V4L2_FRMSIZE_TYPE_DISCRETE: + { + PyDict_SetItemString(cap, "size_x", + PyLong_FromLong(frmsize.discrete.width)); + PyDict_SetItemString(cap, "size_y", + PyLong_FromLong(frmsize.discrete.height)); + } + break; + case V4L2_FRMSIZE_TYPE_STEPWISE: + { + PyDict_SetItemString(cap, "min_width", + PyLong_FromLong(frmsize.stepwise.min_width)); + PyDict_SetItemString(cap, "min_height", + PyLong_FromLong(frmsize.stepwise.min_height)); + PyDict_SetItemString(cap, "max_width", + PyLong_FromLong(frmsize.stepwise.max_width)); + PyDict_SetItemString(cap, "max_height", + PyLong_FromLong(frmsize.stepwise.max_height)); + PyDict_SetItemString(cap, "step_width", + PyLong_FromLong(frmsize.stepwise.step_width)); + PyDict_SetItemString(cap, "step_height", + PyLong_FromLong(frmsize.stepwise.step_height)); + } + break; + } + PyList_Append(ret, cap); + frmsize.index++; + } + PyErr_Clear(); + return ret; +} + +static double fract2sec( + const struct v4l2_fract *f) { + return (double) f->numerator / f->denominator; +} + +static double fract2fps( + const struct v4l2_fract *f) { + return (double) f->denominator / f->numerator; +} + +static PyObject *Video_device_get_frameintervals( + Video_device * self, + PyObject * args) { + struct v4l2_frmivalenum frmival; + CLEAR(frmival); + char *fourcc_str; + int size; + if (!PyArg_ParseTuple + (args, "s#ii", &fourcc_str, &size, &frmival.width, &frmival.height)) { + return NULL; + } + if (size != 4) { + return NULL; + } + frmival.pixel_format = v4l2_fourcc(fourcc_str[0], + fourcc_str[1], + fourcc_str[2], fourcc_str[3]); + frmival.index = 0; + PyObject *ret = PyList_New(0); + while (!my_ioctl(self->fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)) { + PyObject *cap = PyDict_New(); + switch (frmival.type) { + case V4L2_FRMIVAL_TYPE_DISCRETE: + { + PyDict_SetItemString(cap, "interval", + PyFloat_FromDouble(fract2sec(&frmival.discrete))); + PyDict_SetItemString(cap, "fps", + PyFloat_FromDouble(fract2fps(&frmival.discrete))); + } + break; + case V4L2_FRMIVAL_TYPE_CONTINUOUS: + { + PyDict_SetItemString(cap, "interval_min", + PyFloat_FromDouble(fract2sec + (&frmival.stepwise.min))); + PyDict_SetItemString(cap, "interval_max", + PyFloat_FromDouble(fract2sec + (&frmival.stepwise.max))); + PyDict_SetItemString(cap, "fps_max", + PyFloat_FromDouble(fract2fps + (&frmival.stepwise.max))); + PyDict_SetItemString(cap, "fps_min", + PyFloat_FromDouble(fract2fps + (&frmival.stepwise.min))); + } + break; + case V4L2_FRMIVAL_TYPE_STEPWISE: + { + PyDict_SetItemString(cap, "interval_min", + PyFloat_FromDouble(fract2sec + (&frmival.stepwise.min))); + PyDict_SetItemString(cap, "interval_max", + PyFloat_FromDouble(fract2sec + (&frmival.stepwise.max))); + PyDict_SetItemString(cap, "interval_step", + PyFloat_FromDouble(fract2sec + (&frmival.stepwise.step))); + PyDict_SetItemString(cap, "fps_max", + PyFloat_FromDouble(fract2fps + (&frmival.stepwise.max))); + PyDict_SetItemString(cap, "fps_min", + PyFloat_FromDouble(fract2fps + (&frmival.stepwise.min))); + } + break; + } + PyList_Append(ret, cap); + frmival.index++; + } + PyErr_Clear(); + return ret; +} + static PyObject *Video_device_set_focus_auto( Video_device * self, PyObject * args) { @@ -793,6 +922,13 @@ static PyMethodDef Video_device_methods[] = { {"get_focus_auto", (PyCFunction) Video_device_get_focus_auto, METH_NOARGS, "get_focus_auto() -> autofocus \n\n" "Request the video device to get auto focus value. "}, + {"get_framesizes", (PyCFunction) Video_device_get_framesizes, METH_VARARGS, + "get_framesizes() -> framesizes \n\n" + "Request the framesizes suported by the device. "}, + {"get_frameintervals", (PyCFunction) Video_device_get_frameintervals, + METH_VARARGS, + "get_frameintervals() -> frameintervals \n\n" + "Request the frameintervals suported by the device. "}, {"start", (PyCFunction) Video_device_start, METH_NOARGS, "start()\n\n" "Start video capture."}, {"stop", (PyCFunction) Video_device_stop, METH_NOARGS, From 4c14354180f872b56ebd30e50b1d1bbeab3b80bb Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Tue, 30 Dec 2014 11:19:46 +0100 Subject: [PATCH 18/34] add new feature to sample code --- list_devices.py | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/list_devices.py b/list_devices.py index 294d17d..dd01bdd 100755 --- a/list_devices.py +++ b/list_devices.py @@ -12,17 +12,37 @@ import os import v4l2capture + +def exc_get(f, *args): + try: + return f(*args) + except Exception, e: + return str(e) + file_names = [x for x in os.listdir("/dev") if x.startswith("video")] file_names.sort() for file_name in file_names: - path = "/dev/" + file_name - print path - try: - video = v4l2capture.Video_device(path) - driver, card, bus_info, capabilities = video.get_info() - print " driver: %s\n card: %s" \ - "\n bus info: %s\n capabilities: %s" % ( - driver, card, bus_info, ", ".join(capabilities)) - video.close() - except IOError, e: - print " " + str(e) + path = "/dev/" + file_name + print path + try: + video = v4l2capture.Video_device(path) + driver, card, bus_info, capabilities = video.get_info() + print "\tDriver:", driver + print "\tCard:", card + print "\tBus-info:", bus_info + print "\tCapabilities:", ", ".join(capabilities) + width, height, fourcc = video.get_format() + print "\tWidth:", width + print "\tHeight:", height + print "\tFourcc:", fourcc + for cap in exc_get(video.get_framesizes, fourcc): + print "\tFrame-size:", cap + for cap in exc_get(video.get_frameintervals, fourcc, width, height): + print "\tFrame-interval:", cap + print "\tAuto-white-balance:", exc_get(video.get_auto_white_balance) + print "\tWhite-balance-temperature:", exc_get(video.get_white_balance_temperature) + print "\tAuto-exposure:", exc_get(video.get_exposure_auto) + print "\tExposure-absolute:", exc_get(video.get_exposure_absolute) + print "\tAuto-focus:", exc_get(video.get_focus_auto) + finally: + video.close() From eb6da28c21b903fc71f7b05e6b2d682d6ab98c99 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Tue, 30 Dec 2014 11:20:04 +0100 Subject: [PATCH 19/34] collect v4l library switch to one location --- README.md | 17 ++++++++++++----- setup.py | 4 +++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f91259d..9fefd6f 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,19 @@ Installation v4l2capture requires libv4l by default. You can compile v4l2capture without libv4l, but that reduces image format support to YUYV input -and RGB output only. You can do so by erasing ', libraries = ["v4l2"]' -in setup.py and erasing '#define USE_LIBV4L' in v4l2capture.c. +and RGB output only. You can do so by commenting the line: -python-v4l2capture uses distutils. -To build: ./setup.py build -To build and install: ./setup.py install + libraries=["v4l2"], extra_compile_args=['-DUSE_LIBV4L', ], + +in setup.py. + +python-v4l2capture uses distutils. To build: + + ./setup.py build + +To build and install: + + ./setup.py install Example ======= diff --git a/setup.py b/setup.py index 1edfef7..7c3bd16 100755 --- a/setup.py +++ b/setup.py @@ -25,4 +25,6 @@ "License :: Public Domain", "Programming Language :: C"], ext_modules = [ - Extension("v4l2capture", ["v4l2capture.c"], libraries = ["v4l2"])]) + Extension("v4l2capture", ["v4l2capture.c"], + libraries=["v4l2"], extra_compile_args=['-DUSE_LIBV4L', ], + )]) From b57a1bfc6a318f99dc3e766d2ab22e92d245a6eb Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 15:27:41 +0100 Subject: [PATCH 20/34] add 'x' to quit programm --- filmroller.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/filmroller.py b/filmroller.py index 80038ce..55966e4 100755 --- a/filmroller.py +++ b/filmroller.py @@ -46,9 +46,10 @@ def __init__(self): # go! self.root = Tk() self.root.bind('', self.stop_video) - self.root.bind("", self.single_shot) - self.root.bind("", self.single_shot) - self.root.bind("q", self.quit) + self.root.bind('', self.single_shot) + self.root.bind('', self.single_shot) + self.root.bind('q', self.quit) + self.root.bind('x', self.quit) # config: self.config = RawConfigParser() self.config.read('filmroller.conf') @@ -57,17 +58,17 @@ def __init__(self): self.video = None self.invert = BooleanVar(name='invert') self.invert.set(self.config_get('invert', True)) - self.invert.trace("w", self.configure) + self.invert.trace('w', self.configure) self.bw = BooleanVar(name='bw') self.bw.set(self.config_get('bw', False)) - self.bw.trace("w", self.configure) + self.bw.trace('w', self.configure) self.auto = BooleanVar(name='auto') self.auto.set(self.config_get('auto', True)) - self.auto.trace("w", self.configure) + self.auto.trace('w', self.configure) self.videodevice = StringVar(name='videodevice') - dev_names = sorted(['/dev/{}'.format(x) for x in listdir("/dev") if x.startswith("video")]) + dev_names = sorted(['/dev/{}'.format(x) for x in listdir('/dev') if x.startswith('video')]) self.videodevice.set(self.config_get('videodevice', dev_names[-1])) - self.videodevice.trace("w", self.configure) + self.videodevice.trace('w', self.configure) # Frame.__init__(self, self.root) self.pack() From f590cc8616c08cb47c2da5cdde3d72dea7ec1cc7 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 15:29:17 +0100 Subject: [PATCH 21/34] fix typos in README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fefd6f..a6f62ca 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,9 @@ Example See capture_picture.py, capture_picture_delayed.py and list_devices.py. The program filmroller.py provided a gui to take captures from a webcam. It -switches resolution between lowes (for a live view) and highes (for the -snapshot). It is quite useful for use with slide and negative film scanners. +switches resolution between lowest resolution (for a live view) and highest +resolution (for the snapshot). It is quite useful to be used with slide and +negative film scanners. Change log ========== From 0261db7d9ea9b490b760be6f9d94c604dbf092f0 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Fri, 26 Dec 2014 20:21:31 +0100 Subject: [PATCH 22/34] add image of scanner --- README.md | 2 ++ filmroller.device.jpg | Bin 0 -> 11374 bytes filmroller.py | 1 + 3 files changed, 3 insertions(+) create mode 100644 filmroller.device.jpg diff --git a/README.md b/README.md index a6f62ca..a997076 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ switches resolution between lowest resolution (for a live view) and highest resolution (for the snapshot). It is quite useful to be used with slide and negative film scanners. +![Sample picture of scanner](filmroller.device.jpg) + Change log ========== diff --git a/filmroller.device.jpg b/filmroller.device.jpg new file mode 100644 index 0000000000000000000000000000000000000000..49b8fc8b0b476b4abb53c15c1a8be46396004a00 GIT binary patch literal 11374 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 literal 0 HcmV?d00001 diff --git a/filmroller.py b/filmroller.py index 55966e4..cfab4b6 100755 --- a/filmroller.py +++ b/filmroller.py @@ -31,6 +31,7 @@ - set v4l properties (contrast, hue, sat, ..) - get event from usb dev - reduce redundant code +- different target dir ''' def ascii_increment(val): From 5c962743e9a911889038297c16ef27c69e2a9346 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Sun, 28 Dec 2014 16:22:48 +0100 Subject: [PATCH 23/34] fix dev name conf --- filmroller.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/filmroller.py b/filmroller.py index cfab4b6..6d68b38 100755 --- a/filmroller.py +++ b/filmroller.py @@ -2,12 +2,12 @@ from select import select from time import time from os.path import exists -from os import listdir +from os import listdir, makedirs from ConfigParser import RawConfigParser from Image import frombytes, open as fromfile from ImageTk import PhotoImage from ImageOps import invert, autocontrast, grayscale -from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP, Checkbutton, OptionMenu, StringVar, BooleanVar +from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP, Checkbutton, OptionMenu, StringVar, BooleanVar, Menu from v4l2capture import Video_device ''' @@ -32,6 +32,8 @@ - get event from usb dev - reduce redundant code - different target dir +- support rotation (incl preview) +- show countdown during take ''' def ascii_increment(val): @@ -51,6 +53,15 @@ def __init__(self): self.root.bind('', self.single_shot) self.root.bind('q', self.quit) self.root.bind('x', self.quit) + # + self.menu = Menu(self.root) + self.root.config(menu=self.menu) + filemenu = Menu(self.menu) + self.menu.add_cascade(label="File", menu=filemenu, ) + filemenu.add_command(label="New", ) + filemenu.add_command(label="Open...", ) + filemenu.add_separator() + filemenu.add_command(label="Exit", ) # config: self.config = RawConfigParser() self.config.read('filmroller.conf') @@ -68,8 +79,14 @@ def __init__(self): self.auto.trace('w', self.configure) self.videodevice = StringVar(name='videodevice') dev_names = sorted(['/dev/{}'.format(x) for x in listdir('/dev') if x.startswith('video')]) - self.videodevice.set(self.config_get('videodevice', dev_names[-1])) + d = self.config_get('videodevice', dev_names[-1]) + if not d in dev_names: + d = dev_names[-1] + self.videodevice.set(d) self.videodevice.trace('w', self.configure) + self.path = 'filmroller' + if not exists(self.path): + makedirs(self.path) # Frame.__init__(self, self.root) self.pack() @@ -118,10 +135,10 @@ def first_role(self): def inc_picture(self): ' increment the picture number, jump over existing files ' - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + self.filename = '{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, ) while exists(self.filename): self.serial += 1 - self.filename = 'scanned.{}-{:04}.jpg'.format(self.role, self.serial, ) + self.filename = '{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, ) self.root.title('filmroller - ' + self.filename) self.fnl['text'] = self.filename self.root.title('filmroller - ' + self.filename) @@ -192,7 +209,7 @@ def live_view(self, delta=3.0): self.image = autocontrast(self.image) self.photo = PhotoImage(self.image) self.canvas.create_image(self.previewsize['size_x']/2, self.previewsize['size_y']/2, image=self.photo) - self.root.after(1, self.live_view) + self.root.after(3, self.live_view) def single_shot(self, *args): ' do a high res single shot and store it ' @@ -213,6 +230,7 @@ def go(): for n in range(7): # wait for auto select((self.video, ), (), ()) data = self.video.read_and_queue() + self.update_idletasks() image = frombytes('RGB', (self.highressize['size_x'], self.highressize['size_y'], ), data) if self.invert.get(): image = invert(image) @@ -222,6 +240,7 @@ def go(): image = autocontrast(image) image.save(self.filename) self.inc_picture() + # self.root.beep() self.video.stop() finally: self.video.close() From 17d3be140191f484051b91324e6dc45f9085538b Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Sun, 28 Dec 2014 16:31:57 +0100 Subject: [PATCH 24/34] prepare rotate --- filmroller.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/filmroller.py b/filmroller.py index 6d68b38..d586455 100755 --- a/filmroller.py +++ b/filmroller.py @@ -87,10 +87,11 @@ def __init__(self): self.path = 'filmroller' if not exists(self.path): makedirs(self.path) + self.degree = 0 # Frame.__init__(self, self.root) self.pack() - self.canvas = Canvas(self, width=640, height=480, ) + self.canvas = Canvas(self, width=640, height=640, ) self.canvas.pack(side='top') self.xt = Checkbutton(self, text='Invert', variable=self.invert) self.xt.pack(side='left') @@ -154,7 +155,7 @@ def set_pauseimage(self): self.image = fromfile('filmroller.pause.png') self.image.thumbnail((self.previewsize['size_x'], self.previewsize['size_y'], ), ) self.photo = PhotoImage(self.image) - self.canvas.create_image(self.previewsize['size_x']/2, self.previewsize['size_y']/2, image=self.photo) + self.canvas.create_image(640/2, 640/2, image=self.photo) def quit(self, event): ' quit program ' @@ -194,6 +195,7 @@ def start_video(self, *args): #self.canvas.width=640 #self.canvas.height=480 #self.canvas.pack(side='top') + self.degree = 0 def live_view(self, delta=3.0): ' show single pic live view and ask tk to call us again later ' @@ -207,8 +209,10 @@ def live_view(self, delta=3.0): self.image = grayscale(self.image) if self.auto.get(): self.image = autocontrast(self.image) + if self.degree: + self.image = self.image.rotate(self.degree) self.photo = PhotoImage(self.image) - self.canvas.create_image(self.previewsize['size_x']/2, self.previewsize['size_y']/2, image=self.photo) + self.canvas.create_image(640/2, 640/2, image=self.photo) self.root.after(3, self.live_view) def single_shot(self, *args): @@ -238,9 +242,11 @@ def go(): image = grayscale(image) if self.auto.get(): image = autocontrast(image) + if self.degree: + self.image = self.image.rotate(self.degree) image.save(self.filename) self.inc_picture() - # self.root.beep() + self.root.bell() self.video.stop() finally: self.video.close() From 45543e422d752f145871080e1a428b1f3a29ec6f Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Mon, 29 Dec 2014 10:20:48 +0100 Subject: [PATCH 25/34] rename members to be easier to understand --- filmroller.py | 106 +++++++++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/filmroller.py b/filmroller.py index d586455..c772373 100755 --- a/filmroller.py +++ b/filmroller.py @@ -48,20 +48,15 @@ def __init__(self): ' set defaults, create widgets, bind callbacks, start live view ' # go! self.root = Tk() - self.root.bind('', self.stop_video) - self.root.bind('', self.single_shot) - self.root.bind('', self.single_shot) - self.root.bind('q', self.quit) - self.root.bind('x', self.quit) - # - self.menu = Menu(self.root) - self.root.config(menu=self.menu) - filemenu = Menu(self.menu) - self.menu.add_cascade(label="File", menu=filemenu, ) - filemenu.add_command(label="New", ) - filemenu.add_command(label="Open...", ) - filemenu.add_separator() - filemenu.add_command(label="Exit", ) + self.root.bind('', self.do_stop_video) + self.root.bind('', self.do_single_shot) + self.root.bind('', self.do_single_shot) + self.root.bind('q', self.do_quit) + self.root.bind('i', self.do_toggle_invert) + self.root.bind('a', self.do_toggle_auto) + self.root.bind('b', self.do_toggle_bw) + self.root.bind('x', self.do_quit) + self.root.bind('', self.do_single_shot) # config: self.config = RawConfigParser() self.config.read('filmroller.conf') @@ -70,20 +65,29 @@ def __init__(self): self.video = None self.invert = BooleanVar(name='invert') self.invert.set(self.config_get('invert', True)) - self.invert.trace('w', self.configure) + self.invert.trace('w', self.do_configure) self.bw = BooleanVar(name='bw') self.bw.set(self.config_get('bw', False)) - self.bw.trace('w', self.configure) + self.bw.trace('w', self.do_configure) self.auto = BooleanVar(name='auto') self.auto.set(self.config_get('auto', True)) - self.auto.trace('w', self.configure) + self.auto.trace('w', self.do_configure) self.videodevice = StringVar(name='videodevice') dev_names = sorted(['/dev/{}'.format(x) for x in listdir('/dev') if x.startswith('video')]) d = self.config_get('videodevice', dev_names[-1]) if not d in dev_names: d = dev_names[-1] self.videodevice.set(d) - self.videodevice.trace('w', self.configure) + self.videodevice.trace('w', self.do_configure) + # + self.menu = Menu(self.root) + self.root.config(menu=self.menu) + filemenu = Menu(self.menu) + self.menu.add_cascade(label=self.videodevice.get(), menu=filemenu, ) + for n in dev_names: + filemenu.add_command(label=n, ) + #filemenu.add_separator() + # self.path = 'filmroller' if not exists(self.path): makedirs(self.path) @@ -93,6 +97,7 @@ def __init__(self): self.pack() self.canvas = Canvas(self, width=640, height=640, ) self.canvas.pack(side='top') + self.canvas.bind('', self.do_change_rotation) self.xt = Checkbutton(self, text='Invert', variable=self.invert) self.xt.pack(side='left') self.xb = Checkbutton(self, text='B/W', variable=self.bw) @@ -101,16 +106,34 @@ def __init__(self): self.xa.pack(side='left') self.xv = OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video) self.xv.pack(side='left') - self.resetrole = Button(self, text='First role', command=self.first_role) + self.resetrole = Button(self, text='First role', command=self.do_first_role) self.resetrole.pack(side='left') self.fnl = Label(self) self.fnl.pack(side='left') - self.nextrole = Button(self, text='Next role', command=self.inc_role) + self.nextrole = Button(self, text='Next role', command=self.do_inc_role) self.nextrole.pack(side='left') - self.take = Button(self, text='Take!', command=self.single_shot) + self.take = Button(self, text='Take!', command=self.do_single_shot) self.take.pack(side='right') - self.first_role() - self.start_video() + self.do_first_role() + self.do_start_video() + + def do_toggle_invert(self, *args): + self.invert.set(not self.invert.get()) + + def do_toggle_auto(self, *args): + self.auto.set(not self.auto.get()) + + def do_toggle_bw(self, *args): + self.bw.set(not self.bw.get()) + + def do_change_rotation(self, event): + ' determine where the image was clicked and turn that to the top ' + if event.x < 200: + self.degree = -90 + elif event.x > 640 - 200: + self.degree = 90 + else: + self.degree = 0 def config_get(self, name, default): ' read a configuration entry, fallback to default if not already stored ' @@ -121,14 +144,14 @@ def config_get(self, name, default): else: return self.config.get('global', name) - def configure(self, name, mode, cbname): + def do_configure(self, name, mode, cbname): ' change a configuration entry ' if cbname == 'w': value = getattr(self, name).get() self.config.set('global', name, str(value)) self.config.write(open('filmroller.conf', 'w')) - def first_role(self): + def do_first_role(self): ' jump back to first role ' self.role = 'aa' self.serial = 0 @@ -144,7 +167,7 @@ def inc_picture(self): self.fnl['text'] = self.filename self.root.title('filmroller - ' + self.filename) - def inc_role(self): + def do_inc_role(self): ' increment to next role ' self.serial = 0 self.role = ascii_increment(self.role) @@ -157,11 +180,11 @@ def set_pauseimage(self): self.photo = PhotoImage(self.image) self.canvas.create_image(640/2, 640/2, image=self.photo) - def quit(self, event): - ' quit program ' + def do_quit(self, event): + ' exit program ' self.root.destroy() - def stop_video(self, *args): + def do_stop_video(self, *args): ' stop video and release device ' if self.video is not None: self.video.stop() @@ -170,10 +193,10 @@ def stop_video(self, *args): def restart_video(self, *args): ' restart video (if device changes or hangs) ' - self.stop_video() - self.root.after(1, self.start_video) + self.do_stop_video() + self.root.after(1, self.do_start_video) - def start_video(self, *args): + def do_start_video(self, *args): ' init video and start live view ' if self.video is None: self.video = Video_device(self.videodevice.get()) @@ -191,13 +214,13 @@ def start_video(self, *args): self.video.create_buffers(30) self.video.queue_all_buffers() self.video.start() - self.root.after(1, self.live_view) + self.root.after(1, self.do_live_view) #self.canvas.width=640 #self.canvas.height=480 #self.canvas.pack(side='top') self.degree = 0 - def live_view(self, delta=3.0): + def do_live_view(self, delta=3.0): ' show single pic live view and ask tk to call us again later ' if self.video is not None: select((self.video, ), (), ()) @@ -213,11 +236,11 @@ def live_view(self, delta=3.0): self.image = self.image.rotate(self.degree) self.photo = PhotoImage(self.image) self.canvas.create_image(640/2, 640/2, image=self.photo) - self.root.after(3, self.live_view) + self.root.after(3, self.do_live_view) - def single_shot(self, *args): + def do_single_shot(self, *args): ' do a high res single shot and store it ' - def go(): + def _go(): self.video = Video_device(self.videodevice.get()) try: self.highressize['size_x'], self.highressize['size_y'] = self.video.set_format( @@ -243,7 +266,7 @@ def go(): if self.auto.get(): image = autocontrast(image) if self.degree: - self.image = self.image.rotate(self.degree) + image = image.rotate(self.degree) image.save(self.filename) self.inc_picture() self.root.bell() @@ -251,10 +274,11 @@ def go(): finally: self.video.close() self.video = None - self.root.after(1, self.start_video) - self.stop_video() + self.root.after(1, self.do_start_video) + self.do_stop_video() self.set_pauseimage() - self.root.after(1, go) + self.update_idletasks() + self.root.after(1, _go) def main(): From 915380bb72257cff5a74ef17090ef7122e131887 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Mon, 29 Dec 2014 15:49:27 +0100 Subject: [PATCH 26/34] add more keys --- filmroller.py | 68 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/filmroller.py b/filmroller.py index c772373..d3040c2 100755 --- a/filmroller.py +++ b/filmroller.py @@ -51,6 +51,9 @@ def __init__(self): self.root.bind('', self.do_stop_video) self.root.bind('', self.do_single_shot) self.root.bind('', self.do_single_shot) + self.root.bind('', self.do_change_rotation_clockwise) + self.root.bind('', self.do_change_rotation_counterclockwise) + self.root.bind('', self.do_change_rotation_reset) self.root.bind('q', self.do_quit) self.root.bind('i', self.do_toggle_invert) self.root.bind('a', self.do_toggle_auto) @@ -95,25 +98,25 @@ def __init__(self): # Frame.__init__(self, self.root) self.pack() - self.canvas = Canvas(self, width=640, height=640, ) - self.canvas.pack(side='top') - self.canvas.bind('', self.do_change_rotation) - self.xt = Checkbutton(self, text='Invert', variable=self.invert) - self.xt.pack(side='left') - self.xb = Checkbutton(self, text='B/W', variable=self.bw) - self.xb.pack(side='left') - self.xa = Checkbutton(self, text='Auto', variable=self.auto) - self.xa.pack(side='left') - self.xv = OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video) - self.xv.pack(side='left') - self.resetrole = Button(self, text='First role', command=self.do_first_role) - self.resetrole.pack(side='left') - self.fnl = Label(self) - self.fnl.pack(side='left') - self.nextrole = Button(self, text='Next role', command=self.do_inc_role) - self.nextrole.pack(side='left') - self.take = Button(self, text='Take!', command=self.do_single_shot) - self.take.pack(side='right') + self.x_canvas = Canvas(self, width=640, height=640, ) + self.x_canvas.pack(side='top') + self.x_canvas.bind('', self.do_change_rotation) + self.x_invert = Checkbutton(self, text='Invert', variable=self.invert) + self.x_invert.pack(side='left') + self.x_bw = Checkbutton(self, text='B/W', variable=self.bw) + self.x_bw.pack(side='left') + self.x_auto = Checkbutton(self, text='Auto', variable=self.auto) + self.x_auto.pack(side='left') + self.x_restart_video = OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video) + self.x_restart_video.pack(side='left') + self.x_first_role = Button(self, text='First role', command=self.do_first_role) + self.x_first_role.pack(side='left') + self.x_filename = Label(self) + self.x_filename.pack(side='left') + self.x_inc_role = Button(self, text='Next role', command=self.do_inc_role) + self.x_inc_role.pack(side='left') + self.x_single_shot = Button(self, text='Take!', command=self.do_single_shot) + self.x_single_shot.pack(side='right') self.do_first_role() self.do_start_video() @@ -126,14 +129,23 @@ def do_toggle_auto(self, *args): def do_toggle_bw(self, *args): self.bw.set(not self.bw.get()) + def do_change_rotation_clockwise(self, *args): + self.degree = -90 + + def do_change_rotation_counterclockwise(self, *args): + self.degree = 90 + + def do_change_rotation_reset(self, *args): + self.degree = 0 + def do_change_rotation(self, event): ' determine where the image was clicked and turn that to the top ' if event.x < 200: - self.degree = -90 + self.do_change_rotation_clockwise() elif event.x > 640 - 200: - self.degree = 90 + self.do_change_rotation_counterclockwise() else: - self.degree = 0 + self.do_change_rotation_reset() def config_get(self, name, default): ' read a configuration entry, fallback to default if not already stored ' @@ -164,7 +176,7 @@ def inc_picture(self): self.serial += 1 self.filename = '{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, ) self.root.title('filmroller - ' + self.filename) - self.fnl['text'] = self.filename + self.x_filename['text'] = self.filename self.root.title('filmroller - ' + self.filename) def do_inc_role(self): @@ -178,7 +190,7 @@ def set_pauseimage(self): self.image = fromfile('filmroller.pause.png') self.image.thumbnail((self.previewsize['size_x'], self.previewsize['size_y'], ), ) self.photo = PhotoImage(self.image) - self.canvas.create_image(640/2, 640/2, image=self.photo) + self.x_canvas.create_image(640/2, 640/2, image=self.photo) def do_quit(self, event): ' exit program ' @@ -215,9 +227,9 @@ def do_start_video(self, *args): self.video.queue_all_buffers() self.video.start() self.root.after(1, self.do_live_view) - #self.canvas.width=640 - #self.canvas.height=480 - #self.canvas.pack(side='top') + #self.x_canvas.width=640 + #self.x_canvas.height=480 + #self.x_canvas.pack(side='top') self.degree = 0 def do_live_view(self, delta=3.0): @@ -235,7 +247,7 @@ def do_live_view(self, delta=3.0): if self.degree: self.image = self.image.rotate(self.degree) self.photo = PhotoImage(self.image) - self.canvas.create_image(640/2, 640/2, image=self.photo) + self.x_canvas.create_image(640/2, 640/2, image=self.photo) self.root.after(3, self.do_live_view) def do_single_shot(self, *args): From a48037c7c6664bd1395ffd75efe502e4e5f233fd Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Tue, 30 Dec 2014 02:01:03 +0100 Subject: [PATCH 27/34] adjust comment --- filmroller.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/filmroller.py b/filmroller.py index d3040c2..1d6be9c 100755 --- a/filmroller.py +++ b/filmroller.py @@ -32,7 +32,6 @@ - get event from usb dev - reduce redundant code - different target dir -- support rotation (incl preview) - show countdown during take ''' @@ -97,7 +96,7 @@ def __init__(self): self.degree = 0 # Frame.__init__(self, self.root) - self.pack() + self.grid() self.x_canvas = Canvas(self, width=640, height=640, ) self.x_canvas.pack(side='top') self.x_canvas.bind('', self.do_change_rotation) @@ -163,7 +162,7 @@ def do_configure(self, name, mode, cbname): self.config.set('global', name, str(value)) self.config.write(open('filmroller.conf', 'w')) - def do_first_role(self): + def do_first_role(self, *args): ' jump back to first role ' self.role = 'aa' self.serial = 0 @@ -179,7 +178,7 @@ def inc_picture(self): self.x_filename['text'] = self.filename self.root.title('filmroller - ' + self.filename) - def do_inc_role(self): + def do_inc_role(self, *args): ' increment to next role ' self.serial = 0 self.role = ascii_increment(self.role) @@ -192,7 +191,7 @@ def set_pauseimage(self): self.photo = PhotoImage(self.image) self.x_canvas.create_image(640/2, 640/2, image=self.photo) - def do_quit(self, event): + def do_quit(self, *args): ' exit program ' self.root.destroy() @@ -232,7 +231,7 @@ def do_start_video(self, *args): #self.x_canvas.pack(side='top') self.degree = 0 - def do_live_view(self, delta=3.0): + def do_live_view(self, *args): ' show single pic live view and ask tk to call us again later ' if self.video is not None: select((self.video, ), (), ()) @@ -266,10 +265,12 @@ def _go(): self.video.create_buffers(7) self.video.queue_all_buffers() self.video.start() - for n in range(7): # wait for auto + stop_time = time() + 3.0 + # wait for auto + while stop_time >= time(): select((self.video, ), (), ()) - data = self.video.read_and_queue() self.update_idletasks() + data = self.video.read_and_queue() image = frombytes('RGB', (self.highressize['size_x'], self.highressize['size_y'], ), data) if self.invert.get(): image = invert(image) From 0e47e467fda4806f59eb11e51e9a038b08b7ed56 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Tue, 30 Dec 2014 11:30:02 +0100 Subject: [PATCH 28/34] remove unused files --- .bzrignore | 2 -- MANIFEST | 6 ------ 2 files changed, 8 deletions(-) delete mode 100644 .bzrignore delete mode 100644 MANIFEST diff --git a/.bzrignore b/.bzrignore deleted file mode 100644 index 6e8722f..0000000 --- a/.bzrignore +++ /dev/null @@ -1,2 +0,0 @@ -./build -./dist diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index 136c8d9..0000000 --- a/MANIFEST +++ /dev/null @@ -1,6 +0,0 @@ -README -capture_picture.py -capture_picture_delayed.py -list_devices.py -setup.py -v4l2capture.c From 6045ce6bfcd494188fd48bcb48eea3ce7f7501f4 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Wed, 31 Dec 2014 20:11:32 +0100 Subject: [PATCH 29/34] improve samples section --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a997076..59d7fae 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,20 @@ To build and install: ./setup.py install -Example -======= +Examples +======== -See capture_picture.py, capture_picture_delayed.py and list_devices.py. +The script list\_devices.py lists all video4linux devices and it's capabilities +(as seen by python-v4l2capture). + +The script capture\_picture.py shows a simple one-shot immediate capture of a +picture. The image is stored to a file "image.jpg". + +The script capture\_picture\_delayed.py wait some seconds to allow the +auto-exposure to take place. The image is stored to a file "image.jpg". + +The script capture\_video.py takes a video and stores it to a file +"video.mjpg". It stopps after 10 sec of recording automatically. The program filmroller.py provided a gui to take captures from a webcam. It switches resolution between lowest resolution (for a live view) and highest From dda72ed04b24b992a1dc00f9205616186f63ef39 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Sun, 4 Jan 2015 11:25:40 +0100 Subject: [PATCH 30/34] simplify filmroller --- filmroller.py | 109 ++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/filmroller.py b/filmroller.py index 1d6be9c..f07b678 100755 --- a/filmroller.py +++ b/filmroller.py @@ -6,8 +6,8 @@ from ConfigParser import RawConfigParser from Image import frombytes, open as fromfile from ImageTk import PhotoImage -from ImageOps import invert, autocontrast, grayscale -from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP, Checkbutton, OptionMenu, StringVar, BooleanVar, Menu +from ImageOps import invert, autocontrast, grayscale, equalize, solarize +from Tkinter import Frame, Button, Tk, Label, Canvas, BOTH, TOP, Checkbutton, OptionMenu, StringVar, BooleanVar, Menu, IntVar from v4l2capture import Video_device ''' @@ -47,17 +47,19 @@ def __init__(self): ' set defaults, create widgets, bind callbacks, start live view ' # go! self.root = Tk() + self.root.bind('q', lambda e: self.root.quit()) + self.root.bind('x', lambda e: self.root.quit()) self.root.bind('', self.do_stop_video) self.root.bind('', self.do_single_shot) self.root.bind('', self.do_single_shot) - self.root.bind('', self.do_change_rotation_clockwise) - self.root.bind('', self.do_change_rotation_counterclockwise) - self.root.bind('', self.do_change_rotation_reset) - self.root.bind('q', self.do_quit) - self.root.bind('i', self.do_toggle_invert) - self.root.bind('a', self.do_toggle_auto) - self.root.bind('b', self.do_toggle_bw) - self.root.bind('x', self.do_quit) + self.root.bind('', lambda e: self.degree.set(-90)) + self.root.bind('', lambda e: self.degree.set(90)) + self.root.bind('', lambda e: self.degree.set(0)) + self.root.bind('i', lambda e: self.invert.set(not self.invert.get())) + self.root.bind('a', lambda e: self.autocontrast.set(not self.autocontrast.get())) + self.root.bind('b', lambda e: self.grayscale.set(not self.grayscale.get())) + self.root.bind('e', lambda e: self.equalize.set(not self.equalize.get())) + self.root.bind('s', lambda e: self.solarize.set(not self.solarize.get())) self.root.bind('', self.do_single_shot) # config: self.config = RawConfigParser() @@ -68,13 +70,21 @@ def __init__(self): self.invert = BooleanVar(name='invert') self.invert.set(self.config_get('invert', True)) self.invert.trace('w', self.do_configure) - self.bw = BooleanVar(name='bw') - self.bw.set(self.config_get('bw', False)) - self.bw.trace('w', self.do_configure) - self.auto = BooleanVar(name='auto') - self.auto.set(self.config_get('auto', True)) - self.auto.trace('w', self.do_configure) + self.grayscale = BooleanVar(name='grayscale') + self.grayscale.set(self.config_get('grayscale', False)) + self.grayscale.trace('w', self.do_configure) + self.autocontrast = BooleanVar(name='autocontrast') + self.autocontrast.set(self.config_get('autocontrast', True)) + self.autocontrast.trace('w', self.do_configure) + self.equalize = BooleanVar(name='equalize') + self.equalize.set(self.config_get('equalize', False)) + self.equalize.trace('w', self.do_configure) + self.solarize = BooleanVar(name='solarize') + self.solarize.set(self.config_get('solarize', False)) + self.solarize.trace('w', self.do_configure) self.videodevice = StringVar(name='videodevice') + self.degree = IntVar(name='degree') + self.degree.set(0) dev_names = sorted(['/dev/{}'.format(x) for x in listdir('/dev') if x.startswith('video')]) d = self.config_get('videodevice', dev_names[-1]) if not d in dev_names: @@ -93,7 +103,6 @@ def __init__(self): self.path = 'filmroller' if not exists(self.path): makedirs(self.path) - self.degree = 0 # Frame.__init__(self, self.root) self.grid() @@ -102,10 +111,10 @@ def __init__(self): self.x_canvas.bind('', self.do_change_rotation) self.x_invert = Checkbutton(self, text='Invert', variable=self.invert) self.x_invert.pack(side='left') - self.x_bw = Checkbutton(self, text='B/W', variable=self.bw) - self.x_bw.pack(side='left') - self.x_auto = Checkbutton(self, text='Auto', variable=self.auto) - self.x_auto.pack(side='left') + self.x_grayscale = Checkbutton(self, text='B/W', variable=self.grayscale) + self.x_grayscale.pack(side='left') + self.x_autocontrast = Checkbutton(self, text='Auto', variable=self.autocontrast) + self.x_autocontrast.pack(side='left') self.x_restart_video = OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video) self.x_restart_video.pack(side='left') self.x_first_role = Button(self, text='First role', command=self.do_first_role) @@ -119,32 +128,14 @@ def __init__(self): self.do_first_role() self.do_start_video() - def do_toggle_invert(self, *args): - self.invert.set(not self.invert.get()) - - def do_toggle_auto(self, *args): - self.auto.set(not self.auto.get()) - - def do_toggle_bw(self, *args): - self.bw.set(not self.bw.get()) - - def do_change_rotation_clockwise(self, *args): - self.degree = -90 - - def do_change_rotation_counterclockwise(self, *args): - self.degree = 90 - - def do_change_rotation_reset(self, *args): - self.degree = 0 - def do_change_rotation(self, event): ' determine where the image was clicked and turn that to the top ' if event.x < 200: - self.do_change_rotation_clockwise() + self.degree.set(-90) elif event.x > 640 - 200: - self.do_change_rotation_counterclockwise() + self.degree.set(90) else: - self.do_change_rotation_reset() + self.degree.set(0) def config_get(self, name, default): ' read a configuration entry, fallback to default if not already stored ' @@ -191,10 +182,6 @@ def set_pauseimage(self): self.photo = PhotoImage(self.image) self.x_canvas.create_image(640/2, 640/2, image=self.photo) - def do_quit(self, *args): - ' exit program ' - self.root.destroy() - def do_stop_video(self, *args): ' stop video and release device ' if self.video is not None: @@ -226,10 +213,9 @@ def do_start_video(self, *args): self.video.queue_all_buffers() self.video.start() self.root.after(1, self.do_live_view) - #self.x_canvas.width=640 - #self.x_canvas.height=480 + #self.x_canvas.config(width=640, height=480) #self.x_canvas.pack(side='top') - self.degree = 0 + self.degree.set(0) def do_live_view(self, *args): ' show single pic live view and ask tk to call us again later ' @@ -239,12 +225,16 @@ def do_live_view(self, *args): self.image = frombytes('RGB', (self.previewsize['size_x'], self.previewsize['size_y']), data) if self.invert.get(): self.image = invert(self.image) - if self.bw.get(): + if self.grayscale.get(): self.image = grayscale(self.image) - if self.auto.get(): + if self.autocontrast.get(): self.image = autocontrast(self.image) - if self.degree: - self.image = self.image.rotate(self.degree) + if self.equalize.get(): + self.image = equalize(self.image) + if self.solarize.get(): + self.image = solarize(self.image) + if self.degree.get(): + self.image = self.image.rotate(self.degree.get()) self.photo = PhotoImage(self.image) self.x_canvas.create_image(640/2, 640/2, image=self.photo) self.root.after(3, self.do_live_view) @@ -274,12 +264,16 @@ def _go(): image = frombytes('RGB', (self.highressize['size_x'], self.highressize['size_y'], ), data) if self.invert.get(): image = invert(image) - if self.bw.get(): + if self.grayscale.get(): image = grayscale(image) - if self.auto.get(): + if self.autocontrast.get(): image = autocontrast(image) - if self.degree: - image = image.rotate(self.degree) + if self.equalize.get(): + self.image = equalize(self.image) + if self.solarize.get(): + self.image = solarize(self.image) + if self.degree.get(): + image = image.rotate(self.degree.get()) image.save(self.filename) self.inc_picture() self.root.bell() @@ -298,6 +292,7 @@ def main(): ' main start point of the program ' app = Cap() app.mainloop() + app.root.destroy() if __name__ == '__main__': from sys import argv From e562f7fabbf07ddd970617fc81ae6de1c0cf6bb1 Mon Sep 17 00:00:00 2001 From: "M. Dietrich" Date: Sun, 4 Jan 2015 12:05:47 +0100 Subject: [PATCH 31/34] add more filters and reduce code --- filmroller.py | 66 ++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/filmroller.py b/filmroller.py index f07b678..b398e17 100755 --- a/filmroller.py +++ b/filmroller.py @@ -45,22 +45,25 @@ def ascii_increment(val): class Cap(Frame): def __init__(self): ' set defaults, create widgets, bind callbacks, start live view ' - # go! self.root = Tk() + # menu: + self.menu = Menu(self.root) + self.root.config(menu=self.menu) + # bind global keypresses: self.root.bind('q', lambda e: self.root.quit()) self.root.bind('x', lambda e: self.root.quit()) self.root.bind('', self.do_stop_video) self.root.bind('', self.do_single_shot) self.root.bind('', self.do_single_shot) + self.root.bind('', self.do_single_shot) self.root.bind('', lambda e: self.degree.set(-90)) self.root.bind('', lambda e: self.degree.set(90)) self.root.bind('', lambda e: self.degree.set(0)) - self.root.bind('i', lambda e: self.invert.set(not self.invert.get())) self.root.bind('a', lambda e: self.autocontrast.set(not self.autocontrast.get())) - self.root.bind('b', lambda e: self.grayscale.set(not self.grayscale.get())) self.root.bind('e', lambda e: self.equalize.set(not self.equalize.get())) + self.root.bind('g', lambda e: self.grayscale.set(not self.grayscale.get())) + self.root.bind('i', lambda e: self.invert.set(not self.invert.get())) self.root.bind('s', lambda e: self.solarize.set(not self.solarize.get())) - self.root.bind('', self.do_single_shot) # config: self.config = RawConfigParser() self.config.read('filmroller.conf') @@ -82,9 +85,10 @@ def __init__(self): self.solarize = BooleanVar(name='solarize') self.solarize.set(self.config_get('solarize', False)) self.solarize.trace('w', self.do_configure) - self.videodevice = StringVar(name='videodevice') self.degree = IntVar(name='degree') self.degree.set(0) + self.filename = StringVar(name='filename') + self.videodevice = StringVar(name='videodevice') dev_names = sorted(['/dev/{}'.format(x) for x in listdir('/dev') if x.startswith('video')]) d = self.config_get('videodevice', dev_names[-1]) if not d in dev_names: @@ -92,39 +96,29 @@ def __init__(self): self.videodevice.set(d) self.videodevice.trace('w', self.do_configure) # - self.menu = Menu(self.root) - self.root.config(menu=self.menu) - filemenu = Menu(self.menu) - self.menu.add_cascade(label=self.videodevice.get(), menu=filemenu, ) - for n in dev_names: - filemenu.add_command(label=n, ) - #filemenu.add_separator() - # self.path = 'filmroller' if not exists(self.path): makedirs(self.path) - # + # create gui: Frame.__init__(self, self.root) self.grid() self.x_canvas = Canvas(self, width=640, height=640, ) self.x_canvas.pack(side='top') self.x_canvas.bind('', self.do_change_rotation) - self.x_invert = Checkbutton(self, text='Invert', variable=self.invert) - self.x_invert.pack(side='left') - self.x_grayscale = Checkbutton(self, text='B/W', variable=self.grayscale) - self.x_grayscale.pack(side='left') - self.x_autocontrast = Checkbutton(self, text='Auto', variable=self.autocontrast) - self.x_autocontrast.pack(side='left') - self.x_restart_video = OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video) - self.x_restart_video.pack(side='left') - self.x_first_role = Button(self, text='First role', command=self.do_first_role) - self.x_first_role.pack(side='left') - self.x_filename = Label(self) - self.x_filename.pack(side='left') - self.x_inc_role = Button(self, text='Next role', command=self.do_inc_role) - self.x_inc_role.pack(side='left') - self.x_single_shot = Button(self, text='Take!', command=self.do_single_shot) - self.x_single_shot.pack(side='right') + Checkbutton(self, text='Invert', variable=self.invert).pack(side='left') + Checkbutton(self, text='Gray', variable=self.grayscale).pack(side='left') + Checkbutton(self, text='Auto', variable=self.autocontrast).pack(side='left') + OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video).pack(side='left') + Button(self, text='First role', command=self.do_first_role).pack(side='left') + Label(self, textvariable=self.filename).pack(side='left') + Button(self, text='Next role', command=self.do_inc_role).pack(side='left') + Button(self, text='Take!', command=self.do_single_shot).pack(side='right') + #filemenu = Menu(self.menu) + #self.menu.add_cascade(label=self.videodevice.get(), menu=filemenu, ) + #for n in dev_names: + # filemenu.add_command(label=n, ) + #filemenu.add_separator() + # start operation: self.do_first_role() self.do_start_video() @@ -161,13 +155,11 @@ def do_first_role(self, *args): def inc_picture(self): ' increment the picture number, jump over existing files ' - self.filename = '{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, ) - while exists(self.filename): + self.filename.set('{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, )) + while exists(self.filename.get()): self.serial += 1 - self.filename = '{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, ) - self.root.title('filmroller - ' + self.filename) - self.x_filename['text'] = self.filename - self.root.title('filmroller - ' + self.filename) + self.filename.set('{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, )) + self.root.title('filmroller - ' + self.filename.get()) def do_inc_role(self, *args): ' increment to next role ' @@ -274,7 +266,7 @@ def _go(): self.image = solarize(self.image) if self.degree.get(): image = image.rotate(self.degree.get()) - image.save(self.filename) + image.save(self.filename.get()) self.inc_picture() self.root.bell() self.video.stop() From 5dacdbe1548130709294d3f8464957538acacc25 Mon Sep 17 00:00:00 2001 From: Alexander Pitzer Date: Fri, 2 Jun 2017 13:31:45 +0200 Subject: [PATCH 32/34] add focues_absolute getter/ setter --- setup.py | 2 +- v4l2capture.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7c3bd16..ad1be99 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ from distutils.core import Extension, setup setup( name = "v4l2capture", - version = "1.5", + version = "1.6", author = "Fredrik Portstrom", author_email = "fredrik@jemla.se", url = "http://fredrik.jemla.eu/v4l2capture", diff --git a/v4l2capture.c b/v4l2capture.c index f3abe71..620ac85 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -645,6 +645,43 @@ static PyObject *Video_device_get_focus_auto( #endif } +static PyObject *Video_device_set_focus_absolute( + Video_device * self, + PyObject * args) { +#ifdef V4L2_CID_FOCUS_ABSOLUTE + int focus_absolute; + if (!PyArg_ParseTuple(args, "i", &focus_absolute)) { + return NULL; + } + + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_FOCUS_ABSOLUTE; + ctrl.value = focus_absolute; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("i", ctrl.value); +#else + return NULL; +#endif +} + +static PyObject *Video_device_get_focus_absolute( + Video_device * self) { +#ifdef V4L2_CID_FOCUS_ABSOLUTE + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_FOCUS_ABSOLUTE; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("i", ctrl.value); +#else + return NULL; +#endif +} + static PyObject *Video_device_start( Video_device * self) { ASSERT_OPEN; @@ -922,6 +959,13 @@ static PyMethodDef Video_device_methods[] = { {"get_focus_auto", (PyCFunction) Video_device_get_focus_auto, METH_NOARGS, "get_focus_auto() -> autofocus \n\n" "Request the video device to get auto focus value. "}, + {"set_focus_absolute", (PyCFunction) Video_device_set_focus_absolute, METH_VARARGS, + "set_focus_absolute(focus_absolute) -> focus_absolute \n\n" + "Request the video device to set the focus to the given value. The device may " + "choose another value than requested and will return its choice. "}, + {"get_focus_absolute", (PyCFunction) Video_device_get_focus_absolute, METH_NOARGS, + "get_focus_absolute() -> focus_absolute \n\n" + "Request the video device to get the focus value. "}, {"get_framesizes", (PyCFunction) Video_device_get_framesizes, METH_VARARGS, "get_framesizes() -> framesizes \n\n" "Request the framesizes suported by the device. "}, From ea3980a937da56eccf771e4e6e17a64733590ca4 Mon Sep 17 00:00:00 2001 From: Alexander Pitzer Date: Tue, 4 Jul 2017 11:21:13 +0200 Subject: [PATCH 33/34] add sharpness setter and getter --- v4l2capture.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/v4l2capture.c b/v4l2capture.c index 620ac85..2d92046 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -682,6 +682,43 @@ static PyObject *Video_device_get_focus_absolute( #endif } +static PyObject *Video_device_set_sharpness( + Video_device * self, + PyObject * args) { +#ifdef V4L2_CID_SHARPNESS + int sharpness; + if (!PyArg_ParseTuple(args, "i", &sharpness)) { + return NULL; + } + + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_SHARPNESS; + ctrl.value = sharpness; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("i", ctrl.value); +#else + return NULL; +#endif +} + +static PyObject *Video_device_get_sharpness( + Video_device * self) { +#ifdef V4L2_CID_SHARPNESS + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_SHARPNESS; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("i", ctrl.value); +#else + return NULL; +#endif +} + static PyObject *Video_device_start( Video_device * self) { ASSERT_OPEN; @@ -966,6 +1003,13 @@ static PyMethodDef Video_device_methods[] = { {"get_focus_absolute", (PyCFunction) Video_device_get_focus_absolute, METH_NOARGS, "get_focus_absolute() -> focus_absolute \n\n" "Request the video device to get the focus value. "}, + {"set_sharpness", (PyCFunction) Video_device_set_focus_absolute, METH_VARARGS, + "set_sharpness(sharpness) -> sharpness \n\n" + "Request the video device to set the sharpness to the given value. The device may " + "choose another value than requested and will return its choice. "}, + {"get_sharpness", (PyCFunction) Video_device_get_sharpness, METH_NOARGS, + "get_sharpness() -> sharpness \n\n" + "Request the video device to get the sharpness value."}, {"get_framesizes", (PyCFunction) Video_device_get_framesizes, METH_VARARGS, "get_framesizes() -> framesizes \n\n" "Request the framesizes suported by the device. "}, From 86573a661a84c856235655e49115d5b81f0c388a Mon Sep 17 00:00:00 2001 From: Alexander Pitzer Date: Wed, 5 Jul 2017 15:20:16 +0200 Subject: [PATCH 34/34] add gain setter getter --- setup.py | 2 +- v4l2capture.c | 118 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index ad1be99..5dba60d 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ from distutils.core import Extension, setup setup( name = "v4l2capture", - version = "1.6", + version = "1.7", author = "Fredrik Portstrom", author_email = "fredrik@jemla.se", url = "http://fredrik.jemla.eu/v4l2capture", diff --git a/v4l2capture.c b/v4l2capture.c index 2d92046..1f31ba3 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -14,16 +14,16 @@ #include #include -#ifdef USE_LIBV4L -# include -#else -# include -# define v4l2_close close -# define v4l2_ioctl ioctl -# define v4l2_mmap mmap -# define v4l2_munmap munmap -# define v4l2_open open -#endif +//#ifdef USE_LIBV4L +#include +//#else +//# include +//# define v4l2_close close +//# define v4l2_ioctl ioctl +//# define v4l2_mmap mmap +//# define v4l2_munmap munmap +//# define v4l2_open open +//#endif #ifndef Py_TYPE # define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) @@ -398,6 +398,80 @@ static PyObject *Video_device_get_white_balance_temperature( #endif } +static PyObject *Video_device_set_gain( + Video_device * self, + PyObject * args) { +#ifdef V4L2_CID_GAIN + int exposure; + if (!PyArg_ParseTuple(args, "i", &exposure)) { + return NULL; + } + + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_GAIN; + ctrl.value = exposure; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("i", ctrl.value); +#else + return NULL; +#endif +} + +static PyObject *Video_device_get_gain( + Video_device * self) { +#ifdef V4L2_CID_GAIN + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_GAIN; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("i", ctrl.value); +#else + return NULL; +#endif +} + +static PyObject *Video_device_set_autogain( + Video_device * self, + PyObject * args) { +#ifdef V4L2_CID_AUTOGAIN + int exposure; + if (!PyArg_ParseTuple(args, "b", &exposure)) { + return NULL; + } + + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_AUTOGAIN; + ctrl.value = exposure; + if (my_ioctl(self->fd, VIDIOC_S_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("b", ctrl.value); +#else + return NULL; +#endif +} + +static PyObject *Video_device_get_autogain( + Video_device * self) { +#ifdef V4L2_CID_AUTOGAIN + struct v4l2_control ctrl; + CLEAR(ctrl); + ctrl.id = V4L2_CID_AUTOGAIN; + if (my_ioctl(self->fd, VIDIOC_G_CTRL, &ctrl)) { + return NULL; + } + return Py_BuildValue("b", ctrl.value); +#else + return NULL; +#endif +} + static PyObject *Video_device_set_exposure_absolute( Video_device * self, PyObject * args) { @@ -971,6 +1045,28 @@ static PyMethodDef Video_device_methods[] = { (PyCFunction) Video_device_get_white_balance_temperature, METH_NOARGS, "get_white_balance_temperature() -> temp \n\n" "Request the video device to get white balance temperature value. "}, + + {"set_gain", (PyCFunction) Video_device_set_gain, + METH_VARARGS, + "set_gain(gain) -> gain \n\n" + "Request the video device to set gain to value. The device may " + "choose another value than requested and will return its choice. "}, + {"get_gain", (PyCFunction) Video_device_get_gain, + METH_NOARGS, + "get_gain() -> gain \n\n" + "Request the video device to get gain value. "}, + + {"set_autogain", (PyCFunction) Video_device_set_autogain, + METH_VARARGS, + "set_autogain(autogen) -> autogen \n\n" + "Request the video device to set autogain to bool. The device may " + "choose another value than requested and will return its choice. "}, + {"get_autogain", (PyCFunction) Video_device_get_autogain, + METH_NOARGS, + "get_autogain() -> gain \n\n" + "Request the video device to get autogain bool. "}, + + {"set_exposure_auto", (PyCFunction) Video_device_set_exposure_auto, METH_VARARGS, "set_exposure_auto(autoexp) -> autoexp \n\n" @@ -1003,7 +1099,7 @@ static PyMethodDef Video_device_methods[] = { {"get_focus_absolute", (PyCFunction) Video_device_get_focus_absolute, METH_NOARGS, "get_focus_absolute() -> focus_absolute \n\n" "Request the video device to get the focus value. "}, - {"set_sharpness", (PyCFunction) Video_device_set_focus_absolute, METH_VARARGS, + {"set_sharpness", (PyCFunction) Video_device_set_sharpness, METH_VARARGS, "set_sharpness(sharpness) -> sharpness \n\n" "Request the video device to set the sharpness to the given value. The device may " "choose another value than requested and will return its choice. "},