2
# -*- coding: utf-8 -*-
4
# (c) Copyright 2003-2015 HP 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
28
__title__ = 'Scan Utility'
29
__doc__ = "SANE-based scan utility for HPLIP supported all-in-one/mfp devices."
44
from base.sixext import PY3
45
from base import tui, device, module, utils, os_utils
50
username = prop.username
62
email_subject = 'hp-scan from %s' % socket.gethostname()
68
set_brightness = False
74
scanner_compression = 'JPEG'
80
PAGE_SIZES = { # in mm
81
'5x7' : (127, 178, "5x7 photo", 'in'),
82
'4x6' : (102, 152, "4x6 photo", 'in'),
83
'3x5' : (76, 127, "3x5 index card", 'in'),
84
'a2_env' : (111, 146, "A2 Envelope", 'in'),
85
'a3' : (297, 420, "A3", 'mm'),
86
"a4" : (210, 297, "A4", 'mm'),
87
"a5" : (148, 210, "A5", 'mm'),
88
"a6" : (105, 148, "A6", 'mm'),
89
"b4" : (257, 364, "B4", 'mm'),
90
"b5" : (182, 257, "B5", 'mm'),
91
"c6_env" : (114, 162, "C6 Envelope", 'in'),
92
"dl_env" : (110, 220, "DL Envelope", 'in'),
93
"exec" : (184, 267, "Executive", 'in'),
94
"flsa" : (216, 330, "Flsa", 'mm'),
95
"higaki" : (100, 148, "Hagaki", 'mm'),
96
"japan_env_3" : (120, 235, "Japanese Envelope #3", 'mm'),
97
"japan_env_4" : (90, 205, "Japanese Envelope #4", 'mm'),
98
"legal" : (215, 356, "Legal", 'in'),
99
"letter" : (215, 279, "Letter", 'in'),
100
"no_10_env" : (105, 241, "Number 10 Envelope", 'in'),
101
"oufufu-hagaki" : (148, 200, "Oufuku-Hagaki", 'mm'),
102
"photo" : (102, 152, "Photo", 'in'),
103
"super_b" : (330, 483, "Super B", 'in'),
109
viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',]
110
for v in viewer_list:
113
viewer = os.path.join(vv, v)
118
editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',]
119
for e in editor_list:
122
editor = os.path.join(ee, e)
126
pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',]
127
for v in pdf_viewer_list:
130
pdf_viewer = os.path.join(vv, v)
133
mod = module.Module(__mod__, __title__, __version__, __doc__, None,
136
mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS,
137
extra_options=[utils.USAGE_SPACE,
138
("[OPTIONS] (General)", "", "header", False),
139
("Scan destinations:", "-s<dest_list> or --dest=<dest_list>", "option", False),
140
("", "where <dest_list> is a comma separated list containing one or more of: 'file'", "option", False),
141
("", ", 'viewer', 'editor', 'pdf', or 'print'. Use only commas between values, no spaces.", "option", False),
142
("Scan mode:", "-m<mode> or --mode=<mode>. Where <mode> is 'gray'\*, 'color' or 'lineart'.", "option", False),
143
("Scanning resolution:", "-r<resolution_in_dpi> or --res=<resolution_in_dpi> or --resolution=<resolution_in_dpi>", "option", False),
144
("", "where 300 is default.", "option", False),
145
("Image resize:", "--resize=<scale_in_%> (min=1%, max=400%, default=100%)", "option", False),
146
("Image contrast:", "-c=<contrast> or --contrast=<contrast>", "option", False),
147
("", "The contrast range varies from device to device.", "option", False),
148
("Image brightness:", "-b=<brightness> or --brightness=<brightness>", "option", False),
149
("", "The brightness range varies from device to device.", "option", False),
150
("ADF mode:", "--adf (Note, only PDF output is supported when using the ADF)", "option", False),
151
("", "--duplex or --dup for duplex scanning using ADF.", "option", False),
153
("[OPTIONS] (Scan area)", "", "header", False),
154
("Specify the units for area/box measurements:", "-t<units> or --units=<units>", "option", False),
155
("", "where <units> is 'mm'\*, 'cm', 'in', 'px', or 'pt' ('mm' is default).", "option", False),
156
("Scan area:", "-a<tlx>,<tly>,<brx>,<bry> or --area=<tlx>,<tly>,<brx>,<bry>", "option", False),
157
("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
158
("", "Units for tlx, tly, brx, and bry are specified by -t/--units (default is 'mm').", "option", False),
159
("", "Use only commas between values, no spaces.", "option", False),
160
("Scan box:", "--box=<tlx>,<tly>,<width>,<height>", "option", False),
161
("", "tlx and tly coordinates are relative to the upper left corner of the scan area.", "option", False),
162
("", "Units for tlx, tly, width, and height are specified by -t/--units (default is 'mm').", "option", False),
163
("", "Use only commas between values, no spaces.", "option", False),
164
("Top left x of the scan area:", "--tlx=<tlx>", "option", False),
165
("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
166
("", "Units are specified by -t/--units (default is 'mm').", "option", False),
167
("Top left y of the scan area:", "--tly=<tly>", "option", False),
168
("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
169
("", "Units are specified by -t/--units (default is 'mm').", "option", False),
170
("Bottom right x of the scan area:", "--brx=<brx>", "option", False),
171
("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
172
("", "Units are specified by -t/--units (default is 'mm').", "option", False),
173
("Bottom right y of the scan area:", "--bry=<bry>", "option", False),
174
("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
175
("", "Units are specified by -t/--units (default is 'mm').", "option", False),
176
("Specify the scan area based on a paper size:", "--size=<paper size name>", "option", False),
177
("", "where <paper size name> is one of: %s" % ', '.join(sorted(list(PAGE_SIZES.keys()))), "option", False),
179
("[OPTIONS] ('file' dest)", "", "header", False),
180
("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False),
182
("[OPTIONS] ('pdf' dest)", "", "header", False),
183
("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False),
185
("[OPTIONS] ('viewer' dest)", "", "header", False),
186
("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False),
188
("[OPTIONS] ('editor' dest)", "", "header", False),
189
("Image editor application:", "-e<editor> or --editor=<editor>", "option", False),
191
("[OPTIONS] ('email' dest)", "", "header", False),
192
("From: address for 'email' dest:", "--email-from=<email_from_address> (required for 'email' dest.)", "option", False),
193
("To: address for 'email' dest:", "--email-to=<email__to_address> (required for 'email' dest.)", "option", False),
194
("Email subject for 'email' dest:", '--email-subject="<subject>" or --subject="<subject>"', "option", False),
195
("", 'Use double quotes (") around the subject if it contains space characters.', "option", False),
196
("Note or message for the 'email' dest:", '--email-msg="<msg>" or --email-note="<note>"', "option", False),
197
("", 'Use double quotes (") around the note/message if it contains space characters.', "option", False),
199
("[OPTIONS] ('printer' dest)", "", "header", False),
200
("Printer queue/printer dest:", "--dp=<printer_name> or --dest-printer=<printer_name>", "option", False),
201
("Printer device-URI dest:", "--dd=<device-uri> or --dest-device=<device-uri>", "option", False),
203
("[OPTIONS] (advanced)", "", "header", False),
204
("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),],
207
opts, device_uri, printer_name, mode, ui_toolkit, lang = \
208
mod.parseStdOpts('s:m:r:c:t:a:b:o:v:f:c:x:e:',
209
['dest=', 'mode=', 'res=', 'resolution=',
210
'resize=', 'contrast=', 'adf', 'duplex', 'dup', 'unit=',
211
'units=', 'area=', 'box=', 'tlx=',
212
'tly=', 'brx=', 'bry=', 'size=',
213
'file=', 'output=', 'pdf=', 'viewer=',
214
'email-from=', 'from=', 'email-to=',
215
'to=', 'email-msg=', 'msg=',
216
'printer=', 'compression=' , 'raw',
217
'jpeg', 'color', 'lineart', 'colour',
218
'bw', 'gray', 'grayscale', 'grey',
219
'greyscale', 'email-subject=',
220
'subject=', 'to=', 'from=', 'jpg',
221
'grey-scale', 'gray-scale', 'about=',
222
'editor=', 'dp=', 'dest-printer=', 'dd=',
223
'dest-device=', 'brightness=',
228
sane_devices = sane.getDevices()
230
for d, mfg, mdl, t in sane_devices:
234
devicelist[d] = [mdl]
236
devicelist[d].append(mdl)
238
device_uri = mod.getDeviceUri(device_uri, printer_name,
239
back_end_filter=['hpaio'], filter={'scan-type': (operator.gt, 0)}, devices=devicelist)
245
if o in ('-x', '--compression'):
246
a = a.strip().lower()
248
if a in ('jpeg', 'jpg'):
249
scanner_compression = 'JPEG'
251
elif a in ('raw', 'none'):
252
scanner_compression = 'None'
255
log.error("Invalid compression value. Valid values are 'jpeg', 'raw', and 'none'.")
256
log.error("Using default value of 'jpeg'.")
257
scanner_compression = 'JPEG'
260
scanner_compression = 'None'
263
scanner_compression = 'JPEG'
265
elif o in ('--color', '--colour'):
268
elif o in ('--lineart', '--line-art', '--bw'):
269
scan_mode = 'lineart'
271
elif o in ('--gray', '--grayscale', '--gray-scale', '--grey', '--greyscale', '--grey-scale'):
274
elif o in ('-m', '--mode'):
275
a = a.strip().lower()
277
if a in ('color', 'colour'):
280
elif a in ('lineart', 'bw', 'b&w'):
281
scan_mode = 'lineart'
283
elif a in ('gray', 'grayscale', 'grey', 'greyscale'):
287
log.error("Invalid mode. Using default of 'gray'.")
288
log.error("Valid modes are 'color', 'lineart', or 'gray'.")
291
elif o in ('--res', '--resolution', '-r'):
295
log.error("Invalid value for resolution.")
300
elif o in ('-t', '--units', '--unit'):
301
a = a.strip().lower()
303
if a in ('in', 'inch', 'inches'):
306
elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'):
309
elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'):
312
elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'):
315
elif a in ('pt', 'point', 'points', 'pts'):
319
log.error("Invalid units. Using default of 'mm'.")
323
a = a.strip().lower()
327
log.error("Invalid value for tlx.")
332
a = a.strip().lower()
336
log.error("Invalid value for tly.")
341
a = a.strip().lower()
345
log.error("Invalid value for brx.")
350
a = a.strip().lower()
354
log.error("Invalid value for bry.")
358
elif o in ('-a', '--area'): # tlx, tly, brx, bry
359
a = a.strip().lower()
361
tlx, tly, brx, bry = a.split(',')[:4]
363
log.error("Invalid scan area. Using defaults.")
368
log.error("Invalid value for tlx. Using defaults.")
374
log.error("Invalid value for tly. Using defaults.")
380
log.error("Invalid value for brx. Using defaults.")
386
log.error("Invalid value for bry. Using defaults.")
389
elif o == '--box': # tlx, tly, w, h
390
a = a.strip().lower()
392
tlx, tly, width, height = a.split(',')[:4]
394
log.error("Invalid scan area. Using defaults.")
399
log.error("Invalid value for tlx. Using defaults.")
405
log.error("Invalid value for tly. Using defaults.")
410
brx = float(width) + tlx
412
log.error("Invalid value for width. Using defaults.")
415
log.error("Cannot calculate brx since tlx is invalid. Using defaults.")
420
bry = float(height) + tly
422
log.error("Invalid value for height. Using defaults.")
425
log.error("Cannot calculate bry since tly is invalid. Using defaults.")
429
size = a.strip().lower()
430
if size in PAGE_SIZES:
431
brx, bry, size_desc, page_units = PAGE_SIZES[size]
435
log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(list(PAGE_SIZES.keys())))
436
log.error("Using defaults.")
438
elif o in ('-o', '--output', '-f', '--file'):
439
output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip())))
442
ext = os.path.splitext(output)[1]
444
log.error("Invalid filename extension.")
449
if ext.lower() not in ('.jpg', '.png', '.pdf'):
450
log.error("Only JPG (.jpg), PNG (.png) and PDF (.pdf) output files are supported.")
455
if os.path.exists(output):
456
log.warn("Output file '%s' exists. File will be overwritten." % output)
458
if 'file' not in dest:
461
elif o in ('-s', '--dest', '--destination'):
462
a = a.strip().lower().split(',')
465
if aa in ('file', 'viewer', 'editor', 'print', 'email', 'pdf') \
469
elif o in ('--dd', '--dest-device'):
470
dest_devUri = a.strip()
471
if 'print' not in dest:
474
elif o in ('--dp', '--dest-printer'):
475
dest_printer = a.strip()
476
if 'print' not in dest:
479
elif o in ('-v', '--viewer'):
483
log.error("Viewer application not found.")
485
viewer = os.path.join(b, a)
486
if 'viewer' not in dest:
487
dest.append('viewer')
489
elif o in ('-e', '--editor'):
493
log.error("Editor application not found.")
495
editor = os.path.join(b, a)
496
if 'editor' not in dest:
497
dest.append('editor')
503
log.error("PDF viewer application not found.")
505
pdf_viewer = os.path.join(b, a)
506
if 'pdf' not in dest:
510
elif o in ('--email-to', '--to'):
511
email_to = a.split(',')
512
if 'email' not in dest:
515
elif o in ('--email-from', '--from'):
517
if 'email' not in dest:
520
elif o in ('--email-subject', '--subject', '--about'):
522
if 'email' not in dest:
525
elif o in ('--email-note', '--email-msg', '--msg', '--message', '--note', '--notes'):
527
if 'email' not in dest:
530
elif o == '--resize':
531
a = a.replace("%", "")
536
log.error("Invalid resize value. Using default of 100%.")
538
elif o in ('-b', '--brightness'):
540
set_brightness = True
541
brightness = int(a.strip())
543
log.error("Invalid brightness value. Using default of 0.")
546
elif o in ('-c', '--contrast'):
549
contrast = int(a.strip())
551
log.error("Invalid contrast value. Using default of 0.")
557
elif o in ('--dup', '--duplex'):
563
log.warn("No destinations specified. Adding 'file' destination by default.")
566
if 'email' in dest and (not email_from or not email_to):
567
log.error("Email specified, but email to and/or email from address(es) were not specified.")
568
log.error("Disabling 'email' destination.")
575
if tlx is not None: tlx = tlx * 25.4
576
if tly is not None: tly = tly * 25.4
577
if brx is not None: brx = brx * 25.4
578
if bry is not None: bry = bry * 25.4
581
if tlx is not None: tlx = tlx * 10.0
582
if tly is not None: tly = tly * 10.0
583
if brx is not None: brx = brx * 10.0
584
if bry is not None: bry = bry * 10.0
587
if tlx is not None: tlx = tlx * 0.3528
588
if tly is not None: tly = tly * 0.3528
589
if brx is not None: brx = brx * 0.3528
590
if bry is not None: bry = bry * 0.3528
593
log.warn("Units set to pixels. Using resolution of %ddpi for area calculations." % res)
594
if tlx is not None: tlx = tlx / res * 25.4
595
if tly is not None: tly = tly / res * 25.4
596
if brx is not None: brx = brx / res * 25.4
597
if bry is not None: bry = bry / res * 25.4
599
if tlx is not None and brx is not None and tlx >= brx:
600
log.error("Invalid values for tlx (%d) and brx (%d) (tlx>=brx). Using defaults." % (tlx, brx))
603
if tly is not None and bry is not None and tly >= bry:
604
log.error("Invalid values for tly (%d) and bry (%d) (tly>=bry). Using defaults." % (tly, bry))
607
if not prop.scan_build:
608
log.error("Scanning disabled in build. Exiting")
612
log.error("GUI mode is not implemented yet. Refer to 'hp-scan -h' for help.")
616
else: # INTERACTIVE_MODE
617
from base.sixext.moves import queue
623
from base import subproc as subprocess
626
from PIL import Image
628
log.error("%s requires the Python Imaging Library (PIL). Exiting." % __mod__)
629
if PY3: # Workaround due to incomplete Python3 support in Linux distros.
630
log.notice(log.bold("Manually install the PIL package. More information is available at http://hplipopensource.com/node/369"))
634
devices = sane.getDevices()
636
# Make sure SANE backend sees the device...
637
for d, mfg, mdl, t in devices:
641
log.error("Unable to locate device %s using SANE backend hpaio:. Please check HPLIP installation." % device_uri)
644
log.info(log.bold("Using device %s" % device_uri))
645
log.info("Opening connection to device...")
648
device = sane.openDevice(device_uri)
649
except scanext.error as e:
650
sane.reportError(e.args[0])
654
source_option = device.getOptionObj("source").constraint
655
log.debug("Supported source Options: %s size=%d" % (source_option,len(source_option)))
656
if source_option is None:
657
log.error("Device doesn't have scanner.")
660
log.error("Failed to get the source from device.")
662
#check if device has only ADF
663
if len(source_option) == 1 and 'ADF' in source_option:
664
log.debug("Device has only ADF support")
669
if 'ADF' not in source_option:
670
log.error("Failed to set ADF mode. This device doesn't support ADF.")
674
if 'Duplex' in source_option:
675
device.setOption("source", "Duplex")
677
log.warn("Device doesn't support Duplex scanning. Continuing with Simplex ADF scan.")
678
device.setOption("source", "ADF")
680
device.setOption("source", "ADF")
681
device.setOption("batch-scan", True)
682
except scanext.error:
683
log.error("Error in setting ADF mode Duplex=%d." % duplex)
688
device.setOption("source", "Flatbed")
689
device.setOption("batch-scan", False)
690
except scanext.error:
691
log.debug("Error setting source or batch-scan option (this is probably OK).")
694
tlx = device.getOptionObj('tl-x').limitAndSet(tlx)
695
tly = device.getOptionObj('tl-y').limitAndSet(tly)
696
brx = device.getOptionObj('br-x').limitAndSet(brx)
697
bry = device.getOptionObj('br-y').limitAndSet(bry)
699
scan_area = (brx - tlx) * (bry - tly) # mm^2
701
valid_res = device.getOptionObj('resolution').constraint
702
log.debug("Device supported resolutions %s" % (valid_res,))
703
if 0 in valid_res: #min-max range in tuple
704
if res < valid_res[0] or res > valid_res[1]:
705
log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res)
706
if res < valid_res[0]:
708
elif res > valid_res[1]:
712
if res not in valid_res:
713
log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res)
714
log.warn("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))
716
min_dist = sys.maxsize
718
if abs(r-x) < min_dist:
722
res = device.getOptionObj('resolution').limitAndSet(res)
723
scan_px = scan_area * res * res / 645.16 # res is in DPI
725
if scan_mode == 'color':
726
scan_size = scan_px * 3 # 3 bytes/px
727
elif scan_mode == 'gray':
728
scan_size = scan_px # 1 byte/px
730
scan_size = scan_px // 8
732
if scan_size > 52428800: # 50MB
734
log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.")
736
log.warn("The scan current parameters will cause very large files to be created.")
738
log.warn("This can cause the scan to take a long time to complete and may cause your system to slow down.")
739
log.warn("Approx. number of bytes to read from scanner: %s" % utils.format_bytes(scan_size, True))
741
device.setOption('compression', scanner_compression)
744
valid_contrast = device.getOptionObj('contrast').constraint
745
if contrast >= int(valid_contrast[0]) and contrast <= int(valid_contrast[1]):
746
contrast = device.getOptionObj('contrast').limitAndSet(contrast)
748
log.warn("Invalid contrast. Contrast range is (%d, %d). Using closest valid contrast of %d " % (int(valid_contrast[0]), int(valid_contrast[1]), contrast))
749
if contrast < int(valid_contrast[0]):
750
contrast = int(valid_contrast[0])
751
elif contrast > int(valid_contrast[1]):
752
contrast = int(valid_contrast[1])
755
device.setOption('contrast', contrast)
758
valid_brightness = device.getOptionObj('brightness').constraint
759
if brightness >= int(valid_brightness[0]) and brightness <= int(valid_brightness[1]):
760
brightness = device.getOptionObj('brightness').limitAndSet(brightness)
762
log.warn("Invalid brightness. Brightness range is (%d, %d). Using closest valid brightness of %d " % (int(valid_brightness[0]), int(valid_brightness[1]), brightness))
763
if brightness < int(valid_brightness[0]):
764
brightness = int(valid_brightness[0])
765
elif brightness > int(valid_brightness[1]):
766
brightness = int(valid_brightness[1])
767
device.setOption('brightness', brightness)
769
if brx - tlx <= 0.0 or bry - tly <= 0.0:
770
log.error("Invalid scan area (width or height is negative).")
774
log.info("Resolution: %ddpi" % res)
775
log.info("Mode: %s" % scan_mode)
776
log.info("Compression: %s" % scanner_compression)
778
log.info("Contrast: %d" % contrast)
780
log.info("Brightness: %d" % brightness)
782
log.info("Scan area (mm):")
783
log.info(" Top left (x,y): (%fmm, %fmm)" % (tlx, tly))
784
log.info(" Bottom right (x,y): (%fmm, %fmm)" % (brx, bry))
785
log.info(" Width: %fmm" % (brx - tlx))
786
log.info(" Height: %fmm" % (bry - tly))
789
units = page_units # for display purposes only
790
log.info("Page size: %s" % size_desc)
792
log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units)
795
log.info("Scan area (in):")
796
log.info(" Top left (x,y): (%fin, %fin)" % (tlx/25.4, tly/25.4))
797
log.info(" Bottom right (x,y): (%fin, %fin)" % (brx/25.4, bry/25.4))
798
log.info(" Width: %fin" % ((brx - tlx)/25.4))
799
log.info(" Height: %fin" % ((bry - tly)/25.4))
802
log.info("Scan area (cm):")
803
log.info(" Top left (x,y): (%fcm, %fcm)" % (tlx/10.0, tly/10.0))
804
log.info(" Bottom right (x,y): (%fcm, %fcm)" % (brx/10.0, bry/10.0))
805
log.info(" Width: %fcm" % ((brx - tlx)/10.0))
806
log.info(" Height: %fcm" % ((bry - tly)/10.0))
809
log.info("Scan area (px @ %ddpi):" % res)
810
log.info(" Top left (x,y): (%fpx, %fpx)" % (tlx*res/25.4, tly*res/25.4))
811
log.info(" Bottom right (x,y): (%fpx, %fpx)" % (brx*res/25.4, bry*res/25.4))
812
log.info(" Width: %fpx" % ((brx - tlx)*res/25.4))
813
log.info(" Height: %fpx" % ((bry - tly)*res/25.4))
816
log.info("Scan area (pt):")
817
log.info(" Top left (x,y): (%fpt, %fpt)" % (tlx/0.3528, tly/0.3528))
818
log.info(" Bottom right (x,y): (%fpt, %fpt)" % (brx/0.3528, bry/0.3528))
819
log.info(" Width: %fpt" % ((brx - tlx)/0.3528))
820
log.info(" Height: %fpt" % ((bry - tly)/0.3528))
822
log.info("Destination(s): %s" % ', '.join(dest))
825
log.info("Output file: %s" % output)
827
update_queue = queue.Queue()
828
event_queue = queue.Queue()
830
available_scan_mode = device.getOptionObj("mode").constraint
831
available_scan_mode = [x.lower() for x in available_scan_mode]
832
log.debug("Supported modes: %s size=%d" % (available_scan_mode,len(available_scan_mode)))
833
if scan_mode.lower() not in available_scan_mode:
834
log.warn("Device doesn't support %s mode. Continuing with %s mode."%(scan_mode,available_scan_mode[0]))
835
scan_mode = available_scan_mode[0]
837
device.setOption("mode", scan_mode)
840
#For some devices, resolution is changed when we set 'source'.
841
#Hence we need to set resolution here, after setting the 'source'
842
device.setOption("resolution", res)
844
if 'file' in dest and not output:
845
log.warn("File destination enabled with no output file specified.")
848
log.info("Setting output format to PDF for ADF mode.")
849
output = utils.createSequencedFilename("hpscan", ".pdf")
852
if scan_mode == 'gray':
853
log.info("Setting output format to PNG for greyscale mode.")
854
output = utils.createSequencedFilename("hpscan", ".png")
857
log.info("Setting output format to JPEG for color/lineart mode.")
858
output = utils.createSequencedFilename("hpscan", ".jpg")
861
log.warn("Defaulting to '%s'." % output)
865
output_type = os.path.splitext(output)[1].lower()[1:]
866
if output_type == 'jpg':
871
if output_type and output_type not in ('jpeg', 'png', 'pdf'):
872
log.error("Invalid output file format. File formats must be 'jpeg', 'png', or 'pdf'.")
875
if adf and output_type and output_type != 'pdf':
876
log.error("ADF scans must be saved in PDF file format.")
879
log.info("\nWarming up...")
892
log.info("\nPage %d: Scanning..." % page)
894
log.info("\nScanning...")
900
ok, expected_bytes, status = device.startScan("RGBA", update_queue, event_queue)
901
# Note: On some scanners (Marvell) expected_bytes will be < 0 (if lines == -1)
902
log.debug("expected_bytes = %d" % expected_bytes)
903
except scanext.error as e:
904
sane.reportError(e.args[0])
906
except KeyboardInterrupt:
907
log.error("Aborted.")
911
if adf and status == scanext.SANE_STATUS_NO_DOCS:
913
log.error("No document(s). Please load documents and try again.")
916
log.info("Out of documents. Scanned %d pages total." % (page-1))
920
if expected_bytes > 0:
922
log.debug("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes))
924
log.debug("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes))
926
device.waitForScanActive()
928
pm = tui.ProgressMeter("Reading data:")
930
while device.isScanActive():
931
while update_queue.qsize():
933
status, bytes_read = update_queue.get(0)
935
if not log.is_debug():
936
if expected_bytes > 0:
937
pm.update(int(100*bytes_read/expected_bytes),
938
utils.format_bytes(bytes_read))
941
utils.format_bytes(bytes_read))
943
if status != scanext.SANE_STATUS_GOOD:
944
log.error("Error in reading data. Status=%d bytes_read=%d." % (status, bytes_read))
953
except KeyboardInterrupt:
954
log.error("Aborted.")
958
# Make sure queue is cleared out...
959
while update_queue.qsize():
960
status, bytes_read = update_queue.get(0)
962
if not log.is_debug():
963
if expected_bytes > 0:
964
pm.update(int(100*bytes_read/expected_bytes),
965
utils.format_bytes(bytes_read))
968
utils.format_bytes(bytes_read))
970
# For Marvell devices, making scan progress bar to 100%
971
if bytes_read and bytes_read != expected_bytes:
972
pm.update(int(100),utils.format_bytes(bytes_read))
976
log.info("Read %s from scanner." % utils.format_bytes(bytes_read))
978
buffer, format, format_name, pixels_per_line, \
979
lines, depth, bytes_per_line, pad_bytes, total_read, total_write = device.getScan()
981
log.debug("PPL=%d lines=%d depth=%d BPL=%d pad=%d total_read=%d total_write=%d" %
982
(pixels_per_line, lines, depth, bytes_per_line, pad_bytes, total_read, total_write))
984
#For Marvell devices, expected bytes is not same as total_read
985
if lines == -1 or total_read != expected_bytes:
986
lines = int(total_read / bytes_per_line)
988
if scan_mode in ('color', 'gray'):
990
im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
993
log.error("Did not read enough data from scanner (I/O Error?)")
995
elif scan_mode == 'lineart':
997
pixels_per_line = bytes_per_line * 8 # Calculation of pixels_per_line for Lineart must be 8 time of bytes_per_line
998
# Otherwise, scanned image will be corrupted (slanted)
999
im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
1000
'raw', 'RGBA', 0, 1).convert('L')
1002
log.error("Did not read enough data from scanner (I/O Error?)")
1005
if adf or output_type == 'pdf':
1006
temp_output = utils.createSequencedFilename("hpscan_pg%d_" % page, ".png")
1007
adf_page_files.append(temp_output)
1008
im.save(temp_output)
1009
#log.debug("Saved page %d to file %s" % (page, temp_output))
1011
log.error("No data read.")
1014
if not adf or (adf and no_docs):
1020
log.info("Closing device.")
1023
if adf or output_type == 'pdf':
1025
from reportlab.pdfgen import canvas
1027
log.error("PDF output requires ReportLab.")
1031
output = utils.createSequencedFilename("hpscan", ".pdf")
1033
c = canvas.Canvas(output, (brx/0.3528, bry/0.3528))
1035
for p in adf_page_files:
1036
#log.info("Processing page %s..." % p)
1037
image = Image.open(p)
1040
c.drawInlineImage(image, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
1042
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.")
1044
except AssertionError as e:
1047
log.note("You might be running an older version of reportlab. Please update to the latest version")
1048
log.note("More information is available at http://hplipopensource.com/node/369")
1050
except Exception as e:
1052
log.note("Try Updating to reportlab version >= 3.2")
1058
log.info("Saving to file %s" % output)
1060
log.info("Viewing PDF file in %s" % pdf_viewer)
1061
cmd = "%s %s &" % (pdf_viewer, output)
1062
os_utils.execute(cmd)
1066
if resize < 1 or resize > 400:
1067
log.error("Resize parameter is incorrect. Resize must be 0% < resize < 400%.")
1068
log.error("Using resize value of 100%.")
1070
new_w = int(pixels_per_line * resize / 100)
1071
new_h = int(lines * resize / 100)
1072
log.info("Resizing image from %dx%d to %dx%d..." % (pixels_per_line, lines, new_w, new_h))
1073
im = im.resize((new_w, new_h), Image.ANTIALIAS)
1077
log.info("\nOutputting to destination 'file':")
1078
log.info("Saving to file %s" % output)
1082
except IOError as e:
1083
log.error("Error saving file: %s (I/O)" % e)
1089
except ValueError as e:
1090
log.error("Error saving file: %s (PIL)" % e)
1101
if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'print' in dest) \
1104
output_fd, output = utils.make_temp_file(suffix='.png')
1107
except IOError as e:
1108
log.error("Error saving temporary file: %s" % e)
1121
log.info("\nSending to destination '%s':" % d)
1125
from reportlab.pdfgen import canvas
1127
log.error("PDF output requires ReportLab.")
1130
pdf_output = utils.createSequencedFilename("hpscan", ".pdf")
1131
c = canvas.Canvas(pdf_output, (brx/0.3528, bry/0.3528))
1134
c.drawInlineImage(im, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
1136
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.")
1140
log.info("Saving to file %s" % pdf_output)
1142
log.info("Viewing PDF file in %s" % pdf_viewer)
1143
cmd = "%s %s &" % (pdf_viewer, pdf_output)
1144
os_utils.execute(cmd)
1148
hp_print = utils.which("hp-print", True)
1150
hp_print = 'python ./print.py'
1152
if dest_printer is not None:
1153
cmd = '%s -p %s %s &' % (hp_print, dest_printer, output)
1154
elif dest_devUri is not None:
1155
tmp = dest_devUri.partition(":")[2]
1156
dest_devUri = "hp:" + tmp
1157
cmd = '%s -d %s %s &' % (hp_print, dest_devUri, output)
1159
cmd = '%s %s &' % (hp_print, output)
1161
os_utils.execute(cmd)
1165
from email.mime.image import MIMEImage
1166
from email.mime.multipart import MIMEMultipart
1167
from email.mime.text import MIMEText
1170
from email.MIMEImage import MIMEImage
1171
from email.MIMEMultipart import MIMEMultipart
1172
from email.MIMEText import MIMEText
1174
log.error("hp-scan email destination requires Python 2.2+.")
1177
msg = MIMEMultipart()
1178
msg['Subject'] = email_subject
1179
msg['From'] = email_from
1180
msg['To'] = ','.join(email_to)
1181
msg.preamble = 'Scanned using hp-scan'
1184
txt = MIMEText(email_note)
1188
txt = MIMEText("attached: %s: %dx%d %s PNG image." %
1189
(os.path.basename(output), pixels_per_line, lines, scan_mode))
1191
txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode))
1195
fp = open(output, 'r')
1196
img = MIMEImage(fp.read())
1200
img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output))
1204
sendmail = utils.which("sendmail")
1207
sendmail = os.path.join(sendmail, 'sendmail')
1208
cmd = [sendmail,'-t','-r',email_from]
1210
log.debug(repr(cmd))
1213
sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1214
std_out, std_err = sp.communicate(msg.as_string())
1217
except OSError as e:
1222
log.error(repr(err))
1225
log.error("Mail send failed. 'sendmail' not found.")
1229
log.info("Viewing file in %s" % viewer)
1230
cmd = "%s %s &" % (viewer, output)
1231
os_utils.execute(cmd)
1233
log.error("Viewer not found.")
1237
log.info("Editing file in %s" % editor)
1238
cmd = "%s %s &" % (editor, output)
1239
os_utils.execute(cmd)
1241
log.error("Editor not found.")
1248
except KeyboardInterrupt:
1249
log.error("User exit")