#!/usr/bin/python
import time, thread, os, string, termios, TERMIOS, tty, select
from Tkinter import *
from SimpleDialog import SimpleDialog
# This software is freeware and may be used for any purpose free of
# charge. There is no warranty of any kind, expressed or implied, and
# the author accepts no liability for any loss or damage that may result
# from the use of this software. No support is available. You are
# entirely on your own.
class QVrec:
"""
This class just looks after the serial character interface
to the camera in record mode.
"""
def __init__(self,dummy=0,device='/dev/ttyS0'):
self.dummy = dummy
if not dummy:
self.p = open(device, 'w+b', 0)
self.fd = self.p.fileno()
self.rawtty()
def rawtty(self):
termios.tcflush(self.fd,TERMIOS.TCIOFLUSH)
tty.setraw(self.fd, TERMIOS.TCSANOW)
def write(self,data):
if self.dummy:
print 'To camera:', data
else:
self.p.write(data)
termios.tcdrain(self.fd)
def write_pair(self, A, a, timeout=0.02):
self.write(A)
self.sleep(timeout)
self.write(a)
self.sleep(0.02)
def sleep(self,duration):
# Use select to get a more accurate sleep than sleep()
select.select([],[],[],duration)
def focus_lock(self):
self.write_pair('A','a',1.0)
def shutter(self):
self.write_pair('B', 'b', 4.0)
def zoom_start(self,direction):
if direction < 0:
self.write('C')
else:
self.write('D')
self.sleep(0.02) # just to be safe
def zoom_end(self,direction):
if direction < 0:
self.write('c')
else:
self.write('d')
self.sleep(0.02) # just to be safe
def menu(self):
self.write_pair('E','e')
def up(self):
self.write_pair('F','f')
def left(self):
self.write_pair('G','g')
def right(self):
self.write_pair('H','h')
def set(self):
self.write_pair('I', 'i')
def down(self):
self.write_pair('J','j')
def flash(self):
self.write_pair('K', 'k')
def focus_set(self):
self.write_pair('L', 'l', 1.0)
def usb(self):
self.write_pair('M', 'm', 4.0)
def mode(self):
self.write_pair('N', 'n')
def disp(self):
self.write_pair('O', 'o')
def preview(self):
self.write_pair('Q', 'q', 4.0)
def self_timer(self):
self.write_pair('P','p')
class StatusBar(Frame):
def __init__(self,parent):
Frame.__init__(self,parent)
self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
self.label.pack(fill=X)
def set(self, format, *args):
self.label.config(text=(format % args))
self.label.update_idletasks() # Hangs on win32
def clear(self):
self.label.config(text="")
self.label.update_idletasks()
class QVTk:
"""
The GUI
"""
def __init__(self,top=None):
if top:
self.top = top
else:
self.top = Tk(baseName="QV", className="QV")
self.loadrc()
# Topmost menu bar
menubar = Frame(self.top)
Button(menubar,text='Quit',
command=self.quit).pack(side=LEFT)
Button(menubar,text='Help',
command=self.help).pack(side=RIGHT)
menubar.pack(side=TOP,fill=X,pady=5)
# Device and directory info
sdframe = Frame(self.top)
Label(sdframe,text='Serial:').pack(side=LEFT)
self.sentry = Entry(sdframe,width=15,)
self.sentry.insert(0,self.serial_device)
self.sentry.bind("<Return>",self.serialget)
self.sentry.bind("<Leave>",self.serialget)
self.sentry.pack(side=LEFT,fill=X,expand=1)
sdframe.pack(side=TOP,fill=X)
dframe = Frame(self.top)
Label(dframe,text='Download:').pack(side=LEFT)
self.dentry = Entry(dframe)
self.dentry.insert(0,self.diskpath)
self.dentry.bind("<Return>",self.diskpathget)
self.dentry.bind("<Leave>",self.diskpathget)
self.dentry.pack(side=LEFT,fill=X,expand=1)
dframe.pack(side=TOP,fill=X)
cframe = Frame(self.top)
Label(cframe,text='Mount:').pack(side=LEFT)
self.centry = Entry(cframe)
self.centry.insert(0,self.campath)
self.centry.bind("<Return>",self.campathget)
self.centry.bind("<Leave>",self.campathget)
self.centry.pack(side=LEFT,fill=X,expand=1)
cframe.pack(side=TOP,fill=X)
# Other menubar
menubar = Frame(self.top)
Button(menubar,text='Index',
command=self.make_index).pack(side=RIGHT)
Button(menubar,text='Sync',
command=self.synchronize).pack(side=RIGHT)
Button(menubar,text='Unmount',
command=self.unmount).pack(side=RIGHT)
Button(menubar,text='Mount',
command=self.mount).pack(side=RIGHT)
Button(menubar,text='USB',
command=self.usb).pack(side=RIGHT)
menubar.pack(side=TOP,pady=5)
# Frame for camera controls
clabel = Label(self.top,text='Controls',bd=1,relief=SUNKEN,bg='grey')
clabel.pack(fill=X)
cframe = Frame(self.top)
# Frame for menu, arrows, set
qframe = Frame(cframe)
# Up/down/left/right keys for menu traversal, etc.
Button(qframe, text='Menu', command=self.menu).pack(side=TOP)
arrows = self.make_arrows(qframe)
arrows.pack(side=TOP)
Button(qframe, text='Set', command=self.set).pack(side=TOP)
qframe.pack(side=LEFT)
# Menu, mode, flash, self timer buttons.
mframe = Frame(cframe,bg='grey',border=1,relief=RIDGE)
Button(mframe, text='Mode', command=self.mode).pack(side=TOP,fill=X)
Button(mframe, text='Flash', command=self.flash).pack(side=TOP,fill=X)
Button(mframe, text='Timer', command=self.timer).pack(side=TOP,fill=X)
Button(mframe, text='Disp', command=self.disp).pack(side=TOP,fill=X)
Button(mframe, text='Preview', command=self.prev).pack(side=TOP,fill=X)
mframe.pack(side=LEFT,padx=10)
# Zoom, Focus, Take Pic.
pframe = Frame(cframe)
zframe = Frame(pframe,border=0)
Label(zframe,text='Zoom',justify=LEFT,border=0).pack(side=LEFT,fill=X)
zoomin = Button(zframe,text='In')
zoomin.bind("<Button-1>", self.zoomin_start)
zoomin.bind("<ButtonRelease-1>", self.zoomin_stop)
zoomin.pack(side=LEFT,padx=5)
zoomout = Button(zframe,text='Out')
zoomout.bind("<Button-1>", self.zoomout_start)
zoomout.bind("<ButtonRelease-1>", self.zoomout_stop)
zoomout.pack(side=LEFT)
zframe.pack(side=TOP)
fframe = Frame(pframe,border=0)
Label(fframe,text='Focus',justify=LEFT,border=0).pack(side=LEFT,fill=X)
Button(fframe,text='Set',command=self.focus_set).pack(side=LEFT,padx=5)
Button(fframe,text='Lock',command=self.focus_lock).pack(side=LEFT)
fframe.pack(side=TOP,pady=10)
Button(pframe,text='Take Picture',border=4,
command=self.take_picture).pack(side=TOP,fill=X)
pframe.pack(side=LEFT)
cframe.pack(side=TOP,fill=BOTH, pady=5)
# Frame for time lapse controls
Label(self.top,text='Time lapse',bd=1,relief=SUNKEN,bg='grey').\
pack(side=TOP,fill=X,pady=5)
tlframe = Frame(self.top)
vframe = Frame(tlframe,border=0)
Label(vframe,text='Pictures', border=0).pack(side=LEFT,padx=0)
self.pics_entry = Entry(vframe,width=6)
self.pics_entry.bind("<Return>",self.pics_get)
self.pics_entry.bind("<Leave>",self.pics_get)
self.pics_set(999)
self.pics_entry.pack(side=LEFT,padx=5)
Label(vframe,text='Interval (s)', border=0).pack(side=LEFT,padx=5)
self.secs_entry = Entry(vframe,width=6)
self.secs_entry.bind("<Return>",self.secs_get)
self.secs_entry.bind("<Leave>",self.secs_get)
self.secs_set(60)
self.secs_entry.pack(side=LEFT,padx=5)
Label(vframe,text='Delay (h)', border=0).pack(side=LEFT,padx=5)
self.delay_entry = Entry(vframe,width=6)
self.delay_entry.bind("<Return>",self.delay_get)
self.delay_entry.bind("<Leave>",self.delay_get)
self.delay_set(0)
self.delay_entry.pack(side=LEFT,padx=5)
vframe.pack(side=TOP,fill=X)
vframe = Frame(tlframe,border=0)
self.secs_next = Label(vframe,text='Time to next 0.0s',width=22,bd=1)
self.secs_next.pack(side=LEFT,padx=0)
self.taken = 0
self.taken_label = Label(vframe, width=15, text='Inactive: 0')
self.taken_label.pack(side=LEFT,fill=X,expand=YES)
vframe.pack(side=TOP,fill=X)
bframe = Frame(tlframe,border=0)
self.start = Button(bframe, text='Start', command=self.start)
self.start.pack(side=LEFT)
self.pause = Button(bframe, text='Pause', command=self.pause,
state=DISABLED)
self.pause.pack(side=LEFT,padx=5)
self.cancel = Button(bframe, text='Cancel', command=self.cancel,
state=DISABLED)
self.cancel.pack(side=LEFT)
bframe.pack(side=TOP,fill=X)
tlframe.pack(side=TOP,fill=X)
# Status bar at the bottom
self.status = StatusBar(self.top)
self.status.pack(side=TOP,fill=X)
# Finally, connect to the camera
try:
self.qv = QVrec(device=self.serial_device,dummy=0)
except:
self.update_status("Failed to open " + self.serial_device)
if not top:
self.top.mainloop()
def loadrc(self):
self.campath = "/mnt/camera"
self.diskpath = os.environ['HOME'] + '/Desktop/qv3000'
self.serial_device = '/dev/ttyS0'
self.browser = "netscape"
self.rcfile = os.environ['HOME'] + "/" + '.qvrc'
if os.path.exists(self.rcfile):
for line in open(self.rcfile,"r").readlines():
key,value = string.split(line)
exec("self."+key+" = '"+value+"'")
else:
self.saverc()
def saverc(self):
rc = open(self.rcfile,"w")
rc.write("campath " + self.campath + "\n")
rc.write("diskpath " + self.diskpath + "\n")
rc.write("serial_device " + self.serial_device + "\n")
rc.write("browser " + self.browser + "\n")
rc.close()
def make_arrows(self,parent):
arrows = Frame(parent,border=1,bg='grey', relief=RIDGE)
row1 = Frame(arrows,border=0,bg='grey')
row1.pack(side=TOP,fill=X)
row2 = Frame(arrows,border=0,bg='grey')
row2.pack(side=TOP,fill=X)
row3 = Frame(arrows,border=0,bg='grey')
row3.pack(side=TOP,fill=X)
Label(row1,text='',bg='grey').pack(side=LEFT, fill=X, expand=YES)
Button(row1,text='Up',command=self.up).pack(side=LEFT)
Label(row1,text='',bg='grey').pack(side=LEFT, fill=X, expand=YES)
Button(row2,text='Left',command=self.left).pack(side=LEFT)
Label(row2,text='',bg='grey').pack(side=LEFT, fill=X, expand=YES)
Button(row2,text='Right',command=self.right).pack(side=RIGHT)
Label(row3,text='',bg='grey').pack(side=LEFT, fill=X, expand=YES)
Button(row3,text='Down',command=self.down).pack(side=LEFT)
Label(row3,text='',bg='grey').pack(side=RIGHT, fill=X, expand=YES)
return arrows
def serialget(self,event):
prev = self.serial_device
self.serial_device = self.sentry.get()
self.status.clear()
if prev != self.serial_device:
if not os.path.exists(self.serial_device):
self.update_status(self.serial_device + ' does not exist')
self.serial_device = prev
self.sentry.delete(0,END)
self.sentry.insert(0,self.serial_device)
else:
try:
self.qv = QVrec(device=self.serial_device)
self.update_status('Changed serial device to ' +
self.serial_device)
self.saverc()
except:
self.update_status("Failed to open " + self.serial_device)
self.serial_device = prev
self.sentry.delete(0,END)
self.sentry.insert(0,self.serial_device)
self.qv = QVrec(device=self.serial_device)
def diskpathget(self,event):
prev = self.diskpath
self.diskpath = self.dentry.get()
self.status.clear()
if prev != self.diskpath:
self.update_status('New download directory ' + self.diskpath)
self.saverc()
def campathget(self,event):
prev = self.campath
self.campath = self.centry.get()
self.status.clear()
if prev != self.campath:
self.update_status('Mount point ' + self.campath)
self.saverc()
def quit(self):
self.top.quit()
def mounted(self):
return os.path.exists(self.campath+"/dcim")
def usb(self):
self.qv.usb()
self.update_status("Toggled USB mode")
def mount(self):
'''
Try to mount the camera switching into USB mode if necessary
'''
if self.mounted():
self.update_status("Already mounted " + self.campath)
return
mount_command = "mount "+self.campath+" 2>1 1>/dev/null"
if os.system(mount_command) == 0:
self.update_status("Mounted "+self.campath)
return
self.usb()
if os.system(mount_command) == 0:
self.update_status("Mounted "+self.campath)
return
self.update_status("Failed to mount the camera")
def unmount(self):
'''
Unmount the camera and switch out of USB mode if necessary
'''
if self.mounted():
if os.system("umount "+self.campath+" 2>1 1>/dev/null") == 0:
self.update_status("Unmounted "+self.campath)
self.usb()
else:
self.update_status("Failed to unmount "+self.campath)
else:
self.update_status("Not mounted "+self.campath)
def synchronize(self):
# Sync the contents of the camera with the disk copy
# Relies on depth first traversal by walk which is kinda
# broken by empty directories but it works nevertheless
if not self.mounted():
self.mount()
if not os.path.exists(self.diskpath):
try:
os.makedirs(self.diskpath)
os.makedirs(self.diskpath+"/dcim")
os.makedirs(self.diskpath+"/tiff")
except:
self.update_status("Failed to make " + self.diskpath)
return
camfiles = []
os.path.walk(self.campath,
lambda arg,dir,nam: arg.append([dir,nam]),
camfiles)
base = len(self.campath)
total = 0
for dir,names in camfiles:
for name in names:
diskdir = self.diskpath + dir[base:]
if not os.path.exists(diskdir):
os.makedirs(diskdir)
diskfile = diskdir + "/" + name
camfile = self.campath + dir[base:] + "/" + name
if not os.path.exists(diskfile):
self.update_status("Synchronizing "+camfile)
try:
data = open(camfile,"rb").read()
total = total + len(data)/1024
open(diskfile,"wb").write(data)
except:
self.update_status("Failed synchronizing "+camfile)
time.sleep(1)
self.unmount()
self.update_status("Synchronized %d kbyte" % total)
def update_status(self,msg):
self.status.set("%s",msg)
def menu(self):
''' Press and release the camera MENU button '''
self.qv.menu()
self.update_status('Camera menu')
def mode(self):
''' Press and release the camera MODE button '''
self.qv.mode()
self.update_status('Camera mode')
def flash(self):
''' Press and release the camera FLASH button '''
self.qv.flash()
self.update_status('Camera flash')
def timer(self):
''' Press and release the camera self-TIMER button '''
self.qv.self_timer()
self.update_status('Camera self-timer')
def disp(self):
''' Press and release the camera DISP button '''
self.qv.disp()
self.update_status('Camera display')
def prev(self):
''' Press and release the camera Preview button '''
self.qv.preview()
self.update_status('Camera preview')
def set(self):
''' Press and release the camera SET button '''
self.qv.set()
self.update_status('Camera set')
def zoomin_start(self,event):
''' Start ZOOMing IN '''
self.qv.zoom_start(1)
self.update_status('Zooming in ...')
def zoomin_stop(self,event):
''' Stop ZOOMing IN '''
self.qv.zoom_end(1)
self.update_status('Zoom in finished')
def zoomout_start(self,event):
''' Start ZOOMing OUT '''
self.qv.zoom_start(-1)
self.update_status('Zooming out ...')
def zoomout_stop(self,event):
''' Stop ZOOMing OUT '''
self.qv.zoom_end(-1)
self.update_status('Zoom out finished')
def focus_set(self):
''' Half-press and release the camera shutter to SET FOCUS '''
self.qv.focus_set()
self.update_status('Camera focus set')
def focus_lock(self):
''' Half-press and hold the camera shutter to set and LOCK FOCUS '''
self.qv.focus_lock()
self.update_status('Camera focus locked')
def take_picture(self):
''' Press the camera shutter to TAKE a PICTURE '''
self.update_status('Taking picture ...')
self.qv.shutter()
self.update_status('Picture taken')
def up(self):
''' Press and release the camera UP arrow button '''
self.qv.up()
self.update_status('Camera up arrow')
def down(self):
''' Press and release the camera DOWN arrow button '''
self.qv.down()
self.update_status('Camera down arrow')
def left(self):
''' Press and release the camera LEFT arrow button '''
self.qv.left()
self.update_status('Camera left arrow')
def right(self):
''' Press and release the camera RIGHT arrow button '''
self.qv.right()
self.update_status('Camera right arrow')
def update_taken(self,msg):
self.taken_label.config(text=((msg+': %d pics') % self.taken))
def sleep_until(self,target):
'''
Sleep until the current time is >= target checking every second
to see if this thread should exit.
'''
while 1:
rem = target - time.time()
if rem <= 0.5:
break
self.secs_next.config(text=("Time to next %.1fs" % rem))
time.sleep(min(1,rem))
if self.kill_time_lapse_thread:
thread.exit()
self.update_status('Sleeping')
self.secs_next.config(text=("%8.1fs" % 0.0))
def do_time_lapse(self):
self.sleep_until(time.time()+self.delay*3600)
start = next = time.time() # Start timing from when picture taken
self.taken = 0
while self.taken < self.pics:
self.sleep_until(next)
next = next + self.secs
if self.pause_time_lapse:
self.update_status('Not taking scheduled picture due to pause')
else:
self.take_picture()
self.taken = self.taken + 1
self.update_taken('Active')
self.update_taken('Finished')
self.start.config(state=NORMAL)
self.pause.config(state=DISABLED)
self.cancel.config(state=DISABLED)
self.pause.config(text='Pause')
def start(self):
''' Start a sequence of time lapse pictures '''
self.taken = 0
self.pause_time_lapse = 0
self.kill_time_lapse_thread = 0
self.start.config(state=DISABLED)
self.pause.config(state=NORMAL)
self.cancel.config(state=NORMAL)
thread.start_new_thread(self.do_time_lapse,())
self.update_status('Time lapse started')
def pause(self):
if self.pause_time_lapse:
self.pause_time_lapse = 0
self.update_taken('Active')
self.update_status('Time lapse continued')
self.pause.config(text='Pause')
else:
self.pause_time_lapse = 1
self.update_taken('Paused')
self.update_status('Time lapse paused')
self.pause.config(text='Continue')
def cancel(self):
self.kill_time_lapse_thread = 1
self.update_status('Time lapse being cancelled ...')
time.sleep(5) # Wait for the thread to see the flag
self.update_taken('Cancelled')
self.update_status('Time lapse has been cancelled')
self.start.config(state=NORMAL)
self.pause.config(state=DISABLED)
self.cancel.config(state=DISABLED)
self.pause.config(text='Pause')
def secs_set(self,value):
self.secs = value
self.secs_entry.delete(0,END)
self.secs_entry.insert(0,`value`)
def delay_set(self,value):
self.delay = value
self.delay_entry.delete(0,END)
self.delay_entry.insert(0,`value`)
def pics_set(self,value):
self.pics = value
self.pics_entry.delete(0,END)
self.pics_entry.insert(0,`value`)
def secs_get(self,event):
prev = self.secs
s = self.secs_entry.get()
try:
self.secs = int(s)
if prev != self.secs:
self.update_status(
'set time-lapse interval to %ds' % self.secs)
except ValueError:
self.secs_set(prev)
self.update_status('ValueError: interval must be an integer')
if (self.secs < 4) or (self.secs > 864000):
self.secs_set(prev)
self.update_status('ValueError: interval must be in range 4s - 10days')
def delay_get(self,event):
prev = self.delay
h = self.delay_entry.get()
try:
self.delay = float(h)
if prev != self.delay:
self.update_status(
'set delay to %.2f hours' % self.delay)
except ValueError:
self.delay_set(prev)
self.update_status('Specify delay in hours (floating point)')
if (self.delay < 0):
self.delay_set(prev)
self.update_status('ValueError: delay must be positive')
def pics_get(self,event):
prev = self.pics
s = self.pics_entry.get()
try:
self.pics = int(s)
if prev != self.pics:
self.update_status(
'set #pics for time-lapse to %d' % self.pics)
except ValueError:
self.pics_set(prev)
self.update_status('ValueError: #pics must be an integer')
if (self.pics < 1) or (self.pics > 2500):
self.pics_set(prev)
self.update_status('ValueError: #pics must be in range 1 - 2500')
def make_index(self):
fromdir = self.diskpath+"/dcim"
todir = self.diskpath+"/thumbs"
indexpath = self.diskpath+"/index.html"
if os.system("mkdir -p " + todir):
self.update_status("Failed to make " + todir)
return
find = command = 'find ' + fromdir + \
r' \( -name "*.jpg" -o -name "*.JPG" \) | grep -v preview'
title = "Contents of " + fromdir
index = open(indexpath,"w")
index.write("<html>\n<head><title>"+title+"</title></head><body>\n")
index.write("<table border=1>\n<tr>")
npic = 0
npic_per_row = 6
names = [""]*npic_per_row
for file in os.popen(command,'r').readlines():
fromfile = file[:-1]
dir = os.path.dirname(fromfile)
base = os.path.basename(fromfile)
dirnum = string.split(fromfile,"/")[-2]
tofile = todir + '/' + dirnum + "_" + base
if not os.path.exists(tofile):
self.update_status(fromfile)
os.system("convert "+fromfile+" -sample 128x128 " + tofile)
index.write('<td>' +
'<a href="'+fromfile+'">' +
'<img src="'+tofile+'">' +
'</a>' +
'</td>\n')
names[npic%npic_per_row] = dirnum + "/" + base
if (npic%npic_per_row) == (npic_per_row-1):
index.write("</tr><tr>\n")
for name in names:
index.write('<td>'+name+'</td>')
index.write("</tr><tr>\n")
names = [""]*npic_per_row
npic = npic + 1
index.write('</tr></table>\n')
index.write("</body></html>\n")
self.update_status("Index generated")
os.system("netscape "+indexpath+" 2>1 1>/dev/null &")
def help(self):
text = """
Version 1.0
Use with your camera in record mode and connected by one or both of:
- the serial cable - for time lapse
- the USB cable - for mounting and/or synchronization
To use USB get the 2.4.* kernel patch at http://www.harald-schreiber.de.
You need read/write access to the serial device (as root do 'chmod a+rw
/dev/ttyS0' or whatever your device is). So that users can mount
the camera make a mount point (/mnt/camera) and put this in /etc/fstab
/dev/sda1 /mnt/camera vfat rw,noauto,user 0 0
If you have other SCSI devices, you can find out what number the camera
is by using dmesg after connecting it to the computer. On my laptop,
USB does not work after putting it to sleep -- I have to reboot.
Defaults are saved in $HOME/.qvrc
Quit - Immediately terminate
Help - This text
USB - Toggle USB mode on the camera
Mount - Mount the camera, switching into USB mode if necessary
Umount - Umount the camera and switch out of USB mode
Sync - Copy new files from camera to disk, switching in and out of USB
mode, and mount/unmount camera as necessary. Note that switching in/out
of USB resets the camera to defaults so, although you can run synch
during a time lapse (by pausing), you will lose zoom and other settings.
Index - Make thumbnails, index file, and launch browser
Serial - Path to serial device (saved in .qvrc)
Download - Directory for downloads (saved in .qvrc)
Mount - Mount point for camera (saved in .qvrc)
Controls - should be familar from your camera
Time lapse:
Pictures - The number of pictures to take
Interval - Time between pictures in seconds. 10s minimum.
Delay - Time in hours until start of sequence (e.g., 1.5 hours = 90 minutes)
Start - Starts a time lapse sequence
Pause - Keeps timing but does not actually take the pictures
Cancel - Aborts the sequence.
"""
SimpleDialog(self.top, text=text, buttons=['OK']).go()
def __del__(self):
pass
if __name__ == "__main__":
QVTk()
|