-
Notifications
You must be signed in to change notification settings - Fork 7
/
gpmulticam.py
278 lines (240 loc) · 9.05 KB
/
gpmulticam.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#!/usr/bin/env python3
# gphoto2 MultiCam Tethering Utility
# https://github.com/acropup/gphoto2-MultiCam-Tethering/
# Requires gphoto2 (linux-only): http://gphoto.org/doc/manual/using-gphoto2.html
# To run from command line, type: python3 -i gpmulticam.py
import re
import os
import subprocess #https://docs.python.org/3/library/subprocess.html
cameras = []
name_ind = 0
port_ind = 1
filename_format = '{0} - {1}.jpg' #0 is filename, 1 is camera name
simultaneous_capture = True
keep_on_camera = True
#TODO: notes for how to use subprocess
# p = subprocess.run(['dir', '/p'], stdout=subprocess.PIPE, universal_newlines=True)
# p = subprocess.run(['xdg-open', picfilename.jpg], stdout=subprocess.PIPE, universal_newlines=True)
# p= subprocess.Popen('ping google.com /t', stdout=subprocess.PIPE, universal_newlines=True)
# p.stdout.readline()
def main():
welcome = '*' * 3 + ' Welcome to Better Way Camera Tethering ' + '*' * 3
print('*' * len(welcome))
print(welcome)
print('*' * len(welcome))
print()
input('Press Enter to find cameras...')
initCameras()
while(True):
print('-'*40)
cmd = input('{} > '.format(os.getcwd())).strip()
if (not processCommand(cmd)):
break
def queryCameras():
"queries for connected cameras, and returns an array of their port mappings"
p = subprocess.run(['gphoto2', '--auto-detect'], stdout=subprocess.PIPE, universal_newlines=True)
# p.stdout should return something like this:
# Model Port
# ----------------------------------------------------------
# Canon PowerShot G2 usb:001,014
# Canon PowerShot G2 usb:001,023
if p.returncode == 0:
r = re.compile(r'^(.*?) +(usb:\S*)\s', re.MULTILINE)
matches = r.findall(p.stdout)
#tuples are immutable, so convert list of tuples to list of lists (so we can edit the name later)
matches = [list(elem) for elem in matches]
return True, matches
return False, []
def listCameras():
id_width = 4
name_width = 2 + max([len(c[name_ind]) for c in cameras])
print('{}{}{}'.format('ID'.ljust(id_width), 'Name'.ljust(name_width), 'Port'))
for i,c in enumerate(cameras):
print('{}{}({})'.format((str(i)+':').ljust(id_width), c[name_ind].ljust(name_width), c[port_ind]))
def renameCameras():
"Lets the user rename each camera. Takes a picture with each camera sequentially, to identify which camera it is."
print('''The camera name is part of the filename for
all pictures taken with it.
Choose a different name for every camera.
''')
for i,c in enumerate(cameras):
name = ''
print('Taking a picture with camera {}! '.format(i))
takePicture(c[port_ind], 'test.jpg')
openPicture('test.jpg')
while(len(name) == 0):
name = input('Enter name for camera {}: '.format(i))
c[name_ind] = name
print('All cameras have been named!')
def initCameras():
global cameras
success, result = queryCameras()
if (not success):
print('Query Camera failed! Make sure gphoto2 is installed.')
return
if (len(result) == 0):
print('No cameras found! Make sure that cameras are connected by USB and powered on.')
print('If camera is accessible as an external drive, you may have to "Eject..." it.')
return
cameras = result
print(len(cameras), 'cameras found:')
print()
listCameras()
print()
if (input_yn('Name cameras?')):
renameCameras()
print()
listCameras()
def openPicture(filename):
if (os.path.exists(filename)):
#This should open the image using the default image viewer
subprocess.Popen(['xdg-open', filename], universal_newlines=True)
else:
print('Could not open "{}"'.format(filename))
def takePicture(port, filename):
cmd_params = ['gphoto2', '--port', port, '--capture-image-and-download', '--force-overwrite', '--filename', filename]
subprocess.run(cmd_params, stdout=subprocess.DEVNULL)
def takePictures(subject):
procs = []
if (not cameras):
print('There are no cameras connected. Use fc command to find cameras.')
return
for c in cameras:
filename = filename_format.format(subject, c[name_ind])
if (os.path.exists(filename)):
if (not input_yn('File with same name already exists. Overwrite?')):
print('Aborting capture sequence. File already exists:\n{}'.format(os.path.abspath(filename)))
return
cmd_params = ['gphoto2', '--port', c[port_ind], '--capture-image-and-download', '--force-overwrite', '--filename', filename]
print('Taking picture: "{}"'.format(filename))
if (not simultaneous_capture):
subprocess.run(cmd_params, stdout=subprocess.PIPE) #this line blocks until the photo capture and download completes
openPicture(filename)
else:
procs.append([filename, subprocess.Popen(cmd_params, stdout=subprocess.PIPE, universal_newlines=True)])
if (simultaneous_capture):
for p in procs:
filename = p[0]
proc = p[1]
#wait up to 10 seconds for photo capture to complete (typically takes 3s), and just hope it was successful
try:
proc.wait(timeout=10)
openPicture(filename)
except TimeoutExpired:
print("Camera failed to take picture and download within 10 seconds.")
def processCommand(cmd):
global filename_format
global simultaneous_capture
global keep_on_camera
if (cmd == ''):
print('''Commands:
fc - find cameras
cn - camera names
sc - toggle sequential/simultaneous capture
kc - toggle keep photo on camera card
ff - filename format (ex. "{0} - {1}.jpg")
cd - change directory
ls - list directory contents
od - open directory in file browser
q - quit
Photo capture:
Anything more than 3 letters is considered a photo shot name,
and will trigger photo capture for the cameras.''')
if (len(cmd) < 3 or cmd[2] == ' '):
c = cmd[0:2]
param = cmd[3:]
if (c == 'q'): #Quit
if (input_yn('Are you sure you want to quit?')):
print('Quitting...')
return False
elif (c == 'fc'): #Find cameras
if (cameras):
print('Current camera list:')
print()
listCameras()
print()
if (input_yn('Search for cameras?')):
initCameras()
else:
initCameras()
elif (c == 'cn'): #Camera names
if (cameras):
print('Current camera list:')
print()
listCameras()
print()
if (input_yn('Rename all cameras?')):
renameCameras()
else:
print('There are no cameras to name. Use fc command to find cameras.')
elif (c == 'sc'): #Simultaneous capture toggle
simultaneous_capture = not simultaneous_capture
print('Capture mode: ' + ('Simultaneous' if simultaneous_capture else 'Sequential'))
elif (c == 'kc'): #Keep on camera card toggle
keep_on_camera = not keep_on_camera
print('Camera card retention mode: ' + ('Keep on camera card' if keep_on_camera else 'Delete from camera after download'))
elif (c == 'ff'): #Change filename format
print('Filename format: "{}"'.format(filename_format))
if (not param):
print('{0} for shot name, and {1} for camera name')
param = input('Set to: ')
if (param):
filename_format = param
print('Filename format: "{}"'.format(filename_format))
else:
print('No change')
elif (c == 'cd'): #Change directory
if (not param):
param = input('Change directory to: ')
if (param):
changed = cd(param)
if (not changed):
print('Path does not exist: ' + os.path.abspath(param))
if (input_yn("Make new directory?")):
if(mkdir(param)): #Make directory and try to cd again
changed = cd(param)
else:
print('Could not make directory')
else:
print('No change')
elif (c == 'ls'): #List directory contents
all_items = os.listdir('.')
dirs = [i for i in all_items if os.path.isdir(i)]
files = [i for i in all_items if os.path.isfile(i)]
if (dirs):
print('Folders:\n ' + '\n '.join(dirs))
else:
print('No folders.')
if (files):
maxfiles = len(files) if (param == '-a') else 10
print('Files:\n ' + '\n '.join(files[:maxfiles]))
if (len(files) > maxfiles):
print('...and {} more. Show all with "ls -a"'.format(len(files)-maxfiles))
else:
print('No files.')
elif (c == 'od'): #Open directory in file browser
print('Opening file browser to "{}"'.format(os.getcwd()))
#TODO This only works now because openPicture depends on xdg-open to do the right thing
openPicture('./')
elif (len(cmd) >= 3): #Take picture
takePictures(cmd)
return True
def cd(path):
try:
os.chdir(path)
except FileNotFoundError:
return False
except NotADirectoryError:
return False
return True
def mkdir(path):
try:
os.makedirs(path)
except FileNotFoundError:
return False
except FileExistsError:
return False
return True
def input_yn(msg):
return 'y' == input(msg + ' (y/n) ')[:1].lower()
main()