Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python2
- # -*- coding: utf-8 -*-
- import pygame
- import math
- import sys
- import subprocess as sp
- import threading
- import time
- import os
- import re
- import tempfile
- import shutil
- import types
- import ctypes
- from ctypes.util import find_library
- pygame.display.init()
- pygame.font.init()
- CAPTION_H = 25
- STATUS_H = 25
- DIST = 10
- TEXT_PADDING = 30
- SIGINT = 2
- SCREEN_WIDTH = 1200
- SCREEN_HEIGHT = 690
- SAVE_DIRECTORY = "/home/jan/Photos/Camera"
- #SAVE_DIRECTORY = "/Users/anny/Pictures/annYmage"
- class ImageProviderDirectory(threading.Thread):
- def __init__(self, directory):
- threading.Thread.__init__(self)
- self.directory = directory
- def terminate(self):
- self.stop = True
- def shoot(self):
- pass
- def run(self):
- self.list = []
- self.stop = False
- while not self.stop:
- time.sleep(1)
- new = filter(lambda x: x not in self.list,
- os.listdir(self.directory))
- App.getInstance().dispatch('load',
- map(lambda x: os.path.join(self.directory, x), new))
- self.list += new
- class ImageProviderExternal(threading.Thread):
- def __init__(self):
- threading.Thread.__init__(self)
- def terminate(self):
- self.send('quit', True)
- # We could alternatively use a combination of poll and sleep to avoid a deadlock
- self.prog.wait()
- def shoot(self):
- self.send('shoot', True)
- def mode(self):
- self.send('mode', True)
- def send(self, msg, immediate=False):
- self.prog.stdin.write(msg + "\n")
- if immediate: self.prog.send_signal(SIGINT)
- def run(self):
- # start external program
- self.prog = sp.Popen(['./cameraprovider.py'], stdin=sp.PIPE, stdout=sp.PIPE)
- for line in self.prog.stdin:
- (cmd, args) = line.rstrip('\r\n').split(' ', 1)
- print "Firing: %s(%s)" % (cmd, args)
- App.getInstance().dispatch(cmd, args)
- #############
- #
- # ImageProviderCamera
- # - runs in a separate thread
- # - communicates with the camera via libgphoto
- # - filenames of new photos are send to ImageViewer via userevent
- #
- #############
- # gphoto structures
- class CameraFilePath(ctypes.Structure):
- _fields_ = [('name', (ctypes.c_char * 128)),
- ('folder', (ctypes.c_char * 1024))]
- (GP_EVENT_UNKNOWN,
- GP_EVENT_TIMEOUT,
- GP_EVENT_FILE_ADDED,
- GP_EVENT_FOLDER_ADDED,
- GP_EVENT_CAPTURE_COMPLETE) = range(5)
- class CameraEventType(ctypes.c_int): pass
- GP_OK = 0
- GP_CAPTURE_IMAGE = 0
- GP_FILE_TYPE_NORMAL = 1
- class ImageProviderCamera(threading.Thread):
- def __init__(self, manual=False):
- threading.Thread.__init__(self)
- self.todo = threading.Condition()
- self.queued_shots = 0
- self.running = True
- self.manual = manual
- self.fn_no = 0
- #####
- # External signals
- #####
- def terminate(self):
- with self.todo:
- self.running = False
- self.todo.notify()
- def shoot(self):
- with self.todo:
- self.queued_shots += 1
- self.todo.notify()
- def mode(self):
- with self.todo:
- self.manual = not self.manual
- App.getInstance().dispatch("mode", self.manual and "manual" or "automatic")
- self.todo.notify()
- #####
- # Aux
- #####
- def next_fn(self, cam_file):
- self.fn_no += 1
- suffix = cam_file.rsplit('.',1)[-1]
- return os.path.join(self.directory, "img-%s-%d.%s" % (time.strftime("%H%M%S"), self.fn_no, suffix))
- def status(self, s):
- print "status: %s" % s
- App.getInstance().dispatch('status', s)
- #####
- # Camera Controlling
- #####
- def kill_others(self):
- for i in map(lambda x: x.strip().split(' ',1), sp.Popen(['/bin/ps', '-eo', 'pid,command'], stdout=sp.PIPE).communicate()[0].splitlines()):
- if('PTPCamera.app' in i[1]):
- os.kill(int(i[0]), 2)
- time.sleep(1)
- def init_camera(self):
- self.kill_others()
- # Init camera
- self.status('Initialising camera ...')
- self.context = self.gp.gp_context_new()
- self.camera = ctypes.c_void_p()
- self.gp.gp_camera_new(ctypes.byref(self.camera))
- self.gp.gp_camera_init(self.camera, self.context)
- event = CameraEventType()
- data_p = ctypes.c_void_p()
- self.working = (self.gp.gp_camera_wait_for_event
- (self.camera,
- 100,
- ctypes.byref(event),
- ctypes.byref(data_p),
- self.context) == GP_OK)
- print "returned event %d" % event.value
- def release_camera(self):
- # Release the camera
- self.status('Releasing the camera')
- self.gp.gp_camera_exit(self.camera, self.context)
- self.gp.gp_camera_unref(self.camera)
- def download(self, cam_path):
- # download
- self.status('downloading ...')
- fn = self.next_fn(cam_path.name)
- cam_file = ctypes.c_void_p()
- fd = os.open(fn, os.O_CREAT | os.O_WRONLY)
- self.gp.gp_file_new_from_fd(ctypes.pointer(cam_file), fd)
- if self.gp.gp_camera_file_get(self.camera,
- cam_path.folder,
- cam_path.name,
- GP_FILE_TYPE_NORMAL,
- cam_file,
- self.context) != GP_OK:
- print "Download failed"
- self.working = False
- else:
- print "Downloaded %s in %s" % (cam_path.name, fn)
- App.getInstance().dispatch('load', [fn])
- # delete
- self.status('deleting ...')
- self.gp.gp_camera_file_delete(self.camera,
- cam_path.folder,
- cam_path.name,
- self.context)
- print "Deleted %s from camera" % cam_path.name
- self.gp.gp_file_unref(cam_file)
- def capture(self):
- self.status('shooting ...')
- # Capture image
- cam_path = CameraFilePath()
- if self.gp.gp_camera_capture(self.camera,
- GP_CAPTURE_IMAGE,
- ctypes.byref(cam_path),
- self.context) != GP_OK:
- print "Capture failed"
- self.working = False
- else:
- print "Captured %s in %s" % (cam_path.name, cam_path.folder)
- self.download(cam_path)
- def check_for_event(self):
- event = CameraEventType()
- data_ptr = ctypes.c_void_p()
- ret = self.gp.gp_camera_wait_for_event(self.camera,
- 1000, # wait in ms
- ctypes.byref(event),
- ctypes.byref(data_ptr),
- self.context)
- if ret != GP_OK:
- print "wait for event failed: %d", ret
- self.working = False
- else:
- if event.value == GP_EVENT_FILE_ADDED:
- cam_path = ctypes.cast(data_ptr, ctypes.POINTER(CameraFilePath)).contents
- self.download(cam_path)
- def run(self):
- # Make temporary directory
- self.directory = tempfile.mkdtemp()
- #clock = pygame.time.Clock()
- # Load library
- self.gp = ctypes.CDLL(find_library('gphoto2'))
- self.init_camera()
- self.todo.acquire()
- while self.running:
- while not self.working and self.running:
- self.todo.release()
- self.status('*** Camera Error ***')
- time.sleep(1)
- self.release_camera()
- self.init_camera()
- self.todo.acquire()
- self.status('ready.')
- if not self.queued_shots and self.manual:
- self.todo.wait()
- while self.queued_shots and self.running:
- self.todo.release()
- self.capture()
- self.todo.acquire()
- self.queued_shots -= 1
- if not self.manual and self.running:
- self.todo.release()
- self.check_for_event()
- self.todo.acquire()
- #if clock.tick_busy_loop() < 100:
- # print "Loop has run too fast, reinitializing camera"
- # self.working = False
- self.todo.release()
- self.release_camera()
- os.rmdir(self.directory)
- ################
- #
- # Non camera relevant interface
- #
- ################
- class Actor(object):
- def on_terminate(self):
- pass
- def on_draw(self):
- pass
- def on_event(self, event):
- if event.type == pygame.KEYDOWN:
- return self.on_keydown(event)
- elif event.type == pygame.USEREVENT:
- return self.on_userevent(event)
- def on_keydown(self, event):
- if self.keymap is not None and self.keymap.has_key(event.key):
- self.keymap[event.key](self)
- return True
- return False
- def on_userevent(self, event):
- pass
- #######
- #
- # Abstract Box, and specialized Textbox, FilenameBox and HelpBox
- #
- #######
- class Box(Actor):
- BorderColor = pygame.Color('white')
- FillColor = pygame.Color('lightgreen')
- FontColor = pygame.Color('black')
- def __init__ (self, textlines, size=None, center=False, fontsize=18):
- self.textlines = textlines
- self.Font = pygame.font.SysFont('Arial,Sans', fontsize)
- if size is None:
- size = (App.getInstance().screen.get_width()/2 + TEXT_PADDING,
- len(self.textlines) * self.Font.get_linesize() + TEXT_PADDING)
- self.size = size
- self.center = center
- App.getInstance().push(self)
- def on_draw (self):
- screen = App.getInstance().screen
- screen_rect = screen.get_rect()
- surface = pygame.Surface(self.size)
- rect = surface.get_rect()
- surface.fill(self.BorderColor)
- surface.fill(self.FillColor, rect.inflate((-4,-4)))
- surface.set_alpha(200)
- rect.center = screen_rect.center
- screen.blit(surface, rect)
- height = len(self.textlines) * self.Font.get_linesize()
- top = rect.y + (rect.h - height)/2
- left = rect.x + TEXT_PADDING/2
- for line in self.textlines:
- text = self.Font.render(line, True, self.FontColor)
- if self.center: left = rect.x + (rect.w - text.get_width())/2
- screen.blit(text, (left, top))
- top += self.Font.get_linesize()
- class TextBox(Box):
- def __init__(self, lines=[u''], index=None):
- if index is None: index=len(lines)-1
- self.index = index
- Box.__init__(self, lines, center=True)
- def on_keydown(self, event):
- if event.key == pygame.K_BACKSPACE:
- self.textlines[self.index] = self.textlines[self.index][:-1]
- elif event.key == pygame.K_RETURN:
- App.getInstance().pop()
- self.on_return(self.textlines[self.index], pygame.key.get_mods() & pygame.KMOD_CTRL)
- elif event.key == pygame.K_ESCAPE:
- App.getInstance().pop()
- elif event.key <= 127: self.textlines[self.index] += event.unicode
- else: return False
- return True
- def on_return(self, text):
- pass
- class FilenameBox(TextBox):
- def __init__(self, filename='', msg=None):
- TextBox.__init__(self, [u'Name? (Abbrechen mit ESCAPE, Bestรคtigen mit ENTER)',
- filename] + (msg and [msg] or []), 1)
- def on_return(self, text, overwrite=False):
- App.getInstance().dispatch('save', (text, overwrite))
- #######
- #
- # Help window
- #
- ######
- class HelpBox (Box):
- keymap = { pygame.K_h: lambda s: App.getInstance().pop() }
- def __init__ (self):
- Box.__init__ (self, [u'*** Hilfe ***',
- u'',
- u'Bilder auswรคhlen mit den Ziffern 1-9',
- u'Auswahl vergrรถรern mit ENTER',
- u'Alle Bilder anzeigen mit ESCAPE',
- u'Ausgewรคhlte Bilder lรถschen mit BACKSPACE (Rรผckstelltaste)',
- u'Angezeigte Bilder speichern mit S wie save (dann Namen eingeben und ENTER)',
- u'',
- u'Zwischen manuellem und automatischem Modus wechseln mit M',
- u'(Im manuellen Modus kann nicht an der Kamera ausgelรถst werden,',
- u' dafรผr funktioniert der Auslรถser mit LEERTASTE schneller)',
- u'',
- u'Hilfetext ein-/ausblenden mit H wie help',
- u'annYmage verlassen mit Q wie quit'], fontsize=16)
- class Dialog(Box):
- keymap = {pygame.K_ESCAPE: lambda s: App.getInstance().pop() }
- def __init__(self,str):
- Box.__init__(self, [str])
- #######
- #
- # A single Image
- #
- ######
- class Image:
- def __init__(self, fn):
- self.filename = fn
- im = pygame.image.load(fn)
- size = im.get_size()
- if size > (SCREEN_WIDTH, SCREEN_HEIGHT):
- w = SCREEN_HEIGHT * size[0]/size[1]
- if w > SCREEN_WIDTH: size = (w, SCREEN_HEIGHT)
- else: size = (SCREEN_WIDTH, SCREEN_WIDTH * size[1]/size[0])
- im = pygame.transform.smoothscale(im, size)
- self.surface = im.convert()
- self.shown = True
- self.selected = False
- def is_shown(self): return self.shown
- def is_selected(self): return self.selected
- def get_size(self): return self.surface.get_size()
- def show(self, to=None):
- if to is None: self.shown = not self.shown
- else: self.shown = to
- def select(self, to=None):
- if to is None: self.selected = not self.selected
- else: self.selected = to
- self.surface.set_alpha(self.selected and 100 or None)
- def draw(self, screen, dest_rect, no):
- if self.selected:
- screen.fill(Image.SelectionColor, dest_rect.inflate(6,6))
- screen.fill(0, dest_rect)
- screen.blit(pygame.transform.smoothscale(self.surface, dest_rect.size), dest_rect)
- text = Image.Font.render("%d" % no, True, Image.TextColor)
- text_rect = text.get_rect()
- text_rect.midtop = dest_rect.midbottom
- text_rect.move_ip(0,3)
- screen.blit(text, text_rect)
- Image.SelectionColor = pygame.Color('yellow')
- Image.TextColor = pygame.Color('white')
- Image.Font = pygame.font.SysFont('Arial,Sans', 18)
- class ImageViewer(Actor):
- def __init__ (self, directory, provider):
- self.directory = directory
- self.provider = provider
- self.images = []
- self.status = { 'provider': '', 'mode': 'mode: automatic', 'shown': '' }
- self.update_status()
- App.getInstance().push(self)
- keymap = {
- pygame.K_1: lambda s: s.select(0),
- pygame.K_2: lambda s: s.select(1),
- pygame.K_3: lambda s: s.select(2),
- pygame.K_4: lambda s: s.select(3),
- pygame.K_5: lambda s: s.select(4),
- pygame.K_6: lambda s: s.select(5),
- pygame.K_7: lambda s: s.select(6),
- pygame.K_8: lambda s: s.select(7),
- pygame.K_9: lambda s: s.select(8),
- pygame.K_0: lambda s: s.select(9),
- pygame.K_RETURN: lambda s: s.show_selected(),
- pygame.K_ESCAPE: lambda s: s.show_all(),
- pygame.K_BACKSPACE: lambda s: s.del_selected(),
- pygame.K_s: lambda s: s.choose_shown(),
- pygame.K_UP: lambda s: s.show_more(+1),
- pygame.K_DOWN: lambda s: s.show_more(-1),
- pygame.K_m: lambda s: s.switch_mode(),
- pygame.K_SPACE: lambda s: s.provider.shoot(),
- pygame.K_h: lambda s: s.show_help(),
- pygame.K_q: lambda s: App.getInstance().terminate()
- }
- ####
- # Keymap functions
- ####
- def select (self, no):
- shown = self.get_shown()
- if (no >= len(shown)):
- self.show_more(no-len(shown)+1)
- else:
- shown[no].select()
- def show_selected (self):
- for img in self.images:
- img.show(img.is_selected())
- img.select(False)
- self.update_status()
- def show_all (self):
- for img in self.images:
- img.show(True)
- self.update_status()
- def del_selected (self):
- images = []
- for img in self.images:
- if img.is_selected(): os.unlink(img.filename)
- else: images.append(img)
- self.images = images
- self.update_status()
- def choose_shown (self):
- '''Launches FilenameBox. The entered information returns by userevent 'save'.'''
- FilenameBox()
- def show_more (self, count):
- print "show_more(%d)" % count
- show = bool(count > 0)
- count = abs(count)
- for img in self.images:
- if not count > 0: break
- if img.is_shown() != show:
- img.show()
- count -= 1
- self.update_status()
- def switch_mode (self):
- self.status['mode'] = 'switching mode ...'
- self.provider.mode()
- def show_help (self):
- HelpBox()
- ####
- # Small helper functions
- ####
- def update_status (self):
- self.status['shown'] = ("%d / %d images are shown" % (len(self.get_shown()),len(self.images)))
- def load (self, filenames):
- for fn in filenames:
- self.images.append(Image(fn))
- self.update_status()
- def del_all (self):
- for img in self.images:
- try:
- os.unlink(img.filename)
- except OSError: pass
- self.images = []
- self.update_status()
- def get_shown(self):
- return filter(lambda x: x.is_shown(), self.images)
- ####
- # Event functions
- ####
- def on_userevent (self, event):
- if (event.name == 'save'):
- chosen = self.get_shown()
- selected = filter(lambda x: x.is_selected(), chosen)
- if selected: chosen = selected
- (filename, overwrite) = event.data
- index = 1
- for img in chosen:
- suffix = img.filename.rsplit('.',1)[1]
- fn = os.path.join(self.directory, "%s_%d.%s" % (filename, index, suffix))
- print "Moving %s to %s" % (img.filename, fn)
- if (not overwrite and os.path.exists(fn)):
- FilenameBox(filename, u'Datei mit diesem Namen existiert bereits. รberschreiben mit CTRL-ENTER.')
- return True
- shutil.move(img.filename, fn)
- self.images.remove(img)
- index += 1
- self.del_all()
- elif (event.name == 'load'):
- if not isinstance(event.data, (types.TupleType, types.ListType)):
- event.data = [event.data]
- self.load(event.data)
- elif (event.name == 'status'):
- self.status['provider'] = event.data
- elif (event.name == 'mode'):
- self.status['mode'] = "mode: %s" % event.data
- else:
- return False
- return True
- def on_terminate (self):
- self.del_all()
- def find_best_rect (self, count):
- screen_size = App.getInstance().screen.get_size()
- image_rect = pygame.Rect((0,0), self.images[0].get_size())
- best_rect = pygame.Rect(0,0,0,0)
- best_cols = 0
- for cols in range(1,count+1):
- rows = math.ceil(float(count) / cols)
- box_rect = pygame.Rect((0,0), (screen_size[0]/cols - DIST, (screen_size[1] - STATUS_H)/rows - CAPTION_H - DIST))
- pro_rect = image_rect.fit(box_rect)
- if pro_rect.size > best_rect.size:
- best_rect = pro_rect
- best_cols = cols
- return (best_cols, best_rect)
- def on_draw (self):
- screen = App.getInstance().screen
- # Drawing status
- tab = (SCREEN_WIDTH - DIST)/len(self.status)
- top = SCREEN_HEIGHT - (STATUS_H + ImageViewer.Font.get_linesize())/2
- left = DIST/2
- for v in self.status.itervalues():
- screen.blit(ImageViewer.Font.render(v, True, ImageViewer.Color), (left, top))
- left += tab
- if len(self.images) == 0: return
- shown = self.get_shown()
- (best_cols, best_rect) = self.find_best_rect(len(shown))
- no = 1
- rect = best_rect.move(DIST/2, DIST/2)
- for img in shown:
- img.draw(screen, rect, no)
- rect.move_ip((best_rect.w + DIST + 2*best_rect.x, 0))
- if (no % best_cols == 0):
- rect.move_ip((- best_cols * (best_rect.w + DIST + 2*best_rect.x), best_rect.h + CAPTION_H + DIST + 2*best_rect.y))
- no += 1
- ImageViewer.Color = pygame.Color('white')
- ImageViewer.Font = pygame.font.SysFont('Arial,Sans', 12)
- class Singleton(object):
- '''Implement Pattern: SINGLETON'''
- __lockObj = threading.RLock() # lock object
- __instance = None # the unique instance
- def __new__(cls, *args, **kargs):
- return cls.getInstance(*args, **kargs)
- def init(self):
- pass
- @classmethod
- def getInstance(cls, *args, **kargs):
- '''Static method to have a reference to **THE UNIQUE** instance'''
- # Critical section start
- cls.__lockObj.acquire()
- try:
- if cls.__instance is None:
- # (Some exception may be thrown...)
- # Initialize **the unique** instance
- cls.__instance = object.__new__(cls)
- cls.__instance.init(*args, **kargs)
- #'''Initialize object **here**, as you would do in __init__()...'''
- finally:
- # Exit from critical section whatever happens
- cls.__lockObj.release()
- # Critical section end
- return cls.__instance
- class App (Singleton, Actor):
- def init(self, directory, provider):
- self.stack = [self]
- self.provider = provider
- pygame.display.set_icon(pygame.image.load('annYmage.gif'))
- self.screen = pygame.display.set_mode( (SCREEN_WIDTH, SCREEN_HEIGHT) )
- pygame.display.set_caption('annYmage')
- ImageViewer(directory, provider)
- def dispatch(self, name, data):
- pygame.event.post(pygame.event.Event(pygame.USEREVENT, {'name': name, 'data': data}))
- def push(self, instance):
- self.stack.insert(0, instance)
- def pop(self):
- return self.stack.pop(0)
- def on_event(self, event):
- if event.type == pygame.QUIT:
- App.getInstance().terminate()
- def handleEvent(self, event):
- for layer in self.stack:
- if (layer.on_event(event)): break
- def run(self):
- self.stop = False
- while not self.stop:
- self.handleEvent(pygame.event.wait())
- for event in pygame.event.get():
- self.handleEvent(event)
- #print "Will draw on ", self.stack[0]
- self.screen.fill(0)
- for layer in reversed(self.stack):
- layer.on_draw()
- pygame.display.flip();
- def terminate(self):
- self.stop = True
- for layer in self.stack:
- layer.on_terminate()
- self.provider.terminate()
- def main(argv=[]):
- directory = (len(argv) > 1) and argv[1] or SAVE_DIRECTORY
- #provider = ImageProviderDirectory('test')
- provider = ImageProviderCamera()
- provider.start()
- try:
- App(directory, provider).run()
- except Exception as e:
- provider.terminate()
- raise e
- if __name__ == '__main__':
- main(sys.argv)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement