2 # -*- coding: utf-8 -*-
4 # (c) Copyright 2003-2011 Hewlett-Packard Development Company, L.P.
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.
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.
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
21 # Contributors: Sarbeswar Meher
24 from __future__ import division
28 __title__ = 'Scan Utility'
29 __doc__ = "SANE-based scan utility for HPLIP supported all-in-one/mfp devices."
43 from base import tui, device, module, utils
47 username = prop.username
59 email_subject = 'hp-scan from %s' % socket.gethostname()
68 scanner_compression = 'JPEG'
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'),
100 viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',]
101 for v in viewer_list:
104 viewer = os.path.join(vv, v)
109 editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',]
110 for e in editor_list:
113 editor = os.path.join(ee, e)
117 pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',]
118 for v in pdf_viewer_list:
121 pdf_viewer = os.path.join(vv, v)
124 mod = module.Module(__mod__, __title__, __version__, __doc__, None,
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),
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),
166 ("[OPTIONS] ('file' dest)", "", "header", False),
167 ("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False),
169 ("[OPTIONS] ('pdf' dest)", "", "header", False),
170 ("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False),
172 ("[OPTIONS] ('viewer' dest)", "", "header", False),
173 ("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False),
175 ("[OPTIONS] ('editor' dest)", "", "header", False),
176 ("Image editor application:", "-e<editor> or --editor=<editor>", "option", False),
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),
186 ("[OPTIONS] ('printer' dest)", "", "header", False),
187 ("Printer queue/printer:", "--printer=<printer_name>", "option", False),
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),],
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=',
213 device_uri = mod.getDeviceUri(device_uri, printer_name,
214 back_end_filter=['hpaio'], filter={'scan-type': (operator.gt, 0)})
217 if o in ('-x', '--compression'):
218 a = a.strip().lower()
220 if a in ('jpeg', 'jpg'):
221 scanner_compression = 'JPEG'
223 elif a in ('raw', 'none'):
224 scanner_compression = 'None'
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'
232 scanner_compression = 'None'
235 scanner_compression = 'JPEG'
237 elif o in ('--color', '--colour'):
240 elif o in ('--lineart', '--line-art', '--bw'):
241 scan_mode = 'lineart'
243 elif o in ('--gray', '--grayscale', '--gray-scale', '--grey', '--greyscale', '--grey-scale'):
246 elif o in ('-m', '--mode'):
247 a = a.strip().lower()
249 if a in ('color', 'colour'):
252 elif a in ('lineart', 'bw', 'b&w'):
253 scan_mode = 'lineart'
255 elif a in ('gray', 'grayscale', 'grey', 'greyscale'):
259 log.error("Invalid mode. Using default of 'gray'.")
260 log.error("Valid modes are 'color', 'lineart', or 'gray'.")
263 elif o in ('--res', '--resolution', '-r'):
267 log.error("Invalid value for resolution.")
272 elif o in ('-t', '--units', '--unit'):
273 a = a.strip().lower()
275 if a in ('in', 'inch', 'inches'):
278 elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'):
281 elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'):
284 elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'):
287 elif a in ('pt', 'point', 'points', 'pts'):
291 log.error("Invalid units. Using default of 'mm'.")
295 a = a.strip().lower()
299 log.error("Invalid value for tlx.")
304 a = a.strip().lower()
308 log.error("Invalid value for tly.")
313 a = a.strip().lower()
317 log.error("Invalid value for brx.")
322 a = a.strip().lower()
326 log.error("Invalid value for bry.")
330 elif o in ('-a', '--area'): # tlx, tly, brx, bry
331 a = a.strip().lower()
333 tlx, tly, brx, bry = a.split(',')[:4]
335 log.error("Invalid scan area. Using defaults.")
340 log.error("Invalid value for tlx. Using defaults.")
346 log.error("Invalid value for tly. Using defaults.")
352 log.error("Invalid value for brx. Using defaults.")
358 log.error("Invalid value for bry. Using defaults.")
361 elif o in ('-b', '--box'): # tlx, tly, w, h
362 a = a.strip().lower()
364 tlx, tly, width, height = a.split(',')[:4]
366 log.error("Invalid scan area. Using defaults.")
371 log.error("Invalid value for tlx. Using defaults.")
377 log.error("Invalid value for tly. Using defaults.")
382 brx = float(width) + tlx
384 log.error("Invalid value for width. Using defaults.")
387 log.error("Cannot calculate brx since tlx is invalid. Using defaults.")
392 bry = float(height) + tly
394 log.error("Invalid value for height. Using defaults.")
397 log.error("Cannot calculate bry since tly is invalid. Using defaults.")
401 size = a.strip().lower()
402 if size in PAGE_SIZES:
403 brx, bry, size_desc, page_units = PAGE_SIZES[size]
407 log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(PAGE_SIZES.keys()))
408 log.error("Using defaults.")
410 elif o in ('-o', '--output', '-f', '--file'):
411 output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip())))
414 ext = os.path.splitext(output)[1]
416 log.error("Invalid filename extension.")
421 if ext.lower() not in ('.jpg', '.png'):
422 log.error("Only JPG (.jpg) and PNG (.png) output files are supported.")
427 if os.path.exists(output):
428 log.warn("Output file '%s' exists. File will be overwritten." % output)
430 if 'file' not in dest:
433 elif o in ('-s', '--dest', '--destination'):
434 a = a.strip().lower().split(',')
437 if aa in ('file', 'viewer', 'editor', 'printer', 'print', 'email', 'pdf') \
439 if aa == 'print': aa = 'printer'
442 elif o in ('-v', '--viewer'):
446 log.error("Viewer application not found.")
448 viewer = os.path.join(b, a)
449 if 'viewer' not in dest:
450 dest.append('viewer')
452 elif o in ('-e', '--editor'):
456 log.error("Editor application not found.")
458 editor = os.path.join(b, a)
459 if 'editor' not in dest:
460 dest.append('editor')
466 log.error("PDF viewer application not found.")
468 pdf_viewer = os.path.join(b, a)
469 if 'pdf' not in dest:
473 elif o in ('--email-to', '--to'):
474 email_to = a.split(',')
475 if 'email' not in dest:
478 elif o in ('--email-from', '--from'):
480 if 'email' not in dest:
483 elif o in ('--email-subject', '--subject', '--about'):
485 if 'email' not in dest:
488 elif o in ('--email-note', '--email-msg', '--msg', '--message', '--note', '--notes'):
490 if 'email' not in dest:
493 elif o == '--resize':
494 a = a.replace("%", "")
499 log.error("Invalid resize value. Using default of 100%.")
501 elif o in ('-b', '--brightness'):
504 elif o in ('-c', '--contrast'):
506 contrast = int(a.strip())
508 log.error("Invalid contrast value. Using default of 100.")
516 if printer_name is not None and \
517 device.getDeviceURIByPrinterName(printer_name) is not None and \
518 'printer' not in dest:
520 dest.append('printer')
523 log.warn("No destinations specified. Adding 'file' destination by default.")
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.")
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
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
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
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
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))
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))
567 if not prop.scan_build:
568 log.error("Scanning disabled in build. Exiting")
572 log.error("GUI mode is not implemented yet. Refer to 'hp-scan -h' for help.")
576 else: # INTERACTIVE_MODE
578 from scan import sane
586 from base import subproc as subprocess
591 log.error("%s requires the Python Imaging Library (PIL). Exiting." % __mod__)
595 devices = sane.getDevices()
597 # Make sure SANE backend sees the device...
598 for d, mfg, mdl, t in devices:
602 log.error("Unable to locate device %s using SANE backend hpaio:. Please check HPLIP installation." % device_uri)
605 log.info(log.bold("Using device %s" % device_uri))
606 log.info("Opening connection to device...")
609 device = sane.openDevice(device_uri)
610 except scanext.error, e:
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)
619 scan_area = (brx - tlx) * (bry - tly) # mm^2
620 scan_px = scan_area * res * res / 645.16 # res is in DPI
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]:
629 elif res > valid_res[1]:
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]))
637 min_dist = sys.maxint
639 if abs(r-x) < min_dist:
643 res = device.getOptionObj('resolution').limitAndSet(res)
645 if scan_mode == 'color':
646 scan_size = scan_px * 3 # 3 bytes/px
648 scan_size = scan_px # 1 byte/px
650 if scan_size > 52428800: # 50MB
652 log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.")
654 log.warn("The scan current parameters will cause very large files to be created.")
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))
659 device.setOption('compression', scanner_compression)
661 if brx - tlx <= 0.0 or bry - tly <= 0.0:
662 log.error("Invalid scan area (width or height is negative).")
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))
676 units = page_units # for display purposes only
677 log.info("Page size: %s" % size_desc)
679 log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units)
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))
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))
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))
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))
709 log.info("Destination(s): %s" % ', '.join(dest))
712 log.info("Output file: %s" % output)
714 update_queue = Queue.Queue()
715 event_queue = Queue.Queue()
717 device.setOption("mode", scan_mode)
718 device.setOption("resolution", res)
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.")
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")
733 if 'ADF' not in source_option:
734 log.error("Failed to set ADF mode. This device doesn't support ADF.")
737 device.setOption("source", "ADF")
738 device.setOption("batch-scan", True)
739 except scanext.error:
740 log.error("Error in setting ADF mode.")
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).")
751 if 'file' in dest and not output:
752 log.warn("File destination enabled with no output file specified.")
755 log.info("Setting output format to PDF for ADF mode.")
756 output = utils.createSequencedFilename("hpscan", ".pdf")
759 if scan_mode == 'gray':
760 log.info("Setting output format to PNG for greyscale mode.")
761 output = utils.createSequencedFilename("hpscan", ".png")
764 log.info("Setting output format to JPEG for color/lineart mode.")
765 output = utils.createSequencedFilename("hpscan", ".jpg")
768 log.warn("Defaulting to '%s'." % output)
772 output_type = os.path.splitext(output)[1].lower()[1:]
773 if output_type == 'jpg':
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'.")
782 if adf and output_type and output_type != 'pdf':
783 log.error("ADF scans must be saved in PDF file format.")
786 log.info("\nWarming up...")
799 log.info("\nPage %d: Scanning..." % page)
801 log.info("\nScanning...")
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:
813 except KeyboardInterrupt:
814 log.error("Aborted.")
818 if adf and status == scanext.SANE_STATUS_NO_DOCS:
820 log.error("No document(s). Please load documents and try again.")
823 log.info("Out of documents. Scanned %d pages total." % (page-1))
827 if expected_bytes > 0:
829 log.info("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes))
831 log.info("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes))
833 device.waitForScanActive()
835 pm = tui.ProgressMeter("Reading data:")
837 while device.isScanActive():
838 while update_queue.qsize():
840 status, bytes_read = update_queue.get(0)
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))
848 utils.format_bytes(bytes_read))
850 if status != scanext.SANE_STATUS_GOOD:
851 log.error("Error in reading data. Status=%d bytes_read=%d." % (status, bytes_read))
860 except KeyboardInterrupt:
861 log.error("Aborted.")
865 # Make sure queue is cleared out...
866 while update_queue.qsize():
867 status, bytes_read = update_queue.get(0)
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))
875 utils.format_bytes(bytes_read))
880 log.info("Read %s from scanner." % utils.format_bytes(bytes_read))
882 buffer, format, format_name, pixels_per_line, \
883 lines, depth, bytes_per_line, pad_bytes, total_read = device.getScan()
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))
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)
892 if scan_mode in ('color', 'gray'):
894 im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
897 log.error("Did not read enough data from scanner (I/O Error?)")
899 elif scan_mode == 'lineart':
901 im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
902 'raw', 'RGBA', 0, 1).convert('L')
904 log.error("Did not read enough data from scanner (I/O Error?)")
908 temp_output = utils.createSequencedFilename("hpscan_pg%d_" % page, ".png")
909 adf_page_files.append(temp_output)
911 log.debug("Saved page %d to file %s" % (page, temp_output))
913 log.error("No data read.")
916 if not adf or (adf and no_docs):
922 log.info("Closing device.")
927 from reportlab.pdfgen import canvas
929 log.error("PDF output requires ReportLab.")
933 output = utils.createSequencedFilename("hpscan", ".pdf")
935 c = canvas.Canvas(output, (brx/0.3528, bry/0.3528))
937 for p in adf_page_files:
938 log.info("Processing page %s..." % p)
939 image = Image.open(p)
942 c.drawInlineImage(image, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
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.")
949 log.info("Saving to file %s" % output)
951 log.info("Viewing PDF file in %s" % pdf_viewer)
952 os.system("%s %s &" % (pdf_viewer, output))
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%.")
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)
968 log.info("\nOutputting to destination 'file':")
969 log.info("Saving to file %s" % output)
974 log.error("Error saving file: %s (I/O)" % e)
980 except ValueError, e:
981 log.error("Error saving file: %s (PIL)" % e)
992 if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'printer' in dest) \
995 output_fd, output = utils.make_temp_file(suffix='.png')
999 log.error("Error saving temporary file: %s" % e)
1012 log.info("\nSending to destination '%s':" % d)
1016 from reportlab.pdfgen import canvas
1018 log.error("PDF output requires ReportLab.")
1021 pdf_output = utils.createSequencedFilename("hpscan", ".pdf")
1022 c = canvas.Canvas(pdf_output, (brx/0.3528, bry/0.3528))
1025 c.drawInlineImage(im, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
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.")
1031 log.info("Saving to file %s" % pdf_output)
1033 log.info("Viewing PDF file in %s" % pdf_viewer)
1034 os.system("%s %s &" % (pdf_viewer, pdf_output))
1038 elif d == 'printer':
1039 hp_print = utils.which("hp-print")
1041 cmd = 'hp-print %s &' % output
1043 cmd = "python ./print.py %s &" % output
1049 from email.mime.image import MIMEImage
1050 from email.mime.multipart import MIMEMultipart
1051 from email.mime.text import MIMEText
1054 from email.MIMEImage import MIMEImage
1055 from email.MIMEMultipart import MIMEMultipart
1056 from email.MIMEText import MIMEText
1058 log.error("hp-scan email destination requires Python 2.2+.")
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'
1068 txt = MIMEText(email_note)
1072 txt = MIMEText("attached: %s: %dx%d %s PNG image." %
1073 (os.path.basename(output), pixels_per_line, lines, scan_mode))
1075 txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode))
1079 fp = open(output, 'r')
1080 img = MIMEImage(fp.read())
1084 img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output))
1088 sendmail = utils.which("sendmail")
1091 sendmail = os.path.join(sendmail, 'sendmail')
1092 cmd = [sendmail,'-t','-r',email_from]
1094 log.debug(repr(cmd))
1097 sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1098 std_out, std_err = sp.communicate(msg.as_string())
1106 log.error(repr(err))
1109 log.error("Mail send failed. 'sendmail' not found.")
1113 log.info("Viewing file in %s" % viewer)
1114 os.system("%s %s &" % (viewer, output))
1116 log.error("Viewer not found.")
1120 log.info("Editing file in %s" % editor)
1121 os.system("%s %s &" % (editor, output))
1123 log.error("Editor not found.")
1129 except KeyboardInterrupt:
1130 log.error("User exit")