~mimose/+junk/hplip-3.16.11

« back to all changes in this revision

Viewing changes to scan.py

  • Committer: guoyalong
  • Date: 2017-09-20 10:13:05 UTC
  • Revision ID: guoyalong@kylinos.cn-20170920101305-82zaolzpv1qghz29
Modified debian/control & debian/rules.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
#
 
4
# (c) Copyright 2003-2015 HP 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
 
 
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
import scanext
 
41
 
 
42
# Local
 
43
from base.g import *
 
44
from base.sixext import PY3
 
45
from base import tui, device, module, utils, os_utils
 
46
from prnt import cups
 
47
from scan import sane
 
48
 
 
49
 
 
50
username = prop.username
 
51
r = res = 300
 
52
scan_mode = 'gray'
 
53
tlx = None
 
54
tly = None
 
55
brx = None
 
56
bry = None
 
57
units = "mm"
 
58
output = ''
 
59
dest = []
 
60
email_from = ''
 
61
email_to = []
 
62
email_subject = 'hp-scan from %s' % socket.gethostname()
 
63
email_note = ''
 
64
resize = 100
 
65
contrast = 0
 
66
set_contrast = False
 
67
brightness = 0
 
68
set_brightness = False
 
69
brightness = 0
 
70
page_size = ''
 
71
size_desc = ''
 
72
page_units = 'mm'
 
73
default_res = 300
 
74
scanner_compression = 'JPEG'
 
75
adf = False
 
76
duplex = False
 
77
dest_printer = None
 
78
dest_devUri = None
 
79
 
 
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'),
 
104
    }
 
105
 
 
106
 
 
107
try:
 
108
    viewer = ''
 
109
    viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',]
 
110
    for v in viewer_list:
 
111
        vv = utils.which(v)
 
112
        if vv:
 
113
            viewer = os.path.join(vv, v)
 
114
            break
 
115
 
 
116
 
 
117
    editor = ''
 
118
    editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',]
 
119
    for e in editor_list:
 
120
        ee = utils.which(e)
 
121
        if ee:
 
122
            editor = os.path.join(ee, e)
 
123
            break
 
124
 
 
125
    pdf_viewer = ''
 
126
    pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',]
 
127
    for v in pdf_viewer_list:
 
128
        vv = utils.which(v)
 
129
        if vv:
 
130
            pdf_viewer = os.path.join(vv, v)
 
131
            break
 
132
 
 
133
    mod = module.Module(__mod__, __title__, __version__, __doc__, None,
 
134
                        (INTERACTIVE_MODE,))
 
135
 
 
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),
 
152
        utils.USAGE_SPACE,
 
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),
 
178
        utils.USAGE_SPACE,
 
179
        ("[OPTIONS] ('file' dest)", "", "header", False),
 
180
        ("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False),
 
181
        utils.USAGE_SPACE,
 
182
        ("[OPTIONS] ('pdf' dest)", "", "header", False),
 
183
        ("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False),
 
184
        utils.USAGE_SPACE,
 
185
        ("[OPTIONS] ('viewer' dest)", "", "header", False),
 
186
        ("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False),
 
187
        utils.USAGE_SPACE,
 
188
        ("[OPTIONS] ('editor' dest)", "", "header", False),
 
189
        ("Image editor application:", "-e<editor> or --editor=<editor>", "option", False),
 
190
        utils.USAGE_SPACE,
 
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),
 
198
        utils.USAGE_SPACE,
 
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),
 
202
        utils.USAGE_SPACE,
 
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),],
 
205
        see_also_list=[])
 
206
 
 
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=', 
 
224
                         ])
 
225
 
 
226
 
 
227
    sane.init()
 
228
    sane_devices = sane.getDevices()
 
229
    devicelist = {}
 
230
    for d, mfg, mdl, t in sane_devices:
 
231
        try:
 
232
            devicelist[d]
 
233
        except KeyError:
 
234
            devicelist[d] = [mdl]
 
235
        else:
 
236
            devicelist[d].append(mdl)
 
237
    sane.deInit()
 
238
    device_uri = mod.getDeviceUri(device_uri, printer_name,
 
239
        back_end_filter=['hpaio'], filter={'scan-type': (operator.gt, 0)}, devices=devicelist)
 
240
 
 
241
    if not device_uri:
 
242
        sys.exit(1)
 
243
 
 
244
    for o, a in opts:
 
245
        if o in ('-x', '--compression'):
 
246
            a = a.strip().lower()
 
247
 
 
248
            if a in ('jpeg', 'jpg'):
 
249
                scanner_compression = 'JPEG'
 
250
 
 
251
            elif a in ('raw', 'none'):
 
252
                scanner_compression = 'None'
 
253
 
 
254
            else:
 
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'
 
258
 
 
259
        elif o == 'raw':
 
260
            scanner_compression = 'None'
 
261
 
 
262
        elif o == 'jpeg':
 
263
            scanner_compression = 'JPEG'
 
264
 
 
265
        elif o in ('--color', '--colour'):
 
266
            scan_mode = 'color'
 
267
 
 
268
        elif o in ('--lineart', '--line-art', '--bw'):
 
269
            scan_mode = 'lineart'
 
270
 
 
271
        elif o in ('--gray', '--grayscale', '--gray-scale', '--grey', '--greyscale', '--grey-scale'):
 
272
            scan_mode = 'gray'
 
273
 
 
274
        elif o in ('-m', '--mode'):
 
275
            a = a.strip().lower()
 
276
 
 
277
            if a in ('color', 'colour'):
 
278
                scan_mode = 'color'
 
279
 
 
280
            elif a in ('lineart', 'bw', 'b&w'):
 
281
                scan_mode = 'lineart'
 
282
 
 
283
            elif a in ('gray', 'grayscale', 'grey', 'greyscale'):
 
284
                scan_mode = 'gray'
 
285
 
 
286
            else:
 
287
                log.error("Invalid mode. Using default of 'gray'.")
 
288
                log.error("Valid modes are 'color', 'lineart', or 'gray'.")
 
289
                scan_mode = 'gray'
 
290
 
 
291
        elif o in ('--res', '--resolution', '-r'):
 
292
            try:
 
293
                r = int(a.strip())
 
294
            except ValueError:
 
295
                log.error("Invalid value for resolution.")
 
296
                res = default_res
 
297
            else:
 
298
                res = r
 
299
 
 
300
        elif o in ('-t', '--units', '--unit'):
 
301
            a = a.strip().lower()
 
302
 
 
303
            if a in ('in', 'inch', 'inches'):
 
304
                units = 'in'
 
305
 
 
306
            elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'):
 
307
                units = 'mm'
 
308
 
 
309
            elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'):
 
310
                units = 'cm'
 
311
 
 
312
            elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'):
 
313
                units = 'px'
 
314
 
 
315
            elif a in ('pt', 'point', 'points', 'pts'):
 
316
                units = 'pt'
 
317
 
 
318
            else:
 
319
                log.error("Invalid units. Using default of 'mm'.")
 
320
                units = 'mm'
 
321
 
 
322
        elif o == '--tlx':
 
323
            a = a.strip().lower()
 
324
            try:
 
325
                f = float(a)
 
326
            except ValueError:
 
327
                log.error("Invalid value for tlx.")
 
328
            else:
 
329
                tlx = f
 
330
 
 
331
        elif o == '--tly':
 
332
            a = a.strip().lower()
 
333
            try:
 
334
                f = float(a)
 
335
            except ValueError:
 
336
                log.error("Invalid value for tly.")
 
337
            else:
 
338
                tly = f
 
339
 
 
340
        elif o == '--brx':
 
341
            a = a.strip().lower()
 
342
            try:
 
343
                f = float(a)
 
344
            except ValueError:
 
345
                log.error("Invalid value for brx.")
 
346
            else:
 
347
                brx = f
 
348
 
 
349
        elif o == '--bry':
 
350
            a = a.strip().lower()
 
351
            try:
 
352
                f = float(a)
 
353
            except ValueError:
 
354
                log.error("Invalid value for bry.")
 
355
            else:
 
356
                bry = f
 
357
 
 
358
        elif o in ('-a', '--area'): # tlx, tly, brx, bry
 
359
            a = a.strip().lower()
 
360
            try:
 
361
                tlx, tly, brx, bry = a.split(',')[:4]
 
362
            except ValueError:
 
363
                log.error("Invalid scan area. Using defaults.")
 
364
            else:
 
365
                try:
 
366
                    tlx = float(tlx)
 
367
                except ValueError:
 
368
                    log.error("Invalid value for tlx. Using defaults.")
 
369
                    tlx = None
 
370
 
 
371
                try:
 
372
                    tly = float(tly)
 
373
                except ValueError:
 
374
                    log.error("Invalid value for tly. Using defaults.")
 
375
                    tly = None
 
376
 
 
377
                try:
 
378
                    brx = float(brx)
 
379
                except ValueError:
 
380
                    log.error("Invalid value for brx. Using defaults.")
 
381
                    brx = None
 
382
 
 
383
                try:
 
384
                    bry = float(bry)
 
385
                except ValueError:
 
386
                    log.error("Invalid value for bry. Using defaults.")
 
387
                    bry = None
 
388
 
 
389
        elif o == '--box': # tlx, tly, w, h
 
390
            a = a.strip().lower()
 
391
            try:
 
392
                tlx, tly, width, height = a.split(',')[:4]
 
393
            except ValueError:
 
394
                log.error("Invalid scan area. Using defaults.")
 
395
            else:
 
396
                try:
 
397
                    tlx = float(tlx)
 
398
                except ValueError:
 
399
                    log.error("Invalid value for tlx. Using defaults.")
 
400
                    tlx = None
 
401
 
 
402
                try:
 
403
                    tly = float(tly)
 
404
                except ValueError:
 
405
                    log.error("Invalid value for tly. Using defaults.")
 
406
                    tly = None
 
407
 
 
408
                if tlx is not None:
 
409
                    try:
 
410
                        brx = float(width) + tlx
 
411
                    except ValueError:
 
412
                        log.error("Invalid value for width. Using defaults.")
 
413
                        brx = None
 
414
                else:
 
415
                    log.error("Cannot calculate brx since tlx is invalid. Using defaults.")
 
416
                    brx = None
 
417
 
 
418
                if tly is not None:
 
419
                    try:
 
420
                        bry = float(height) + tly
 
421
                    except ValueError:
 
422
                        log.error("Invalid value for height. Using defaults.")
 
423
                        bry = None
 
424
                else:
 
425
                    log.error("Cannot calculate bry since tly is invalid. Using defaults.")
 
426
                    bry = None
 
427
 
 
428
        elif o == '--size':
 
429
            size = a.strip().lower()
 
430
            if size in PAGE_SIZES:
 
431
                brx, bry, size_desc, page_units = PAGE_SIZES[size]
 
432
                tlx, tly = 0, 0
 
433
                page_size = size
 
434
            else:
 
435
                log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(list(PAGE_SIZES.keys())))
 
436
                log.error("Using defaults.")
 
437
 
 
438
        elif o in ('-o', '--output', '-f', '--file'):
 
439
            output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip())))
 
440
 
 
441
            try:
 
442
                ext = os.path.splitext(output)[1]
 
443
            except IndexError:
 
444
                log.error("Invalid filename extension.")
 
445
                output = ''
 
446
                if 'file' in dest:
 
447
                    dest.remove('file')
 
448
            else:
 
449
                if ext.lower() not in ('.jpg', '.png', '.pdf'):
 
450
                    log.error("Only JPG (.jpg), PNG (.png) and PDF (.pdf) output files are supported.")
 
451
                    output = ''
 
452
                    if 'file' in dest:
 
453
                        dest.remove('file')
 
454
                else:
 
455
                    if os.path.exists(output):
 
456
                        log.warn("Output file '%s' exists. File will be overwritten." % output)
 
457
 
 
458
                    if 'file' not in dest:
 
459
                        dest.append('file')
 
460
 
 
461
        elif o in ('-s', '--dest', '--destination'):
 
462
            a = a.strip().lower().split(',')
 
463
            for aa in a:
 
464
                aa = aa.strip()
 
465
                if aa in ('file', 'viewer', 'editor', 'print', 'email', 'pdf') \
 
466
                    and aa not in dest:
 
467
                    dest.append(aa)
 
468
 
 
469
        elif o in ('--dd', '--dest-device'):
 
470
            dest_devUri = a.strip()
 
471
            if 'print' not in dest:
 
472
                dest.append('print')
 
473
 
 
474
        elif o in ('--dp', '--dest-printer'):
 
475
            dest_printer = a.strip()
 
476
            if 'print' not in dest:
 
477
                dest.append('print')
 
478
 
 
479
        elif o in ('-v', '--viewer'):
 
480
            a = a.strip()
 
481
            b = utils.which(a)
 
482
            if not b:
 
483
                log.error("Viewer application not found.")
 
484
            else:
 
485
                viewer = os.path.join(b, a)
 
486
                if 'viewer' not in dest:
 
487
                    dest.append('viewer')
 
488
 
 
489
        elif o in ('-e', '--editor'):
 
490
            a = a.strip()
 
491
            b = utils.which(a)
 
492
            if not b:
 
493
                log.error("Editor application not found.")
 
494
            else:
 
495
                editor = os.path.join(b, a)
 
496
                if 'editor' not in dest:
 
497
                    dest.append('editor')
 
498
 
 
499
        elif o == '--pdf':
 
500
            a = a.strip()
 
501
            b = utils.which(a)
 
502
            if not b:
 
503
                log.error("PDF viewer application not found.")
 
504
            else:
 
505
                pdf_viewer = os.path.join(b, a)
 
506
                if 'pdf' not in dest:
 
507
                    dest.append('pdf')
 
508
 
 
509
 
 
510
        elif o in ('--email-to', '--to'):
 
511
            email_to = a.split(',')
 
512
            if 'email' not in dest:
 
513
                dest.append('email')
 
514
 
 
515
        elif o in ('--email-from', '--from'):
 
516
            email_from = a
 
517
            if 'email' not in dest:
 
518
                dest.append('email')
 
519
 
 
520
        elif o in ('--email-subject', '--subject', '--about'):
 
521
            email_subject = a
 
522
            if 'email' not in dest:
 
523
                dest.append('email')
 
524
 
 
525
        elif o in ('--email-note', '--email-msg', '--msg', '--message', '--note', '--notes'):
 
526
            email_note = a
 
527
            if 'email' not in dest:
 
528
                dest.append('email')
 
529
 
 
530
        elif o == '--resize':
 
531
            a = a.replace("%", "")
 
532
            try:
 
533
                resize = int(a)
 
534
            except ValueError:
 
535
                resize = 100
 
536
                log.error("Invalid resize value. Using default of 100%.")
 
537
 
 
538
        elif o in ('-b', '--brightness'):
 
539
            try:
 
540
                set_brightness = True
 
541
                brightness = int(a.strip())
 
542
            except ValueError:
 
543
                log.error("Invalid brightness value. Using default of 0.")
 
544
                brightness = 0
 
545
 
 
546
        elif o in ('-c', '--contrast'):
 
547
            try:
 
548
                set_contrast = True
 
549
                contrast = int(a.strip())
 
550
            except ValueError:
 
551
                log.error("Invalid contrast value. Using default of 0.")
 
552
                contrast = 0
 
553
 
 
554
        elif o == '--adf':
 
555
            adf = True
 
556
            output_type = 'pdf'
 
557
        elif o in ('--dup', '--duplex'):
 
558
            duplex = True
 
559
            adf = True
 
560
            output_type = 'pdf'
 
561
 
 
562
    if not dest:
 
563
        log.warn("No destinations specified. Adding 'file' destination by default.")
 
564
        dest.append('file')
 
565
 
 
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.")
 
569
        dest.remove("email")
 
570
 
 
571
    if page_size:
 
572
        units = 'mm'
 
573
 
 
574
    if units == 'in':
 
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
 
579
 
 
580
    elif units == 'cm':
 
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
 
585
 
 
586
    elif units == 'pt':
 
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
 
591
 
 
592
    elif units == 'px':
 
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
 
598
 
 
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))
 
601
        tlx = brx = None
 
602
 
 
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))
 
605
        tly = bry = None
 
606
 
 
607
    if not prop.scan_build:
 
608
        log.error("Scanning disabled in build. Exiting")
 
609
        sys.exit(1)
 
610
 
 
611
    if mode == GUI_MODE:
 
612
        log.error("GUI mode is not implemented yet. Refer to 'hp-scan -h' for help.")
 
613
        sys.exit(1)
 
614
 
 
615
 
 
616
    else: # INTERACTIVE_MODE
 
617
        from base.sixext.moves import queue
 
618
 
 
619
        try:
 
620
            import subprocess
 
621
        except ImportError:
 
622
            # Pre-2.4 Python
 
623
            from base import subproc as subprocess
 
624
 
 
625
        try:
 
626
            from PIL import Image
 
627
        except ImportError:
 
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"))
 
631
            sys.exit(1)
 
632
 
 
633
        sane.init()
 
634
        devices = sane.getDevices()
 
635
 
 
636
        # Make sure SANE backend sees the device...
 
637
        for d, mfg, mdl, t in devices:
 
638
            if d == device_uri:
 
639
                break
 
640
        else:
 
641
            log.error("Unable to locate device %s using SANE backend hpaio:. Please check HPLIP installation." % device_uri)
 
642
            sys.exit(1)
 
643
 
 
644
        log.info(log.bold("Using device %s" % device_uri))
 
645
        log.info("Opening connection to device...")
 
646
 
 
647
        try:
 
648
            device = sane.openDevice(device_uri)
 
649
        except scanext.error as e:
 
650
            sane.reportError(e.args[0])
 
651
            sys.exit(1)
 
652
 
 
653
        try:
 
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.")
 
658
                sys.exit(1)
 
659
        except:
 
660
            log.error("Failed to get the source from device.")
 
661
 
 
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")
 
665
             adf = True
 
666
 
 
667
        if adf:
 
668
            try:
 
669
                if 'ADF' not in source_option:
 
670
                    log.error("Failed to set ADF mode. This device doesn't support ADF.")
 
671
                    sys.exit(1)
 
672
                else:
 
673
                    if duplex == True:
 
674
                        if 'Duplex' in source_option:
 
675
                            device.setOption("source", "Duplex")
 
676
                        else:
 
677
                            log.warn("Device doesn't support Duplex scanning. Continuing with Simplex ADF scan.")
 
678
                            device.setOption("source", "ADF")
 
679
                    else:
 
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)
 
684
                sys.exit(1)
 
685
 
 
686
        else:
 
687
            try:
 
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).")
 
692
 
 
693
 
 
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)
 
698
 
 
699
        scan_area = (brx - tlx) * (bry - tly) # mm^2
 
700
 
 
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]:
 
707
              res = valid_res[0]
 
708
           elif res > valid_res[1]:
 
709
              res = valid_res[1]
 
710
 
 
711
        else:
 
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]))
 
715
            res = valid_res[0]
 
716
            min_dist = sys.maxsize
 
717
            for x in valid_res:
 
718
                  if abs(r-x) < min_dist:
 
719
                        min_dist = abs(r-x)
 
720
                        res = x
 
721
 
 
722
        res = device.getOptionObj('resolution').limitAndSet(res)
 
723
        scan_px = scan_area * res * res / 645.16 # res is in DPI
 
724
 
 
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
 
729
        else: # lineart
 
730
            scan_size = scan_px // 8 
 
731
 
 
732
        if scan_size > 52428800: # 50MB
 
733
            if res > 600:
 
734
                log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.")
 
735
            else:
 
736
                log.warn("The scan current parameters will cause very large files to be created.")
 
737
 
 
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))
 
740
 
 
741
        device.setOption('compression', scanner_compression)
 
742
 
 
743
        if set_contrast:
 
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)
 
747
            else:
 
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])
 
753
 
 
754
 
 
755
            device.setOption('contrast', contrast)
 
756
 
 
757
        if set_brightness:
 
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)
 
761
            else:
 
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)
 
768
 
 
769
        if brx - tlx <= 0.0 or bry - tly <= 0.0:
 
770
            log.error("Invalid scan area (width or height is negative).")
 
771
            sys.exit(1)
 
772
 
 
773
        log.info("")
 
774
        log.info("Resolution: %ddpi" % res)
 
775
        log.info("Mode: %s" % scan_mode)
 
776
        log.info("Compression: %s" % scanner_compression)
 
777
        if(set_contrast):
 
778
            log.info("Contrast: %d" % contrast)
 
779
        if(set_brightness):
 
780
            log.info("Brightness: %d" % brightness)
 
781
        if units == 'mm':
 
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))
 
787
 
 
788
        if page_size:
 
789
            units = page_units # for display purposes only
 
790
            log.info("Page size: %s" % size_desc)
 
791
            if units != 'mm':
 
792
                log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units)
 
793
 
 
794
        if units == 'in':
 
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))
 
800
 
 
801
        elif units == 'cm':
 
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))
 
807
 
 
808
        elif units == 'px':
 
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))
 
814
 
 
815
        elif units == 'pt':
 
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))
 
821
 
 
822
        log.info("Destination(s): %s" % ', '.join(dest))
 
823
 
 
824
        if 'file' in dest:
 
825
            log.info("Output file: %s" % output)
 
826
 
 
827
        update_queue = queue.Queue()
 
828
        event_queue = queue.Queue()
 
829
 
 
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]
 
836
 
 
837
        device.setOption("mode", scan_mode)
 
838
 
 
839
 
 
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)
 
843
 
 
844
        if 'file' in dest and not output:
 
845
            log.warn("File destination enabled with no output file specified.")
 
846
 
 
847
            if adf:
 
848
               log.info("Setting output format to PDF for ADF mode.")
 
849
               output = utils.createSequencedFilename("hpscan", ".pdf")
 
850
               output_type = 'pdf'
 
851
            else:
 
852
               if scan_mode == 'gray':
 
853
                  log.info("Setting output format to PNG for greyscale mode.")
 
854
                  output = utils.createSequencedFilename("hpscan", ".png")
 
855
                  output_type = 'png'
 
856
               else:
 
857
                  log.info("Setting output format to JPEG for color/lineart mode.")
 
858
                  output = utils.createSequencedFilename("hpscan", ".jpg")
 
859
                  output_type = 'jpeg'
 
860
 
 
861
            log.warn("Defaulting to '%s'." % output)
 
862
 
 
863
        else:
 
864
            try:
 
865
               output_type = os.path.splitext(output)[1].lower()[1:]
 
866
               if output_type == 'jpg':
 
867
                  output_type = 'jpeg'
 
868
            except IndexError:
 
869
               output_type = ''
 
870
 
 
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'.")
 
873
            sys.exit(1)
 
874
 
 
875
        if adf and output_type and output_type != 'pdf':
 
876
            log.error("ADF scans must be saved in PDF file format.")
 
877
            sys.exit(1)
 
878
 
 
879
        log.info("\nWarming up...")
 
880
 
 
881
        no_docs = False
 
882
        page = 1
 
883
        adf_page_files = []
 
884
        #adf_pages = []
 
885
 
 
886
        cleanup_spinner()
 
887
        log.info("")
 
888
 
 
889
        try:
 
890
            while True:
 
891
                if adf:
 
892
                    log.info("\nPage %d: Scanning..." % page)
 
893
                else:
 
894
                    log.info("\nScanning...")
 
895
 
 
896
                bytes_read = 0
 
897
 
 
898
                try:
 
899
                    try:
 
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])
 
905
                        sys.exit(1)
 
906
                    except KeyboardInterrupt:
 
907
                        log.error("Aborted.")
 
908
                        device.cancelScan()
 
909
                        sys.exit(1)
 
910
 
 
911
                    if adf and status == scanext.SANE_STATUS_NO_DOCS:
 
912
                        if page-1 == 0:
 
913
                            log.error("No document(s). Please load documents and try again.")
 
914
                            sys.exit(0)
 
915
                        else:
 
916
                            log.info("Out of documents. Scanned %d pages total." % (page-1))
 
917
                            no_docs = True
 
918
                            break
 
919
 
 
920
                    if expected_bytes > 0:
 
921
                        if adf:
 
922
                            log.debug("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes))
 
923
                        else:
 
924
                            log.debug("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes))
 
925
 
 
926
                    device.waitForScanActive()
 
927
 
 
928
                    pm = tui.ProgressMeter("Reading data:")
 
929
 
 
930
                    while device.isScanActive():
 
931
                        while update_queue.qsize():
 
932
                            try:
 
933
                                status, bytes_read = update_queue.get(0)
 
934
 
 
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))
 
939
                                    else:
 
940
                                        pm.update(0,
 
941
                                            utils.format_bytes(bytes_read))
 
942
 
 
943
                                if status != scanext.SANE_STATUS_GOOD:
 
944
                                    log.error("Error in reading data. Status=%d bytes_read=%d." % (status, bytes_read))
 
945
                                    sys.exit(1)
 
946
 
 
947
                            except queue.Empty:
 
948
                                break
 
949
 
 
950
 
 
951
                        time.sleep(0.5)
 
952
 
 
953
                except KeyboardInterrupt:
 
954
                    log.error("Aborted.")
 
955
                    device.cancelScan()
 
956
                    sys.exit(1)
 
957
 
 
958
                # Make sure queue is cleared out...
 
959
                while update_queue.qsize():
 
960
                    status, bytes_read = update_queue.get(0)
 
961
 
 
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))
 
966
                        else:
 
967
                            pm.update(0,
 
968
                                utils.format_bytes(bytes_read))
 
969
 
 
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))
 
973
                log.info("")
 
974
 
 
975
                if bytes_read:
 
976
                    log.info("Read %s from scanner." % utils.format_bytes(bytes_read))
 
977
 
 
978
                    buffer, format, format_name, pixels_per_line, \
 
979
                        lines, depth, bytes_per_line, pad_bytes, total_read, total_write = device.getScan()
 
980
 
 
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))
 
983
 
 
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)
 
987
 
 
988
                    if scan_mode in ('color', 'gray'):
 
989
                        try:
 
990
                            im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
 
991
                                'raw', 'RGBA', 0, 1)
 
992
                        except ValueError:
 
993
                            log.error("Did not read enough data from scanner (I/O Error?)")
 
994
                            sys.exit(1)
 
995
                    elif scan_mode == 'lineart':
 
996
                        try:
 
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')
 
1001
                        except ValueError:
 
1002
                            log.error("Did not read enough data from scanner (I/O Error?)")
 
1003
                            sys.exit(1)
 
1004
 
 
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))
 
1010
                else:
 
1011
                    log.error("No data read.")
 
1012
                    sys.exit(1)
 
1013
 
 
1014
                if not adf or (adf and no_docs):
 
1015
                    break
 
1016
 
 
1017
                page += 1
 
1018
 
 
1019
        finally:
 
1020
            log.info("Closing device.")
 
1021
            device.cancelScan()
 
1022
 
 
1023
        if adf or output_type == 'pdf':
 
1024
            try:
 
1025
                from reportlab.pdfgen import canvas
 
1026
            except ImportError:
 
1027
                log.error("PDF output requires ReportLab.")
 
1028
                sys.exit(1)
 
1029
 
 
1030
            if not output:
 
1031
                output = utils.createSequencedFilename("hpscan", ".pdf")
 
1032
 
 
1033
            c = canvas.Canvas(output, (brx/0.3528, bry/0.3528))
 
1034
 
 
1035
            for p in adf_page_files:
 
1036
                #log.info("Processing page %s..." % p)
 
1037
                image = Image.open(p)
 
1038
 
 
1039
                try:
 
1040
                    c.drawInlineImage(image, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
 
1041
                except NameError:
 
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.")
 
1043
                    sys.exit(1)
 
1044
                except AssertionError as e:
 
1045
                    log.error(e)
 
1046
                    if PY3:
 
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")
 
1049
                        sys.exit(1)
 
1050
                except Exception as e:
 
1051
                    log.error(e)
 
1052
                    log.note("Try Updating to reportlab version >= 3.2")
 
1053
                    sys.exit(1)
 
1054
 
 
1055
                c.showPage()
 
1056
                os.unlink(p)
 
1057
 
 
1058
            log.info("Saving to file %s" % output)
 
1059
            c.save()
 
1060
            log.info("Viewing PDF file in %s" % pdf_viewer)
 
1061
            cmd = "%s %s &" % (pdf_viewer, output)
 
1062
            os_utils.execute(cmd)
 
1063
            sys.exit(0)
 
1064
 
 
1065
        if resize != 100:
 
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%.")
 
1069
            else:
 
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)
 
1074
 
 
1075
        file_saved = False
 
1076
        if 'file' in dest:
 
1077
            log.info("\nOutputting to destination 'file':")
 
1078
            log.info("Saving to file %s" % output)
 
1079
 
 
1080
            try:
 
1081
                im.save(output)
 
1082
            except IOError as e:
 
1083
                log.error("Error saving file: %s (I/O)" % e)
 
1084
                try:
 
1085
                    os.remove(output)
 
1086
                except OSError:
 
1087
                    pass
 
1088
                sys.exit(1)
 
1089
            except ValueError as e:
 
1090
                log.error("Error saving file: %s (PIL)" % e)
 
1091
                try:
 
1092
                    os.remove(output)
 
1093
                except OSError:
 
1094
                    pass
 
1095
                sys.exit(1)
 
1096
 
 
1097
            file_saved = True
 
1098
            dest.remove("file")
 
1099
 
 
1100
        temp_saved = False
 
1101
        if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'print' in dest) \
 
1102
            and not file_saved:
 
1103
 
 
1104
            output_fd, output = utils.make_temp_file(suffix='.png')
 
1105
            try:
 
1106
                im.save(output)
 
1107
            except IOError as e:
 
1108
                log.error("Error saving temporary file: %s" % e)
 
1109
 
 
1110
                try:
 
1111
                    os.remove(output)
 
1112
                except OSError:
 
1113
                    pass
 
1114
 
 
1115
                sys.exit(1)
 
1116
 
 
1117
            os.close(output_fd)
 
1118
            temp_saved = True
 
1119
 
 
1120
        for d in dest:
 
1121
            log.info("\nSending to destination '%s':" % d)
 
1122
 
 
1123
            if d == 'pdf':
 
1124
                try:
 
1125
                    from reportlab.pdfgen import canvas
 
1126
                except ImportError:
 
1127
                    log.error("PDF output requires ReportLab.")
 
1128
                    continue
 
1129
 
 
1130
                pdf_output = utils.createSequencedFilename("hpscan", ".pdf")
 
1131
                c = canvas.Canvas(pdf_output, (brx/0.3528, bry/0.3528))
 
1132
 
 
1133
                try:
 
1134
                    c.drawInlineImage(im, (tlx/0.3528), (tly/0.3528), ((brx-tlx)/0.3528),((bry-tly)/0.3528))
 
1135
                except NameError:
 
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.")
 
1137
                    continue
 
1138
 
 
1139
                c.showPage()
 
1140
                log.info("Saving to file %s" % pdf_output)
 
1141
                c.save()
 
1142
                log.info("Viewing PDF file in %s" % pdf_viewer)
 
1143
                cmd = "%s %s &" % (pdf_viewer, pdf_output)
 
1144
                os_utils.execute(cmd)
 
1145
                sys.exit(0)
 
1146
 
 
1147
            elif d == 'print':
 
1148
                hp_print = utils.which("hp-print", True)
 
1149
                if not hp_print:
 
1150
                    hp_print = 'python ./print.py'
 
1151
                 
 
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)
 
1158
                else:
 
1159
                   cmd = '%s %s &' % (hp_print, output)
 
1160
                
 
1161
                os_utils.execute(cmd)
 
1162
 
 
1163
            elif d == 'email':
 
1164
                try:
 
1165
                    from email.mime.image import MIMEImage
 
1166
                    from email.mime.multipart import MIMEMultipart
 
1167
                    from email.mime.text import MIMEText
 
1168
                except ImportError:
 
1169
                    try:
 
1170
                        from email.MIMEImage import MIMEImage
 
1171
                        from email.MIMEMultipart import MIMEMultipart
 
1172
                        from email.MIMEText import MIMEText
 
1173
                    except ImportError:
 
1174
                        log.error("hp-scan email destination requires Python 2.2+.")
 
1175
                        continue
 
1176
 
 
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'
 
1182
 
 
1183
                if email_note:
 
1184
                    txt = MIMEText(email_note)
 
1185
                    msg.attach(txt)
 
1186
 
 
1187
                if file_saved:
 
1188
                    txt = MIMEText("attached: %s: %dx%d %s PNG image." %
 
1189
                        (os.path.basename(output), pixels_per_line, lines, scan_mode))
 
1190
                else:
 
1191
                    txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode))
 
1192
 
 
1193
                msg.attach(txt)
 
1194
 
 
1195
                fp = open(output, 'r')
 
1196
                img = MIMEImage(fp.read())
 
1197
                fp.close()
 
1198
 
 
1199
                if file_saved:
 
1200
                    img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output))
 
1201
 
 
1202
                msg.attach(img)
 
1203
 
 
1204
                sendmail = utils.which("sendmail")
 
1205
 
 
1206
                if sendmail:
 
1207
                    sendmail = os.path.join(sendmail, 'sendmail')
 
1208
                    cmd = [sendmail,'-t','-r',email_from]
 
1209
 
 
1210
                    log.debug(repr(cmd))
 
1211
                    err = None
 
1212
                    try:
 
1213
                        sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
1214
                        std_out, std_err = sp.communicate(msg.as_string())
 
1215
                        if std_err != '':
 
1216
                            err = std_err
 
1217
                    except OSError as e:
 
1218
                        err = str(e)
 
1219
                    cleanup_spinner()
 
1220
 
 
1221
                    if err:
 
1222
                        log.error(repr(err))
 
1223
 
 
1224
                else:
 
1225
                    log.error("Mail send failed. 'sendmail' not found.")
 
1226
 
 
1227
            elif d == 'viewer':
 
1228
                if viewer:
 
1229
                    log.info("Viewing file in %s" % viewer)
 
1230
                    cmd = "%s %s &" % (viewer, output)
 
1231
                    os_utils.execute(cmd)
 
1232
                else:
 
1233
                    log.error("Viewer not found.")
 
1234
 
 
1235
            elif d == 'editor':
 
1236
                if editor:
 
1237
                    log.info("Editing file in %s" % editor)
 
1238
                    cmd = "%s %s &" % (editor, output)
 
1239
                    os_utils.execute(cmd)
 
1240
                else:
 
1241
                    log.error("Editor not found.")
 
1242
 
 
1243
        device.freeScan()
 
1244
        device.closeScan()
 
1245
        sane.deInit()
 
1246
 
 
1247
 
 
1248
except KeyboardInterrupt:
 
1249
    log.error("User exit")
 
1250
 
 
1251
log.info("")
 
1252
log.info("Done.")
 
1253