1
2
3
4
5
6
7 __license__ = "GPL v2 or later"
8 __author__ = """Sebastian Hilbert <Sebastian.Hilbert@gmx.net>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"""
9
10
11
12 import sys
13 import os.path
14 import os
15 import time
16 import shutil
17 import io
18 import glob
19 import logging
20
21
22
23
24 if __name__ == '__main__':
25 sys.path.insert(0, '../../')
26 from Gnumed.pycommon import gmShellAPI
27 from Gnumed.pycommon import gmTools
28 from Gnumed.pycommon import gmI18N
29 from Gnumed.pycommon import gmLog2
30
31
32 _log = logging.getLogger('gm.scanning')
33
34 _twain_module = None
35 _sane_module = None
36
37 use_XSane = True
38
39
40
51
53
54
55
56
57
58 - def __init__(self, calling_window=None):
59 _twain_import_module()
60
61 self.__calling_window = calling_window
62 self.__src_manager = None
63 self.__scanner = None
64 self.__done_transferring_image = False
65
66 self.__register_event_handlers()
67
68
69
70 - def acquire_pages_into_files(self, delay=None, filename=None):
71 if filename is None:
72 filename = gmTools.get_unique_filename(prefix = 'gmScannedObj-', suffix = '.bmp')
73 else:
74 tmp, ext = os.path.splitext(filename)
75 if ext != '.bmp':
76 filename = filename + '.bmp'
77
78 self.__filename = os.path.abspath(os.path.expanduser(filename))
79
80 if not self.__init_scanner():
81 raise OSError(-1, 'cannot init TWAIN scanner device')
82
83 self.__done_transferring_image = False
84 self.__scanner.RequestAcquire(True)
85
86 return [self.__filename]
87
89 return self.__done_transferring_image
90
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 return
109
110
111
113 if self.__scanner is not None:
114 return True
115
116 self.__init_src_manager()
117 if self.__src_manager is None:
118 return False
119
120
121 self.__src_manager.SetCallback(self._twain_event_callback)
122
123
124 try:
125 self.__scanner = self.__src_manager.OpenSource()
126 except _twain_module.excDSOpenFailed:
127 _log.exception('cannot open TWAIN data source (image capture device)')
128 gmLog2.log_stack_trace()
129 return False
130
131 if self.__scanner is None:
132 _log.error("user canceled scan source selection dialog")
133 return False
134
135 _log.info("TWAIN data source: %s" % self.__scanner.GetSourceName())
136 _log.debug("TWAIN data source config: %s" % str(self.__scanner.GetIdentity()))
137
138 return True
139
141
142 if self.__src_manager is not None:
143 return
144
145
146
147
148
149
150
151
152
153
154
155
156 try:
157 self.__src_manager = _twain_module.SourceManager(self.__calling_window.GetHandle())
158
159 except _twain_module.excSMLoadFileFailed:
160 _log.exception('failed to load TWAIN_32.DLL')
161 return
162
163 except _twain_module.excSMGetProcAddressFailed:
164 _log.exception('failed to jump into TWAIN_32.DLL')
165 return
166
167 except _twain_module.excSMOpenFailed:
168 _log.exception('failed to open Source Manager')
169 return
170
171 _log.info("TWAIN source manager config: %s" % str(self.__src_manager.GetIdentity()))
172
173
174
176 self.__twain_event_handlers = {
177 _twain_module.MSG_XFERREADY: self._twain_handle_transfer_in_memory,
178 _twain_module.MSG_CLOSEDSREQ: self._twain_close_datasource,
179 _twain_module.MSG_CLOSEDSOK: self._twain_save_state,
180 _twain_module.MSG_DEVICEEVENT: self._twain_handle_src_event
181 }
182
184 _log.debug('notification of TWAIN event <%s>' % str(twain_event))
185 self.__twain_event_handlers[twain_event]()
186 self.__scanner = None
187 return
188
190 _log.info("being asked to close data source")
191
193 _log.info("being asked to save application state")
194
196 _log.info("being asked to handle device specific event")
197
199
200
201
202 _log.debug('receiving image from TWAIN source')
203 _log.debug('image info: %s' % self.__scanner.GetImageInfo())
204 _log.debug('image layout: %s' % str(self.__scanner.GetImageLayout()))
205
206
207 (external_data_handle, more_images_pending) = self.__scanner.XferImageNatively()
208 try:
209
210 _twain_module.DIBToBMFile(external_data_handle, self.__filename)
211 finally:
212 _twain_module.GlobalHandleFree(external_data_handle)
213 _log.debug('%s pending images' % more_images_pending)
214
215
216
217
218
219 self.__done_transferring_image = True
220
222
223
224
225
226
227 _log.debug('receiving image from TWAIN source')
228 _log.debug('image info: %s' % self.__scanner.GetImageInfo())
229 _log.debug('image layout: %s' % self.__scanner.GetImageLayout())
230
231 self.__scanner.SetXferFileName(self.__filename)
232
233 more_images_pending = self.__scanner.XferImageByFile()
234 _log.debug('%s pending images' % more_images_pending)
235
236
237 self.__scanner.HideUI()
238
239
240 return
241
242
243
256
258
259
260
261 _src_manager = None
262
264 _sane_import_module()
265
266
267
268
269
270
271
272
273 self.__device = device
274 _log.info('using SANE device [%s]' % self.__device)
275
276 self.__init_scanner()
277
279 self.__scanner = _sane_module.open(self.__device)
280
281 _log.debug('opened SANE device: %s' % str(self.__scanner))
282 _log.debug('SANE device config: %s' % str(self.__scanner.get_parameters()))
283 _log.debug('SANE device opts : %s' % str(self.__scanner.optlist))
284 _log.debug('SANE device opts : %s' % str(self.__scanner.get_options()))
285
286 return True
287
289 self.__scanner.close()
290
291 - def acquire_pages_into_files(self, delay=None, filename=None):
292 if filename is None:
293 filename = gmTools.get_unique_filename(prefix='gmScannedObj-', suffix='.bmp')
294 else:
295 tmp, ext = os.path.splitext(filename)
296 if ext != '.bmp':
297 filename = filename + '.bmp'
298
299 filename = os.path.abspath(os.path.expanduser(filename))
300
301 if delay is not None:
302 time.sleep(delay)
303 _log.debug('some sane backends report device_busy if we advance too fast. delay set to %s sec' % delay)
304
305 _log.debug('Trying to get image from scanner into [%s] !' % filename)
306 self.__scanner.start()
307 img = self.__scanner.snap()
308 img.save(filename)
309
310 return [filename]
311
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
339
340 _FILETYPE = '.png'
341
342
344
345
346 self._stock_xsanerc = os.path.expanduser(os.path.join('~', '.sane', 'xsane', 'xsane.rc'))
347 try:
348 open(self._stock_xsanerc, 'r').close()
349 except IOError:
350 msg = (
351 'XSane not properly installed for this user:\n\n'
352 ' [%s] not found\n\n'
353 'Start XSane once before using it with GNUmed.'
354 ) % self._stock_xsanerc
355 raise ImportError(msg)
356
357
358
359 self._gm_custom_xsanerc = os.path.expanduser(os.path.join('~', '.gnumed', 'gm-xsanerc.conf'))
360 try:
361 open(self._gm_custom_xsanerc, 'r+b').close()
362 except IOError:
363 _log.info('creating [%s] from [%s]', self._gm_custom_xsanerc, self._stock_xsanerc)
364 shutil.copyfile(self._stock_xsanerc, self._gm_custom_xsanerc)
365
366 self.device_settings_file = None
367 self.default_device = None
368
371
372 - def acquire_pages_into_files(self, delay=None, filename=None):
373 """Call XSane.
374
375 <filename> name part must have format name-001.ext>
376 """
377 if filename is None:
378 filename = gmTools.get_unique_filename(prefix = 'gm-scan-')
379
380 name, ext = os.path.splitext(filename)
381 filename = '%s-001%s' % (name, cXSaneScanner._FILETYPE)
382 filename = os.path.abspath(os.path.expanduser(filename))
383
384 cmd = 'xsane --no-mode-selection --save --force-filename "%s" --xsane-rc "%s" %s %s' % (
385 filename,
386 self.__get_session_xsanerc(),
387 gmTools.coalesce(self.device_settings_file, '', '--device-settings %s'),
388 gmTools.coalesce(self.default_device, '')
389 )
390 normal_exit = gmShellAPI.run_command_in_shell(command = cmd, blocking = True)
391
392 if normal_exit:
393 flist = glob.glob(filename.replace('001', '*'))
394 flist.sort()
395 return flist
396
397 raise OSError(-1, 'error running XSane as [%s]' % cmd)
398
401
402
403
405
406
407 session_xsanerc = gmTools.get_unique_filename (
408 prefix = 'gm-session_xsanerc-',
409 suffix = '.conf'
410 )
411 _log.debug('GNUmed -> XSane session xsanerc: %s', session_xsanerc)
412
413
414 enc = gmI18N.get_encoding()
415 fread = io.open(self._gm_custom_xsanerc, mode = "rt", encoding = enc)
416 fwrite = io.open(session_xsanerc, mode = "wt", encoding = enc)
417
418 paths = gmTools.gmPaths()
419 val_dict = {
420 'tmp-path': paths.tmp_dir,
421 'working-directory': paths.tmp_dir,
422 'filename': '<--force-filename>',
423 'filetype': cXSaneScanner._FILETYPE,
424 'skip-existing-numbers': '1',
425 'filename-counter-step': '1',
426 'filename-counter-len': '3'
427 }
428
429 for idx, line in enumerate(fread):
430 line = line.replace('\n', '')
431 line = line.replace('\r', '')
432
433 if idx % 2 == 0:
434 curr_key = line.strip('"')
435 fwrite.write('"%s"\n' % curr_key)
436 else:
437 try:
438 value = val_dict[curr_key]
439 _log.debug('replaced [%s] with [%s]', curr_key, val_dict[curr_key])
440 except KeyError:
441 value = line
442 fwrite.write('%s\n' % value)
443
444 fwrite.flush()
445 fwrite.close()
446 fread.close()
447
448 return session_xsanerc
449
451 try:
452 _twain_import_module()
453
454
455 return None
456 except ImportError:
457 pass
458
459 if use_XSane:
460
461 return None
462
463 _sane_import_module()
464 return _sane_module.get_devices()
465
466 -def acquire_pages_into_files(device=None, delay=None, filename=None, calling_window=None, xsane_device_settings=None):
467 """Connect to a scanner and return the scanned pages as a file list.
468
469 returns:
470 - list of filenames: names of scanned pages, may be []
471 - None: unable to connect to scanner
472 """
473 try:
474 scanner = cTwainScanner(calling_window=calling_window)
475 _log.debug('using TWAIN')
476 except ImportError:
477 if use_XSane:
478 _log.debug('using XSane')
479 scanner = cXSaneScanner()
480 scanner.device_settings_file = xsane_device_settings
481 scanner.default_device = device
482 else:
483 _log.debug('using SANE directly')
484 scanner = cSaneScanner(device=device)
485
486 _log.debug('requested filename: [%s]' % filename)
487 fnames = scanner.acquire_pages_into_files(filename=filename, delay=delay)
488 scanner.close()
489 _log.debug('acquired pages into files: %s' % str(fnames))
490
491 return fnames
492
493
494
495 if __name__ == '__main__':
496
497 if len(sys.argv) > 1 and sys.argv[1] == 'test':
498
499 logging.basicConfig(level=logging.DEBUG)
500
501 print("devices:")
502 print(get_devices())
503
504 sys.exit()
505
506 setups = [
507 {'dev': 'test:0', 'file': 'x1-test0-1-0001'},
508 {'dev': 'test:1', 'file': 'x2-test1-1-0001.bmp'},
509 {'dev': 'test:0', 'file': 'x3-test0-2-0001.bmp-ccc'}
510 ]
511
512 idx = 1
513 for setup in setups:
514 print("scanning page #%s from device [%s]" % (idx, setup['dev']))
515 idx += 1
516 fnames = acquire_pages_into_files(device = setup['dev'], filename = setup['file'], delay = (idx*5))
517 if fnames is False:
518 print("error, cannot acquire page")
519 else:
520 print(" image files:", fnames)
521