Replace 'tap' to 'spaces' to make gbs build succeed
[platform/upstream/hplip.git] / scan.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # (c) Copyright 2003-2011 Hewlett-Packard Development Company, L.P.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 #
20 # Author: Don Welch
21 # Contributors: Sarbeswar Meher
22 #
23
24 from __future__ import division
25
26 __version__ = '2.2'
27 __mod__ = 'hp-scan'
28 __title__ = 'Scan Utility'
29 __doc__ = "SANE-based scan utility for HPLIP supported all-in-one/mfp devices."
30
31 # Std Lib
32 import sys
33 import os
34 import os.path
35 import getopt
36 import signal
37 import time
38 import socket
39 import operator
40
41 # Local
42 from base.g import *
43 from base import tui, device, module, utils
44 from prnt import cups
45
46
47 username = prop.username
48 r = res = 300
49 scan_mode = 'gray'
50 tlx = None
51 tly = None
52 brx = None
53 bry = None
54 units = "mm"
55 output = ''
56 dest = []
57 email_from = ''
58 email_to = []
59 email_subject = 'hp-scan from %s' % socket.gethostname()
60 email_note = ''
61 resize = 100
62 contrast = 0
63 brightness = 0
64 page_size = ''
65 size_desc = ''
66 page_units = 'mm'
67 default_res = 300
68 scanner_compression = 'JPEG'
69 adf = False
70
71 PAGE_SIZES = { # in mm
72     '5x7' : (127, 178, "5x7 photo", 'in'),
73     '4x6' : (102, 152, "4x6 photo", 'in'),
74     '3x5' : (76, 127, "3x5 index card", 'in'),
75     'a2_env' : (111, 146, "A2 Envelope", 'in'),
76     'a3' : (297, 420, "A3", 'mm'),
77     "a4" : (210, 297, "A4", 'mm'),
78     "a5" : (148, 210, "A5", 'mm'),
79     "a6" : (105, 148, "A6", 'mm'),
80     "b4" : (257, 364, "B4", 'mm'),
81     "b5" : (182, 257, "B5", 'mm'),
82     "c6_env" : (114, 162, "C6 Envelope", 'in'),
83     "dl_env" : (110, 220, "DL Envelope", 'in'),
84     "exec" : (184, 267, "Executive", 'in'),
85     "flsa" : (216, 330, "Flsa", 'mm'),
86     "higaki" : (100, 148, "Hagaki", 'mm'),
87     "japan_env_3" : (120, 235, "Japanese Envelope #3", 'mm'),
88     "japan_env_4" : (90, 205, "Japanese Envelope #4", 'mm'),
89     "legal" : (215, 356, "Legal", 'in'),
90     "letter" : (215, 279, "Letter", 'in'),
91     "no_10_env" : (105, 241, "Number 10 Envelope", 'in'),
92     "oufufu-hagaki" : (148, 200, "Oufuku-Hagaki", 'mm'),
93     "photo" : (102, 152, "Photo", 'in'),
94     "super_b" : (330, 483, "Super B", 'in'),
95     }
96
97
98 try:
99     viewer = ''
100     viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',]
101     for v in viewer_list:
102         vv = utils.which(v)
103         if vv:
104             viewer = os.path.join(vv, v)
105             break
106
107
108     editor = ''
109     editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',]
110     for e in editor_list:
111         ee = utils.which(e)
112         if ee:
113             editor = os.path.join(ee, e)
114             break
115
116     pdf_viewer = ''
117     pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',]
118     for v in pdf_viewer_list:
119         vv = utils.which(v)
120         if vv:
121             pdf_viewer = os.path.join(vv, v)
122             break
123
124     mod = module.Module(__mod__, __title__, __version__, __doc__, None,
125                         (INTERACTIVE_MODE,))
126
127     mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS,
128         extra_options=[utils.USAGE_SPACE,
129         ("[OPTIONS] (General)", "", "header", False),
130         ("Scan destinations:", "-s<dest_list> or --dest=<dest_list>", "option", False),
131         ("", "where <dest_list> is a comma separated list containing one or more of: 'file'\*, ", "option", False),
132         ("", "'viewer', 'editor', 'pdf', or 'print'. Use only commas between values, no spaces.", "option", False),
133         ("Scan mode:", "-m<mode> or --mode=<mode>. Where <mode> is 'gray'\*, 'color' or 'lineart'.", "option", False),
134         ("Scanning resolution:", "-r<resolution_in_dpi> or --res=<resolution_in_dpi> or --resolution=<resolution_in_dpi>", "option", False),
135         ("", "where 300 is default.", "option", False),
136         ("Image resize:", "--resize=<scale_in_%> (min=1%, max=400%, default=100%)", "option", False),
137         ("Image contrast:", "--contrast=<contrast>", "option", False),
138         ("ADF mode:", "--adf (Note, only PDF output is supported when using the ADF)", "option", False),
139         utils.USAGE_SPACE,
140         ("[OPTIONS] (Scan area)", "", "header", False),
141         ("Specify the units for area/box measurements:", "-t<units> or --units=<units>", "option", False),
142         ("", "where <units> is 'mm'\*, 'cm', 'in', 'px', or 'pt' ('mm' is default).", "option", False),
143         ("Scan area:", "-a<tlx>,<tly>,<brx>,<bry> or --area=<tlx>,<tly>,<brx>,<bry>", "option", False),
144         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
145         ("", "Units for tlx, tly, brx, and bry are specified by -t/--units (default is 'mm').", "option", False),
146         ("", "Use only commas between values, no spaces.", "option", False),
147         ("Scan box:", "--box=<tlx>,<tly>,<width>,<height>", "option", False),
148         ("", "tlx and tly coordinates are relative to the upper left corner of the scan area.", "option", False),
149         ("", "Units for tlx, tly, width, and height are specified by -t/--units (default is 'mm').", "option", False),
150         ("", "Use only commas between values, no spaces.", "option", False),
151         ("Top left x of the scan area:", "--tlx=<tlx>", "option", False),
152         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
153         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
154         ("Top left y of the scan area:", "--tly=<tly>", "option", False),
155         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
156         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
157         ("Bottom right x of the scan area:", "--brx=<brx>", "option", False),
158         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
159         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
160         ("Bottom right y   of the scan area:", "--bry=<bry>", "option", False),
161         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
162         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
163         ("Specify the scan area based on a paper size:", "--size=<paper size name>", "option", False),
164         ("", "where <paper size name> is one of: %s" % ', '.join(PAGE_SIZES.keys()), "option", False),
165         utils.USAGE_SPACE,
166         ("[OPTIONS] ('file' dest)", "", "header", False),
167         ("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False),
168         utils.USAGE_SPACE,
169         ("[OPTIONS] ('pdf' dest)", "", "header", False),
170         ("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False),
171         utils.USAGE_SPACE,
172         ("[OPTIONS] ('viewer' dest)", "", "header", False),
173         ("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False),
174         utils.USAGE_SPACE,
175         ("[OPTIONS] ('editor' dest)", "", "header", False),
176         ("Image editor application:", "-e<editor> or --editor=<editor>", "option", False),
177         utils.USAGE_SPACE,
178         ("[OPTIONS] ('email' dest)", "", "header", False),
179         ("From: address for 'email' dest:", "--email-from=<email_from_address> (required for 'email' dest.)", "option", False),
180         ("To: address for 'email' dest:", "--email-to=<email__to_address> (required for 'email' dest.)", "option", False),
181         ("Email subject for 'email' dest:", '--email-subject="<subject>" or --subject="<subject>"', "option", False),
182         ("", 'Use double quotes (") around the subject if it contains space characters.', "option", False),
183         ("Note or message for the 'email' dest:", '--email-msg="<msg>" or --email-note="<note>"', "option", False),
184         ("", 'Use double quotes (") around the note/message if it contains space characters.', "option", False),
185         utils.USAGE_SPACE,
186         ("[OPTIONS] ('printer' dest)", "", "header", False),
187         ("Printer queue/printer:", "--printer=<printer_name>", "option", False),
188         utils.USAGE_SPACE,
189         ("[OPTIONS] (advanced)", "", "header", False),
190         ("Set the scanner compression mode:", "-x<mode> or --compression=<mode>, <mode>='raw', 'none' or 'jpeg' ('jpeg' is default) ('raw' and 'none' are equivalent)", "option", False),],
191         see_also_list=[])
192
193     opts, device_uri, printer_name, mode, ui_toolkit, lang = \
194         mod.parseStdOpts('s:m:r:c:t:a:b:o:v:f:c:x:e:',
195                          ['dest=', 'mode=', 'res=', 'resolution=',
196                           'resize=', 'contrast=', 'adf', 'unit=',
197                           'units=', 'area=', 'box=', 'tlx=',
198                           'tly=', 'brx=', 'bry=', 'size=',
199                           'file=', 'output=', 'pdf=', 'viewer=',
200                           'email-from=', 'from=', 'email-to=',
201                           'to=', 'email-msg=', 'msg=',
202                           'printer=', 'compression=' , 'raw',
203                           'jpeg', 'color', 'lineart', 'colour',
204                           'bw', 'gray', 'grayscale', 'grey',
205                           'greyscale', 'email-subject=',
206                           'subject=', 'to=', 'from=', 'jpg',
207                           'grey-scale', 'gray-scale', 'about=',
208                           'editor='
209                          ])
210
211
212
213     device_uri = mod.getDeviceUri(device_uri, printer_name,
214         back_end_filter=['hpaio'], filter={'scan-type': (operator.gt, 0)})
215
216     for o, a in opts:
217         if o in ('-x', '--compression'):
218             a = a.strip().lower()
219
220             if a in ('jpeg', 'jpg'):
221                 scanner_compression = 'JPEG'
222
223             elif a in ('raw', 'none'):
224                 scanner_compression = 'None'
225
226             else:
227                 log.error("Invalid compression value. Valid values are 'jpeg', 'raw', and 'none'.")
228                 log.error("Using default value of 'jpeg'.")
229                 scanner_compression = 'JPEG'
230
231         elif o == 'raw':
232             scanner_compression = 'None'
233
234         elif o == 'jpeg':
235             scanner_compression = 'JPEG'
236
237         elif o in ('--color', '--colour'):
238             scan_mode = 'color'
239
240         elif o in ('--lineart', '--line-art', '--bw'):
241             scan_mode = 'lineart'
242
243         elif o in ('--gray', '--grayscale', '--gray-scale', '--grey', '--greyscale', '--grey-scale'):
244             scan_mode = 'gray'
245
246         elif o in ('-m', '--mode'):
247             a = a.strip().lower()
248
249             if a in ('color', 'colour'):
250                 scan_mode = 'color'
251
252             elif a in ('lineart', 'bw', 'b&w'):
253                 scan_mode = 'lineart'
254
255             elif a in ('gray', 'grayscale', 'grey', 'greyscale'):
256                 scan_mode = 'gray'
257
258             else:
259                 log.error("Invalid mode. Using default of 'gray'.")
260                 log.error("Valid modes are 'color', 'lineart', or 'gray'.")
261                 scan_mode = 'gray'
262
263         elif o in ('--res', '--resolution', '-r'):
264             try:
265                 r = int(a.strip())
266             except ValueError:
267                 log.error("Invalid value for resolution.")
268                 res = default_res
269             else:
270                 res = r
271
272         elif o in ('-t', '--units', '--unit'):
273             a = a.strip().lower()
274
275             if a in ('in', 'inch', 'inches'):
276                 units = 'in'
277
278             elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'):
279                 units = 'mm'
280
281             elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'):
282                 units = 'cm'
283
284             elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'):
285                 units = 'px'
286
287             elif a in ('pt', 'point', 'points', 'pts'):
288                 units = 'pt'
289
290             else:
291                 log.error("Invalid units. Using default of 'mm'.")
292                 units = 'mm'
293
294         elif o == '--tlx':
295             a = a.strip().lower()
296             try:
297                 f = float(a)
298             except ValueError:
299                 log.error("Invalid value for tlx.")
300             else:
301                 tlx = f
302
303         elif o == '--tly':
304             a = a.strip().lower()
305             try:
306                 f = float(a)
307             except ValueError:
308                 log.error("Invalid value for tly.")
309             else:
310                 tly = f
311
312         elif o == '--brx':
313             a = a.strip().lower()
314             try:
315                 f = float(a)
316             except ValueError:
317                 log.error("Invalid value for brx.")
318             else:
319                 brx = f
320
321         elif o == '--bry':
322             a = a.strip().lower()
323             try:
324                 f = float(a)
325             except ValueError:
326                 log.error("Invalid value for bry.")
327             else:
328                 bry = f
329
330         elif o in ('-a', '--area'): # tlx, tly, brx, bry
331             a = a.strip().lower()
332             try:
333                 tlx, tly, brx, bry = a.split(',')[:4]
334             except ValueError:
335                 log.error("Invalid scan area. Using defaults.")
336             else:
337                 try:
338                     tlx = float(tlx)
339                 except ValueError:
340                     log.error("Invalid value for tlx. Using defaults.")
341                     tlx = None
342
343                 try:
344                     tly = float(tly)
345                 except ValueError:
346                     log.error("Invalid value for tly. Using defaults.")
347                     tly = None
348
349                 try:
350                     brx = float(brx)
351                 except ValueError:
352                     log.error("Invalid value for brx. Using defaults.")
353                     brx = None
354
355                 try:
356                     bry = float(bry)
357                 except ValueError:
358                     log.error("Invalid value for bry. Using defaults.")
359                     bry = None
360
361         elif o in ('-b', '--box'): # tlx, tly, w, h
362             a = a.strip().lower()
363             try:
364                 tlx, tly, width, height = a.split(',')[:4]
365             except ValueError:
366                 log.error("Invalid scan area. Using defaults.")
367             else:
368                 try:
369                     tlx = float(tlx)
370                 except ValueError:
371                     log.error("Invalid value for tlx. Using defaults.")
372                     tlx = None
373
374                 try:
375                     tly = float(tly)
376                 except ValueError:
377                     log.error("Invalid value for tly. Using defaults.")
378                     tly = None
379
380                 if tlx is not None:
381                     try:
382                         brx = float(width) + tlx
383                     except ValueError:
384                         log.error("Invalid value for width. Using defaults.")
385                         brx = None
386                 else:
387                     log.error("Cannot calculate brx since tlx is invalid. Using defaults.")
388                     brx = None
389
390                 if tly is not None:
391                     try:
392                         bry = float(height) + tly
393                     except ValueError:
394                         log.error("Invalid value for height. Using defaults.")
395                         bry = None
396                 else:
397                     log.error("Cannot calculate bry since tly is invalid. Using defaults.")
398                     bry = None
399
400         elif o == '--size':
401             size = a.strip().lower()
402             if size in PAGE_SIZES:
403                 brx, bry, size_desc, page_units = PAGE_SIZES[size]
404                 tlx, tly = 0, 0
405                 page_size = size
406             else:
407                 log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(PAGE_SIZES.keys()))
408                 log.error("Using defaults.")
409
410         elif o in ('-o', '--output', '-f', '--file'):
411             output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip())))
412
413             try:
414                 ext = os.path.splitext(output)[1]
415             except IndexError:
416                 log.error("Invalid filename extension.")
417                 output = ''
418                 if 'file' in dest:
419                     dest.remove('file')
420             else:
421                 if ext.lower() not in ('.jpg', '.png'):
422                     log.error("Only JPG (.jpg) and PNG (.png) output files are supported.")
423                     output = ''
424                     if 'file' in dest:
425                         dest.remove('file')
426                 else:
427                     if os.path.exists(output):
428                         log.warn("Output file '%s' exists. File will be overwritten." % output)
429
430                     if 'file' not in dest:
431                         dest.append('file')
432
433         elif o in ('-s', '--dest', '--destination'):
434             a = a.strip().lower().split(',')
435             for aa in a:
436                 aa = aa.strip()
437                 if aa in ('file', 'viewer', 'editor', 'printer', 'print', 'email', 'pdf') \
438                     and aa not in dest:
439                     if aa == 'print': aa = 'printer'
440                     dest.append(aa)
441
442         elif o in ('-v', '--viewer'):
443             a = a.strip()
444             b = utils.which(a)
445             if not b:
446                 log.error("Viewer application not found.")
447             else:
448                 viewer = os.path.join(b, a)
449                 if 'viewer' not in dest:
450                     dest.append('viewer')
451
452         elif o in ('-e', '--editor'):
453             a = a.strip()
454             b = utils.which(a)
455             if not b:
456                 log.error("Editor application not found.")
457             else:
458                 editor = os.path.join(b, a)
459                 if 'editor' not in dest:
460                     dest.append('editor')
461
462         elif o == '--pdf':
463             a = a.strip()
464             b = utils.which(a)
465             if not b:
466                 log.error("PDF viewer application not found.")
467             else:
468                 pdf_viewer = os.path.join(b, a)
469                 if 'pdf' not in dest:
470                     dest.append('pdf')
471
472
473         elif o in ('--email-to', '--to'):
474             email_to = a.split(',')
475             if 'email' not in dest:
476                 dest.append('email')
477
478         elif o in ('--email-from', '--from'):
479             email_from = a
480             if 'email' not in dest:
481                 dest.append('email')
482
483         elif o in ('--email-subject', '--subject', '--about'):
484             email_subject = a
485             if 'email' not in dest:
486                 dest.append('email')
487
488         elif o in ('--email-note', '--email-msg', '--msg', '--message', '--note', '--notes'):
489             email_note = a
490             if 'email' not in dest:
491                 dest.append('email')
492
493         elif o == '--resize':
494             a = a.replace("%", "")
495             try:
496                 resize = int(a)
497             except ValueError:
498                 resize = 100
499                 log.error("Invalid resize value. Using default of 100%.")
500
501         elif o in ('-b', '--brightness'):
502             pass
503
504         elif o in ('-c', '--contrast'):
505             try:
506                 contrast = int(a.strip())
507             except ValueError:
508                 log.error("Invalid contrast value. Using default of 100.")
509                 contrast = 100
510
511         elif o == '--adf':
512             adf = True
513             output_type = 'pdf'
514
515
516     if printer_name is not None and \
517         device.getDeviceURIByPrinterName(printer_name) is not None and \
518         'printer' not in dest:
519
520         dest.append('printer')
521
522     if not dest:
523         log.warn("No destinations specified. Adding 'file' destination by default.")
524         dest.append('file')
525
526     if 'email' in dest and (not email_from or not email_to):
527         log.error("Email specified, but email to and/or email from address(es) were not specified.")
528         log.error("Disabling 'email' destination.")
529         dest.remove("email")
530
531     if page_size:
532         units = 'mm'
533
534     if units == 'in':
535         if tlx is not None: tlx = tlx * 25.4
536         if tly is not None: tly = tly * 25.4
537         if brx is not None: brx = brx * 25.4
538         if bry is not None: bry = bry * 25.4
539
540     elif units == 'cm':
541         if tlx is not None: tlx = tlx * 10.0
542         if tly is not None: tly = tly * 10.0
543         if brx is not None: brx = brx * 10.0
544         if bry is not None: bry = bry * 10.0
545
546     elif units == 'pt':
547         if tlx is not None: tlx = tlx * 0.3528
548         if tly is not None: tly = tly * 0.3528
549         if brx is not None: brx = brx * 0.3528
550         if bry is not None: bry = bry * 0.3528
551
552     elif units == 'px':
553         log.warn("Units set to pixels. Using resolution of %ddpi for area calculations." % res)
554         if tlx is not None: tlx = tlx / res * 25.4
555         if tly is not None: tly = tly / res * 25.4
556         if brx is not None: brx = brx / res * 25.4
557         if bry is not None: bry = bry / res * 25.4
558
559     if tlx is not None and brx is not None and tlx >= brx:
560         log.error("Invalid values for tlx (%d) and brx (%d) (tlx>=brx). Using defaults." % (tlx, brx))
561         tlx = brx = None
562
563     if tly is not None and bry is not None and tly >= bry:
564         log.error("Invalid values for tly (%d) and bry (%d) (tly>=bry). Using defaults." % (tly, bry))
565         tly = bry = None
566
567     if not prop.scan_build:
568         log.error("Scanning disabled in build. Exiting")
569         sys.exit(1)
570
571     if mode == GUI_MODE:
572         log.error("GUI mode is not implemented yet. Refer to 'hp-scan -h' for help.")
573         sys.exit(1)
574
575
576     else: # INTERACTIVE_MODE
577         import Queue
578         from scan import sane
579         import scanext
580         import cStringIO
581
582         try:
583             import subprocess
584         except ImportError:
585             # Pre-2.4 Python
586             from base import subproc as subprocess
587
588         try:
589             import Image
590         except ImportError:
591             log.error("%s requires the Python Imaging Library (PIL). Exiting." % __mod__)
592             sys.exit(1)
593
594         sane.init()
595         devices = sane.getDevices()
596
597         # Make sure SANE backend sees the device...
598         for d, mfg, mdl, t in devices:
599             if d == device_uri:
600                 break
601         else:
602             log.error("Unable to locate device %s using SANE backend hpaio:. Please check HPLIP installation." % device_uri)
603             sys.exit(1)
604
605         log.info(log.bold("Using device %s" % device_uri))
606         log.info("Opening connection to device...")
607
608         try:
609             device = sane.openDevice(device_uri)
610         except scanext.error, e:
611             sane.reportError(e)
612             sys.exit(1)
613
614         tlx = device.getOptionObj('tl-x').limitAndSet(tlx)
615         tly = device.getOptionObj('tl-y').limitAndSet(tly)
616         brx = device.getOptionObj('br-x').limitAndSet(brx)
617         bry = device.getOptionObj('br-y').limitAndSet(bry)
618
619         scan_area = (brx - tlx) * (bry - tly) # mm^2
620         scan_px = scan_area * res * res / 645.16 # res is in DPI
621         
622         valid_res = device.getOptionObj('resolution').constraint
623         log.debug("Device supported resolutions %s" % (valid_res,))
624         if 0 in valid_res: #min-max range in tuple
625            if res < valid_res[0] or res > valid_res[1]:
626              log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res)
627            if res < valid_res[0]:
628               res = valid_res[0]
629            elif res > valid_res[1]:
630               res = valid_res[1] 
631
632         else:
633           if res not in valid_res:
634             log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res)
635             log.warn("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))
636             res = valid_res[0]
637             min_dist = sys.maxint
638             for x in valid_res:
639                   if abs(r-x) < min_dist:
640                         min_dist = abs(r-x)
641                         res = x
642
643         res = device.getOptionObj('resolution').limitAndSet(res)
644         
645         if scan_mode == 'color':
646             scan_size = scan_px * 3 # 3 bytes/px
647         else:
648             scan_size = scan_px # 1 byte/px
649
650         if scan_size > 52428800: # 50MB
651             if res > 600:
652                 log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.")
653             else:
654                 log.warn("The scan current parameters will cause very large files to be created.")
655
656             log.warn("This can cause the scan to take a long time to complete and may cause your system to slow down.")
657             log.warn("Approx. number of bytes to read from scanner: %s" % utils.format_bytes(scan_size, True))
658        
659         device.setOption('compression', scanner_compression)
660
661         if brx - tlx <= 0.0 or bry - tly <= 0.0:
662             log.error("Invalid scan area (width or height is negative).")
663             sys.exit(1)
664
665         log.info("")
666         log.info("Resolution: %ddpi" % res)
667         log.info("Mode: %s" % scan_mode)
668         log.info("Compression: %s" % scanner_compression)
669         log.info("Scan area (mm):")
670         log.info("  Top left (x,y): (%fmm, %fmm)" % (tlx, tly))
671         log.info("  Bottom right (x,y): (%fmm, %fmm)" % (brx, bry))
672         log.info("  Width: %fmm" % (brx - tlx))
673         log.info("  Height: %fmm" % (bry - tly))
674
675         if page_size:
676             units = page_units # for display purposes only
677             log.info("Page size: %s" % size_desc)
678             if units != 'mm':
679                 log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units)
680
681         if units == 'in':
682             log.info("Scan area (in):")
683             log.info("  Top left (x,y): (%fin, %fin)" % (tlx/25.4, tly/25.4))
684             log.info("  Bottom right (x,y): (%fin, %fin)" % (brx/25.4, bry/25.4))
685             log.info("  Width: %fin" % ((brx - tlx)/25.4))
686             log.info("  Height: %fin" % ((bry - tly)/25.4))
687
688         elif units == 'cm':
689             log.info("Scan area (cm):")
690             log.info("  Top left (x,y): (%fcm, %fcm)" % (tlx/10.0, tly/10.0))
691             log.info("  Bottom right (x,y): (%fcm, %fcm)" % (brx/10.0, bry/10.0))
692             log.info("  Width: %fcm" % ((brx - tlx)/10.0))
693             log.info("  Height: %fcm" % ((bry - tly)/10.0))
694
695         elif units == 'px':
696             log.info("Scan area (px @ %ddpi):" % res)
697             log.info("  Top left (x,y): (%fpx, %fpx)" % (tlx*res/25.4, tly*res/25.4))
698             log.info("  Bottom right (x,y): (%fpx, %fpx)" % (brx*res/25.4, bry*res/25.4))
699             log.info("  Width: %fpx" % ((brx - tlx)*res/25.4))
700             log.info("  Height: %fpx" % ((bry - tly)*res/25.4))
701
702         elif units == 'pt':
703             log.info("Scan area (pt):")
704             log.info("  Top left (x,y): (%fpt, %fpt)" % (tlx/0.3528, tly/0.3528))
705             log.info("  Bottom right (x,y): (%fpt, %fpt)" % (brx/0.3528, bry/0.3528))
706             log.info("  Width: %fpt" % ((brx - tlx)/0.3528))
707             log.info("  Height: %fpt" % ((bry - tly)/0.3528))
708
709         log.info("Destination(s): %s" % ', '.join(dest))
710
711         if 'file' in dest:
712             log.info("Output file: %s" % output)
713
714         update_queue = Queue.Queue()
715         event_queue = Queue.Queue()
716
717         device.setOption("mode", scan_mode)
718         device.setOption("resolution", res)
719
720         source_option = device.getOptionObj("source").constraint
721         log.debug("Supported source Options: %s size=%d" % (source_option,len(source_option)))
722         if source_option is None:
723              log.error("Device doesn't have scanner.")
724              sys.exit(1)
725
726         #check if device has only ADF
727         if len(source_option) == 1 and 'ADF'  in source_option:
728              log.debug("Device has only ADF support")
729              adf = True
730
731         if adf:
732             try:
733                 if 'ADF' not in source_option:
734                      log.error("Failed to set ADF mode. This device doesn't support ADF.")
735                      sys.exit(1)
736                 else:
737                      device.setOption("source", "ADF")
738                      device.setOption("batch-scan", True)
739             except scanext.error:
740                 log.error("Error in setting ADF mode.")
741                 sys.exit(1)
742
743         else:
744             try:
745                 device.setOption("source", "Flatbed")
746                 device.setOption("batch-scan", False)
747             except scanext.error:
748                 log.debug("Error setting source or batch-scan option (this is probably OK).")
749
750
751         if 'file' in dest and not output:
752             log.warn("File destination enabled with no output file specified.")
753
754             if adf:
755                log.info("Setting output format to PDF for ADF mode.")
756                output = utils.createSequencedFilename("hpscan", ".pdf")
757                output_type = 'pdf'
758             else:
759                if scan_mode == 'gray':
760                   log.info("Setting output format to PNG for greyscale mode.")
761                   output = utils.createSequencedFilename("hpscan", ".png")
762                   output_type = 'png'
763                else:
764                   log.info("Setting output format to JPEG for color/lineart mode.")
765                   output = utils.createSequencedFilename("hpscan", ".jpg")
766                   output_type = 'jpeg'
767
768             log.warn("Defaulting to '%s'." % output)
769
770         else:
771             try:
772                output_type = os.path.splitext(output)[1].lower()[1:]
773                if output_type == 'jpg':
774                   output_type = 'jpeg'
775             except IndexError:
776                output_type = ''
777
778         if output_type and output_type not in ('jpeg', 'png', 'pdf'):
779             log.error("Invalid output file format. File formats must be 'jpeg', 'png', or 'pdf'.")
780             sys.exit(1)
781
782         if adf and output_type and output_type != 'pdf':
783             log.error("ADF scans must be saved in PDF file format.")
784             sys.exit(1)
785
786         log.info("\nWarming up...")
787
788         no_docs = False
789         page = 1
790         adf_page_files = []
791         #adf_pages = []
792
793         cleanup_spinner()
794         log.info("")
795
796         try:
797             while True:
798                 if adf:
799                     log.info("\nPage %d: Scanning..." % page)
800                 else:
801                     log.info("\nScanning...")
802
803                 bytes_read = 0
804
805                 try:
806                     try:
807                         ok, expected_bytes, status = device.startScan("RGBA", update_queue, event_queue)
808                         # Note: On some scanners (Marvell) expected_bytes will be < 0 (if lines == -1)
809                         log.debug("expected_bytes = %d" % expected_bytes)
810                     except scanext.error, e:
811                         sane.reportError(e)
812                         sys.exit(1)
813                     except KeyboardInterrupt:
814                         log.error("Aborted.")
815                         device.cancelScan()
816                         sys.exit(1)
817
818                     if adf and status == scanext.SANE_STATUS_NO_DOCS:
819                         if page-1 == 0:
820                             log.error("No document(s). Please load documents and try again.")
821                             sys.exit(0)
822                         else:
823                             log.info("Out of documents. Scanned %d pages total." % (page-1))
824                             no_docs = True
825                             break
826
827                     if expected_bytes > 0:
828                         if adf:
829                             log.info("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes))
830                         else:
831                             log.info("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes))
832
833                     device.waitForScanActive()
834
835                     pm = tui.ProgressMeter("Reading data:")
836
837                     while device.isScanActive():
838                         while update_queue.qsize():
839                             try:
840                                 status, bytes_read = update_queue.get(0)
841
842                                 if not log.is_debug():
843                                     if expected_bytes > 0:
844                                         pm.update(int(100*bytes_read/expected_bytes),
845                                             utils.format_bytes(bytes_read))
846                                     else:
847                                         pm.update(0,
848                                             utils.format_bytes(bytes_read))
849
850                                 if status != scanext.SANE_STATUS_GOOD:
851                                     log.error("Error in reading data. Status=%d bytes_read=%d." % (status, bytes_read))
852                                     sys.exit(1)
853
854                             except Queue.Empty:
855                                 break
856
857
858                         time.sleep(0.5)
859
860                 except KeyboardInterrupt:
861                     log.error("Aborted.")
862                     device.cancelScan()
863                     sys.exit(1)
864
865                 # Make sure queue is cleared out...
866                 while update_queue.qsize():
867                     status, bytes_read = update_queue.get(0)
868
869                     if not log.is_debug():
870                         if expected_bytes > 0:
871                             pm.update(int(100*bytes_read/expected_bytes),
872                                 utils.format_bytes(bytes_read))
873                         else:
874                             pm.update(0,
875                                 utils.format_bytes(bytes_read))
876
877                 log.info("")
878
879                 if bytes_read:
880                     log.info("Read %s from scanner." % utils.format_bytes(bytes_read))
881
882                     buffer, format, format_name, pixels_per_line, \
883                         lines, depth, bytes_per_line, pad_bytes, total_read = device.getScan()
884
885                     log.debug("PPL=%d lines=%d depth=%d BPL=%d pad=%d total=%d" %
886                         (pixels_per_line, lines, depth, bytes_per_line, pad_bytes, total_read))
887
888                     #For Marvell devices, expected bytes is not same as total_read
889                     if lines == -1 or total_read != expected_bytes:
890                         lines = int(total_read / bytes_per_line)
891
892                     if scan_mode in ('color', 'gray'):
893                         try:
894                             im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
895                                 'raw', 'RGBA', 0, 1)
896                         except ValueError:
897                             log.error("Did not read enough data from scanner (I/O Error?)")
898                             sys.exit(1)
899                     elif scan_mode == 'lineart':
900                         try:
901                             im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
902                                 'raw', 'RGBA', 0, 1).convert('L')
903                         except ValueError:
904                             log.error("Did not read enough data from scanner (I/O Error?)")
905                             sys.exit(1)
906
907                     if adf:
908                         temp_output = utils.createSequencedFilename("hpscan_pg%d_" % page, ".png")
909                         adf_page_files.append(temp_output)
910                         im.save(temp_output)
911                         log.debug("Saved page %d to file %s" % (page, temp_output))
912                 else:
913                     log.error("No data read.")
914                     sys.exit(1)
915
916                 if not adf or (adf and no_docs):
917                     break
918
919                 page += 1
920
921         finally:
922             log.info("Closing device.")
923             device.cancelScan()
924
925         if adf:
926             try:
927                 from reportlab.pdfgen import canvas
928             except ImportError:
929                 log.error("PDF output requires ReportLab.")
930                 sys.exit(1)
931
932             if not output:
933                 output = utils.createSequencedFilename("hpscan", ".pdf")
934
935             c = canvas.Canvas(output, (brx/0.3528, bry/0.3528)) 
936
937             for p in adf_page_files:
938                 log.info("Processing page %s..." % p)
939                 image = Image.open(p)
940
941                 try:
942                     c.drawInlineImage(image, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
943                 except NameError:
944                     log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
945                     sys.exit(1)
946
947                 c.showPage()
948
949             log.info("Saving to file %s" % output)
950             c.save()
951             log.info("Viewing PDF file in %s" % pdf_viewer)
952             os.system("%s %s &" % (pdf_viewer, output))
953
954             sys.exit(0)
955
956         if resize != 100:
957             if resize < 1 or resize > 400:
958                 log.error("Resize parameter is incorrect. Resize must be 0% < resize < 400%.")
959                 log.error("Using resize value of 100%.")
960             else:
961                 new_w = int(pixels_per_line * resize / 100)
962                 new_h = int(lines * resize / 100)
963                 log.info("Resizing image from %dx%d to %dx%d..." % (pixels_per_line, lines, new_w, new_h))
964                 im = im.resize((new_w, new_h), Image.ANTIALIAS)
965
966         file_saved = False
967         if 'file' in dest:
968             log.info("\nOutputting to destination 'file':")
969             log.info("Saving to file %s" % output)
970
971             try:
972                 im.save(output)
973             except IOError, e:
974                 log.error("Error saving file: %s (I/O)" % e)
975                 try:
976                     os.remove(output)
977                 except OSError:
978                     pass
979                 sys.exit(1)
980             except ValueError, e:
981                 log.error("Error saving file: %s (PIL)" % e)
982                 try:
983                     os.remove(output)
984                 except OSError:
985                     pass
986                 sys.exit(1)
987
988             file_saved = True
989             dest.remove("file")
990
991         temp_saved = False
992         if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'printer' in dest) \
993             and not file_saved:
994
995             output_fd, output = utils.make_temp_file(suffix='.png')
996             try:
997                 im.save(output)
998             except IOError, e:
999                 log.error("Error saving temporary file: %s" % e)
1000
1001                 try:
1002                     os.remove(output)
1003                 except OSError:
1004                     pass
1005
1006                 sys.exit(1)
1007
1008             os.close(output_fd)
1009             temp_saved = True
1010
1011         for d in dest:
1012             log.info("\nSending to destination '%s':" % d)
1013
1014             if d == 'pdf':
1015                 try:
1016                     from reportlab.pdfgen import canvas
1017                 except ImportError:
1018                     log.error("PDF output requires ReportLab.")
1019                     continue
1020
1021                 pdf_output = utils.createSequencedFilename("hpscan", ".pdf")
1022                 c = canvas.Canvas(pdf_output, (brx/0.3528, bry/0.3528)) 
1023
1024                 try:
1025                     c.drawInlineImage(im, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
1026                 except NameError:
1027                     log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
1028                     continue
1029
1030                 c.showPage()
1031                 log.info("Saving to file %s" % pdf_output)
1032                 c.save()
1033                 log.info("Viewing PDF file in %s" % pdf_viewer)
1034                 os.system("%s %s &" % (pdf_viewer, pdf_output))
1035                 
1036                 sys.exit(0)
1037
1038             elif d == 'printer':
1039                 hp_print = utils.which("hp-print")
1040                 if hp_print:
1041                     cmd = 'hp-print %s &' % output
1042                 else:
1043                     cmd = "python ./print.py %s &" % output
1044
1045                 os.system(cmd)
1046
1047             elif d == 'email':
1048                 try:
1049                     from email.mime.image import MIMEImage
1050                     from email.mime.multipart import MIMEMultipart
1051                     from email.mime.text import MIMEText
1052                 except ImportError:
1053                     try:
1054                         from email.MIMEImage import MIMEImage
1055                         from email.MIMEMultipart import MIMEMultipart
1056                         from email.MIMEText import MIMEText
1057                     except ImportError:
1058                         log.error("hp-scan email destination requires Python 2.2+.")
1059                         continue
1060
1061                 msg = MIMEMultipart()
1062                 msg['Subject'] = email_subject
1063                 msg['From'] = email_from
1064                 msg['To'] = ','.join(email_to)
1065                 msg.preamble = 'Scanned using hp-scan'
1066
1067                 if email_note:
1068                     txt = MIMEText(email_note)
1069                     msg.attach(txt)
1070
1071                 if file_saved:
1072                     txt = MIMEText("attached: %s: %dx%d %s PNG image." %
1073                         (os.path.basename(output), pixels_per_line, lines, scan_mode))
1074                 else:
1075                     txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode))
1076
1077                 msg.attach(txt)
1078
1079                 fp = open(output, 'r')
1080                 img = MIMEImage(fp.read())
1081                 fp.close()
1082
1083                 if file_saved:
1084                     img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output))
1085
1086                 msg.attach(img)
1087
1088                 sendmail = utils.which("sendmail")
1089
1090                 if sendmail:
1091                     sendmail = os.path.join(sendmail, 'sendmail')
1092                     cmd = [sendmail,'-t','-r',email_from]
1093
1094                     log.debug(repr(cmd))
1095                     err = None
1096                     try:
1097                         sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1098                         std_out, std_err = sp.communicate(msg.as_string())
1099                         if std_err != '':
1100                             err = std_err
1101                     except OSError, e:
1102                         err = str(e)
1103                     cleanup_spinner()
1104
1105                     if err:
1106                         log.error(repr(err))
1107
1108                 else:
1109                     log.error("Mail send failed. 'sendmail' not found.")
1110
1111             elif d == 'viewer':
1112                 if viewer:
1113                     log.info("Viewing file in %s" % viewer)
1114                     os.system("%s %s &" % (viewer, output))
1115                 else:
1116                     log.error("Viewer not found.")
1117
1118             elif d == 'editor':
1119                 if editor:
1120                     log.info("Editing file in %s" % editor)
1121                     os.system("%s %s &" % (editor, output))
1122                 else:
1123                     log.error("Editor not found.")
1124
1125         device.freeScan()
1126         sane.deInit()
1127
1128
1129 except KeyboardInterrupt:
1130     log.error("User exit")
1131
1132 log.info("")
1133 log.info("Done.")
1134