Merge lp:~bladernr/checkbox/1100594-fix-camera_test-new-gir1.2 into lp:checkbox
- 1100594-fix-camera_test-new-gir1.2
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Brendan Donegan |
Approved revision: | 2104 |
Merged at revision: | 2101 |
Proposed branch: | lp:~bladernr/checkbox/1100594-fix-camera_test-new-gir1.2 |
Merge into: | lp:checkbox |
Diff against target: |
279 lines (+79/-47) 3 files modified
debian/changelog (+3/-0) jobs/camera.txt.in (+0/-1) scripts/camera_test (+76/-46) |
To merge this branch: | bzr merge lp:~bladernr/checkbox/1100594-fix-camera_test-new-gir1.2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brendan Donegan (community) | Approve | ||
Jeff Lane | Needs Resubmitting | ||
Review via email:
|
Commit message
Description of the change
fixes camera test by adding bits to determine what version of gst we're using and then set the plugin and video type appropriately for either 0.10 or 1.x. This resolves a problem when fswebcam is not present and we fall back to gst.
Fixed the error handling for the fswebcam call to avoid having nested tracebacks that can be confusing. Now if the call to fswebcam fails, we instead trap the error and run the fallback code outside the exception. If THAT fails, we'll get a less confusing traceback now.
Removed an extraneous line in the requires for one of the camera test jobs (listed gir1.2 twice)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Zygmunt Krynicki (zyga) wrote : | # |
- package.name == 'gir1.2-
This is a bit wrong in general (not the actual change but stuff really required). Anything that uses gir needs to depend on python3-gi
- 2099. By Jeff Lane
-
"[r=zkrynicki][bug=1103647][author=bladernr] automatic merge by tarmac"
- 2100. By Brendan Donegan
-
"[r=zkrynicki][bug=1093718][author=
brendan- donegan] automatic merge by tarmac" - 2101. By Jeff Lane
-
Fixed camera_test to work with both gir1.2-0.10 and gir1.2-1.x
- 2102. By Jeff Lane
-
Removed an exraneous requires line in one of the camera test jobs
- 2103. By Jeff Lane
-
Fixed problems with camera_test in Raring with gir1.2-1.0.6 changes
- 2104. By Jeff Lane
-
Fixed a problem where running camera_test without a test specified resulted in a traceback. A lot of PEP8 fixes
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jeff Lane (bladernr) wrote : | # |
Brendan: fixed my two pep8 errors (spaces) the rest are original to sylvain's code. I've fixed all of those but one because no matter what I tried, I couldn't keep it from generating several more pep8 problems that were seemingly unsolvable. But this one remaining one has been in the test for a long while now so it shouldn't affect things, I hope.
As for fswebcam, it's not specifically the lack of fswebcam, that just brought it to the attention of the guy who reported the bug initially. IIRC, you mentioned the "display" test, which uses gstreamer to create the display window, so I'd expect you would see that bug there.
If this updated script doesn't resolve it on your end, there may be more going on than simply setting the right plugin and video type for gstreamer.
Zygmunt: Yeah, probably so, there are 17 scripts that rely on python3-gi, none of which list it as a requirement. I'm not going to fix those here. That said, however, python3-gi is required for installing checkbox itself, rathter than just a suggests. Then again, so are the gir1.2 packages, so really, neither of those should be required on a test by test basis, you'd think, since they're already required for installing checkbox to begin with.
Anyway, here's a new version with fixes to some problems I didn't catch the first time, and a rash of PEP8 fixes as well.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Brendan Donegan (brendan-donegan) wrote : | # |
To me it looks fine then. I hope Tarmac doesn't get pissy about the pep8 errors - FYI only files which are modified get checked for errors, so that could be the reason why previous ones were missed, since this file hasn't been touched in a long time.
Preview Diff
1 | === modified file 'debian/changelog' | |||
2 | --- debian/changelog 2013-05-03 10:47:13 +0000 | |||
3 | +++ debian/changelog 2013-05-03 14:34:26 +0000 | |||
4 | @@ -10,6 +10,9 @@ | |||
5 | 10 | than define elements during parsing of /proc/cpuinfo (LP: #1111878) | 10 | than define elements during parsing of /proc/cpuinfo (LP: #1111878) |
6 | 11 | * scripts/lsmod_info: Corrected error handling for the check_output() call to | 11 | * scripts/lsmod_info: Corrected error handling for the check_output() call to |
7 | 12 | trap the correct error. (LP: #1103647) | 12 | trap the correct error. (LP: #1103647) |
8 | 13 | * jobs/camera.txt.in: removed an extraneous requres line for gir1.2 | ||
9 | 14 | scripts/camera_test: added code to determine what version of gst we're | ||
10 | 15 | using and set video type and plugin accordingly. (LP: #1100594) | ||
11 | 13 | 16 | ||
12 | 14 | [ Sylvain Pineau ] | 17 | [ Sylvain Pineau ] |
13 | 15 | * jobs/suspend.txt.in, scripts/gpu_test: Remove the need of running the script | 18 | * jobs/suspend.txt.in, scripts/gpu_test: Remove the need of running the script |
14 | 16 | 19 | ||
15 | === modified file 'jobs/camera.txt.in' | |||
16 | --- jobs/camera.txt.in 2012-11-06 17:11:49 +0000 | |||
17 | +++ jobs/camera.txt.in 2013-05-03 14:34:26 +0000 | |||
18 | @@ -9,7 +9,6 @@ | |||
19 | 9 | name: camera/display | 9 | name: camera/display |
20 | 10 | depends: camera/detect | 10 | depends: camera/detect |
21 | 11 | requires: | 11 | requires: |
22 | 12 | package.name == 'gir1.2-gst-plugins-base-0.10' or package.name == 'gir1.2-gst-plugins-base-1.0' | ||
23 | 13 | device.category == 'CAPTURE' | 12 | device.category == 'CAPTURE' |
24 | 14 | command: camera_test display | 13 | command: camera_test display |
25 | 15 | _description: | 14 | _description: |
26 | 16 | 15 | ||
27 | === modified file 'scripts/camera_test' | |||
28 | --- scripts/camera_test 2012-11-01 11:58:46 +0000 | |||
29 | +++ scripts/camera_test 2013-05-03 14:34:26 +0000 | |||
30 | @@ -162,11 +162,13 @@ | |||
31 | 162 | """ | 162 | """ |
32 | 163 | A simple class that displays a test image via GStreamer. | 163 | A simple class that displays a test image via GStreamer. |
33 | 164 | """ | 164 | """ |
35 | 165 | def __init__(self, args): | 165 | def __init__(self, args, gst_plugin=None, gst_video_type=None): |
36 | 166 | self.args = args | 166 | self.args = args |
37 | 167 | self._mainloop = GObject.MainLoop() | 167 | self._mainloop = GObject.MainLoop() |
38 | 168 | self._width = 640 | 168 | self._width = 640 |
39 | 169 | self._height = 480 | 169 | self._height = 480 |
40 | 170 | self._gst_plugin = gst_plugin | ||
41 | 171 | self._gst_video_type = gst_video_type | ||
42 | 170 | 172 | ||
43 | 171 | def detect(self): | 173 | def detect(self): |
44 | 172 | """ | 174 | """ |
45 | @@ -186,19 +188,25 @@ | |||
46 | 186 | print(" name : %s" % cp.card.decode('UTF-8')) | 188 | print(" name : %s" % cp.card.decode('UTF-8')) |
47 | 187 | print(" driver : %s" % cp.driver.decode('UTF-8')) | 189 | print(" driver : %s" % cp.driver.decode('UTF-8')) |
48 | 188 | print(" version: %s.%s.%s" | 190 | print(" version: %s.%s.%s" |
50 | 189 | % (cp.version >> 16, (cp.version >> 8) & 0xff, cp.version & 0xff)) | 191 | % (cp.version >> 16, |
51 | 192 | (cp.version >> 8) & 0xff, | ||
52 | 193 | cp.version & 0xff)) | ||
53 | 190 | print(" flags : 0x%x [" % cp.capabilities, | 194 | print(" flags : 0x%x [" % cp.capabilities, |
59 | 191 | ' CAPTURE' if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE else '', | 195 | ' CAPTURE' if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE |
60 | 192 | ' OVERLAY' if cp.capabilities & V4L2_CAP_VIDEO_OVERLAY else '', | 196 | else '', |
61 | 193 | ' READWRITE' if cp.capabilities & V4L2_CAP_READWRITE else '', | 197 | ' OVERLAY' if cp.capabilities & V4L2_CAP_VIDEO_OVERLAY |
62 | 194 | ' STREAMING' if cp.capabilities & V4L2_CAP_STREAMING else '', | 198 | else '', |
63 | 195 | ' ]', sep="") | 199 | ' READWRITE' if cp.capabilities & V4L2_CAP_READWRITE |
64 | 200 | else '', | ||
65 | 201 | ' STREAMING' if cp.capabilities & V4L2_CAP_STREAMING | ||
66 | 202 | else '', | ||
67 | 203 | ' ]', sep="") | ||
68 | 196 | 204 | ||
69 | 197 | resolutions = self._get_supported_resolutions(device) | 205 | resolutions = self._get_supported_resolutions(device) |
70 | 198 | print(' ', | 206 | print(' ', |
74 | 199 | self._supported_resolutions_to_string(resolutions).replace( | 207 | self._supported_resolutions_to_string(resolutions).replace( |
75 | 200 | "\n", " "), | 208 | "\n", " "), |
76 | 201 | sep="") | 209 | sep="") |
77 | 202 | 210 | ||
78 | 203 | if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE: | 211 | if cp.capabilities & V4L2_CAP_VIDEO_CAPTURE: |
79 | 204 | cap_status = 0 | 212 | cap_status = 0 |
80 | @@ -209,10 +217,12 @@ | |||
81 | 209 | Activate camera (switch on led), but don't display any output | 217 | Activate camera (switch on led), but don't display any output |
82 | 210 | """ | 218 | """ |
83 | 211 | pipespec = ("v4l2src device=%(device)s " | 219 | pipespec = ("v4l2src device=%(device)s " |
86 | 212 | "! video/x-raw-yuv " | 220 | "! %(type)s " |
87 | 213 | "! ffmpegcolorspace " | 221 | "! %(plugin)s " |
88 | 214 | "! testsink" | 222 | "! testsink" |
90 | 215 | % {'device': self.args.device}) | 223 | % {'device': self.args.device, |
91 | 224 | 'type': self._gst_video_type, | ||
92 | 225 | 'plugin': self._gst_plugin}) | ||
93 | 216 | self._pipeline = Gst.parse_launch(pipespec) | 226 | self._pipeline = Gst.parse_launch(pipespec) |
94 | 217 | self._pipeline.set_state(Gst.State.PLAYING) | 227 | self._pipeline.set_state(Gst.State.PLAYING) |
95 | 218 | time.sleep(10) | 228 | time.sleep(10) |
96 | @@ -223,12 +233,14 @@ | |||
97 | 223 | Displays the preview window | 233 | Displays the preview window |
98 | 224 | """ | 234 | """ |
99 | 225 | pipespec = ("v4l2src device=%(device)s " | 235 | pipespec = ("v4l2src device=%(device)s " |
102 | 226 | "! video/x-raw-yuv,width=%(width)d,height=%(height)d " | 236 | "! %(type)s,width=%(width)d,height=%(height)d " |
103 | 227 | "! ffmpegcolorspace " | 237 | "! %(plugin)s " |
104 | 228 | "! autovideosink" | 238 | "! autovideosink" |
105 | 229 | % {'device': self.args.device, | 239 | % {'device': self.args.device, |
106 | 240 | 'type': self._gst_video_type, | ||
107 | 230 | 'width': self._width, | 241 | 'width': self._width, |
109 | 231 | 'height': self._height}) | 242 | 'height': self._height, |
110 | 243 | 'plugin': self._gst_plugin}) | ||
111 | 232 | self._pipeline = Gst.parse_launch(pipespec) | 244 | self._pipeline = Gst.parse_launch(pipespec) |
112 | 233 | self._pipeline.set_state(Gst.State.PLAYING) | 245 | self._pipeline.set_state(Gst.State.PLAYING) |
113 | 234 | time.sleep(10) | 246 | time.sleep(10) |
114 | @@ -240,11 +252,11 @@ | |||
115 | 240 | """ | 252 | """ |
116 | 241 | if self.args.filename: | 253 | if self.args.filename: |
117 | 242 | self._still_helper(self.args.filename, self._width, self._height, | 254 | self._still_helper(self.args.filename, self._width, self._height, |
119 | 243 | self.args.quiet) | 255 | self.args.quiet) |
120 | 244 | else: | 256 | else: |
121 | 245 | with NamedTemporaryFile(prefix='camera_test_', suffix='.jpg') as f: | 257 | with NamedTemporaryFile(prefix='camera_test_', suffix='.jpg') as f: |
122 | 246 | self._still_helper(f.name, self._width, self._height, | 258 | self._still_helper(f.name, self._width, self._height, |
124 | 247 | self.args.quiet) | 259 | self.args.quiet) |
125 | 248 | 260 | ||
126 | 249 | def _still_helper(self, filename, width, height, quiet, pixelformat=None): | 261 | def _still_helper(self, filename, width, height, quiet, pixelformat=None): |
127 | 250 | """ | 262 | """ |
128 | @@ -253,25 +265,30 @@ | |||
129 | 253 | user (quiet = True means do not display image). | 265 | user (quiet = True means do not display image). |
130 | 254 | """ | 266 | """ |
131 | 255 | command = ["fswebcam", "-S 3", "--no-banner", | 267 | command = ["fswebcam", "-S 3", "--no-banner", |
135 | 256 | "-d", self.args.device, | 268 | "-d", self.args.device, |
136 | 257 | "-r", "%dx%d" | 269 | "-r", "%dx%d" |
137 | 258 | % (width, height), filename] | 270 | % (width, height), filename] |
138 | 271 | use_gstreamer = False | ||
139 | 259 | if pixelformat: | 272 | if pixelformat: |
140 | 260 | command.extend(["-p", pixelformat]) | 273 | command.extend(["-p", pixelformat]) |
141 | 261 | 274 | ||
142 | 262 | try: | 275 | try: |
143 | 263 | check_call(command, stdout=open(os.devnull, 'w'), stderr=STDOUT) | 276 | check_call(command, stdout=open(os.devnull, 'w'), stderr=STDOUT) |
144 | 264 | except (CalledProcessError, OSError): | 277 | except (CalledProcessError, OSError): |
155 | 265 | pipespec = \ | 278 | use_gstreamer = True |
156 | 266 | ("v4l2src device=%(device)s " | 279 | |
157 | 267 | "! video/x-raw-yuv,width=%(width)d,height=%(height)d " | 280 | if use_gstreamer: |
158 | 268 | "! ffmpegcolorspace " | 281 | pipespec = ("v4l2src device=%(device)s " |
159 | 269 | "! jpegenc " | 282 | "! %(type)s,width=%(width)d,height=%(height)d " |
160 | 270 | "! filesink location=%(filename)s" | 283 | "! %(plugin)s " |
161 | 271 | % {'device': self.args.device, | 284 | "! jpegenc " |
162 | 272 | 'width': width, | 285 | "! filesink location=%(filename)s" |
163 | 273 | 'height': height, | 286 | % {'device': self.args.device, |
164 | 274 | 'filename': filename}) | 287 | 'type': self._gst_video_type, |
165 | 288 | 'width': width, | ||
166 | 289 | 'height': height, | ||
167 | 290 | 'plugin': self._gst_plugin, | ||
168 | 291 | 'filename': filename}) | ||
169 | 275 | self._pipeline = Gst.parse_launch(pipespec) | 292 | self._pipeline = Gst.parse_launch(pipespec) |
170 | 276 | self._pipeline.set_state(Gst.State.PLAYING) | 293 | self._pipeline.set_state(Gst.State.PLAYING) |
171 | 277 | time.sleep(3) | 294 | time.sleep(3) |
172 | @@ -290,7 +307,7 @@ | |||
173 | 290 | ret = "" | 307 | ret = "" |
174 | 291 | for resolution in supported_resolutions: | 308 | for resolution in supported_resolutions: |
175 | 292 | ret += "Format: %s (%s)\n" % (resolution['pixelformat'], | 309 | ret += "Format: %s (%s)\n" % (resolution['pixelformat'], |
177 | 293 | resolution['description']) | 310 | resolution['description']) |
178 | 294 | ret += "Resolutions: " | 311 | ret += "Resolutions: " |
179 | 295 | for res in resolution['resolutions']: | 312 | for res in resolution['resolutions']: |
180 | 296 | ret += "%sx%s," % (res[0], res[1]) | 313 | ret += "%sx%s," % (res[0], res[1]) |
181 | @@ -314,13 +331,13 @@ | |||
182 | 314 | resolution = resolutions[0] | 331 | resolution = resolutions[0] |
183 | 315 | if resolution: | 332 | if resolution: |
184 | 316 | print("Taking multiple images using the %s format" | 333 | print("Taking multiple images using the %s format" |
186 | 317 | % resolution['pixelformat']) | 334 | % resolution['pixelformat']) |
187 | 318 | for res in resolution['resolutions']: | 335 | for res in resolution['resolutions']: |
188 | 319 | w = res[0] | 336 | w = res[0] |
189 | 320 | h = res[1] | 337 | h = res[1] |
190 | 321 | f = NamedTemporaryFile(prefix='camera_test_%s%sx%s' % | 338 | f = NamedTemporaryFile(prefix='camera_test_%s%sx%s' % |
193 | 322 | (resolution['pixelformat'], w, h), suffix='.jpg', | 339 | (resolution['pixelformat'], w, h), |
194 | 323 | delete=False) | 340 | suffix='.jpg', delete=False) |
195 | 324 | print("Taking a picture at %sx%s" % (w, h)) | 341 | print("Taking a picture at %sx%s" % (w, h)) |
196 | 325 | self._still_helper(f.name, w, h, True, | 342 | self._still_helper(f.name, w, h, True, |
197 | 326 | pixelformat=resolution['pixelformat']) | 343 | pixelformat=resolution['pixelformat']) |
198 | @@ -329,7 +346,7 @@ | |||
199 | 329 | os.remove(f.name) | 346 | os.remove(f.name) |
200 | 330 | else: | 347 | else: |
201 | 331 | print("Failed to validate image %s" % f.name, | 348 | print("Failed to validate image %s" % f.name, |
203 | 332 | file=sys.stderr) | 349 | file=sys.stderr) |
204 | 333 | os.remove(f.name) | 350 | os.remove(f.name) |
205 | 334 | return 1 | 351 | return 1 |
206 | 335 | return 0 | 352 | return 0 |
207 | @@ -354,9 +371,9 @@ | |||
208 | 354 | pixelformat['pixelformat_int'] = fmt.pixelformat | 371 | pixelformat['pixelformat_int'] = fmt.pixelformat |
209 | 355 | pixelformat['pixelformat'] = "%s%s%s%s" % \ | 372 | pixelformat['pixelformat'] = "%s%s%s%s" % \ |
210 | 356 | (chr(fmt.pixelformat & 0xFF), | 373 | (chr(fmt.pixelformat & 0xFF), |
214 | 357 | chr((fmt.pixelformat >> 8) & 0xFF), | 374 | chr((fmt.pixelformat >> 8) & 0xFF), |
215 | 358 | chr((fmt.pixelformat >> 16) & 0xFF), | 375 | chr((fmt.pixelformat >> 16) & 0xFF), |
216 | 359 | chr((fmt.pixelformat >> 24) & 0xFF)) | 376 | chr((fmt.pixelformat >> 24) & 0xFF)) |
217 | 360 | pixelformat['description'] = fmt.description.decode() | 377 | pixelformat['description'] = fmt.description.decode() |
218 | 361 | supported_formats.append(pixelformat) | 378 | supported_formats.append(pixelformat) |
219 | 362 | fmt.index = fmt.index + 1 | 379 | fmt.index = fmt.index + 1 |
220 | @@ -404,16 +421,18 @@ | |||
221 | 404 | framesize) == 0: | 421 | framesize) == 0: |
222 | 405 | if framesize.type == V4L2_FRMSIZE_TYPE_DISCRETE: | 422 | if framesize.type == V4L2_FRMSIZE_TYPE_DISCRETE: |
223 | 406 | resolutions.append([framesize.discrete.width, | 423 | resolutions.append([framesize.discrete.width, |
225 | 407 | framesize.discrete.height]) | 424 | framesize.discrete.height]) |
226 | 408 | # for continuous and stepwise, let's just use min and | 425 | # for continuous and stepwise, let's just use min and |
227 | 409 | # max they use the same structure and only return | 426 | # max they use the same structure and only return |
228 | 410 | # one result | 427 | # one result |
229 | 411 | elif framesize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS or\ | 428 | elif framesize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS or\ |
231 | 412 | framesize.type == V4L2_FRMSIZE_TYPE_STEPWISE: | 429 | framesize.type == V4L2_FRMSIZE_TYPE_STEPWISE: |
232 | 413 | resolutions.append([framesize.stepwise.min_width, | 430 | resolutions.append([framesize.stepwise.min_width, |
234 | 414 | framesize.stepwise.min_height]) | 431 | framesize.stepwise.min_height] |
235 | 432 | ) | ||
236 | 415 | resolutions.append([framesize.stepwise.max_width, | 433 | resolutions.append([framesize.stepwise.max_width, |
238 | 416 | framesize.stepwise.max_height]) | 434 | framesize.stepwise.max_height] |
239 | 435 | ) | ||
240 | 417 | break | 436 | break |
241 | 418 | framesize.index = framesize.index + 1 | 437 | framesize.index = framesize.index + 1 |
242 | 419 | except IOError as e: | 438 | except IOError as e: |
243 | @@ -455,11 +474,11 @@ | |||
244 | 455 | 474 | ||
245 | 456 | if outw != width: | 475 | if outw != width: |
246 | 457 | print("Image width does not match, was %s should be %s" % | 476 | print("Image width does not match, was %s should be %s" % |
248 | 458 | (outw, width), file=sys.stderr) | 477 | (outw, width), file=sys.stderr) |
249 | 459 | return False | 478 | return False |
250 | 460 | if outh != height: | 479 | if outh != height: |
251 | 461 | print("Image width does not match, was %s should be %s" % | 480 | print("Image width does not match, was %s should be %s" % |
253 | 462 | (outh, height), file=sys.stderr) | 481 | (outh, height), file=sys.stderr) |
254 | 463 | return False | 482 | return False |
255 | 464 | 483 | ||
256 | 465 | return True | 484 | return True |
257 | @@ -519,10 +538,21 @@ | |||
258 | 519 | if __name__ == "__main__": | 538 | if __name__ == "__main__": |
259 | 520 | args = parse_arguments(sys.argv[1:]) | 539 | args = parse_arguments(sys.argv[1:]) |
260 | 521 | 540 | ||
261 | 541 | if not args.test: | ||
262 | 542 | args.test = 'detect' | ||
263 | 543 | |||
264 | 522 | # Import Gst only for the test cases that will need it | 544 | # Import Gst only for the test cases that will need it |
265 | 523 | if args.test in ['display', 'still', 'led', 'resolutions']: | 545 | if args.test in ['display', 'still', 'led', 'resolutions']: |
266 | 524 | from gi.repository import Gst | 546 | from gi.repository import Gst |
267 | 547 | if Gst.version()[0] > 0: | ||
268 | 548 | gst_plugin = 'videoconvert' | ||
269 | 549 | gst_video_type = 'video/x-raw' | ||
270 | 550 | else: | ||
271 | 551 | gst_plugin = 'ffmpegcolorspace' | ||
272 | 552 | gst_video_type = 'video/x-raw-yuv' | ||
273 | 525 | Gst.init(None) | 553 | Gst.init(None) |
274 | 554 | camera = CameraTest(args, gst_plugin, gst_video_type) | ||
275 | 555 | else: | ||
276 | 556 | camera = CameraTest(args) | ||
277 | 526 | 557 | ||
278 | 527 | camera = CameraTest(args) | ||
279 | 528 | sys.exit(getattr(camera, args.test)()) | 558 | sys.exit(getattr(camera, args.test)()) |
There seem to be some pep8 errors in your code related to spacing between ',' mainly. I can't remember for certain but I think this may cause Tarmac to reject the submission. Also I'm curious about the comment about fswebcam above - I have fswebcam installed on my system and still get this error. Is there a further problem in the script related to that? Probably best to check it out now.