SimulPiscator / AirSane

Publish SANE scanners to MacOS, Android, and Windows via Apple AirScan.
GNU General Public License v3.0
247 stars 26 forks source link

FUJITSU ScanSnap ix500 Sheet-Fed Scanner Appearing as Flatbed Scanner #45

Closed elsiehupp closed 3 years ago

elsiehupp commented 3 years ago

Hi @SimulPiscator, thanks for your quick response on the other issue! It seems to have fixed some things, but there are still some quirks to be ironed out. For example, the ESCL version of the scanner shows up as a flatbed, rather than sheet-fed. Scanning still works on macOS, but the scanner settings in Image Capture indeed think the device is a flatbed, so neither double-sided nor automatic multi-page scans work. Both double-sided and automatic multi-page scans work locally using SANE over USB, so this seems to be a problem with AirSane, not SANE.

Here's one set of outputs:

$ sudo -u saned scanimage -L
device `fujitsu:ScanSnap iX500:1200753' is a FUJITSU ScanSnap iX500 scanner
device `escl:http://127.0.0.1:8090' is a ESCL FUJITSU ScanSnap iX500 flatbed scanner
device `escl:http://192.168.4.34:8090' is a ESCL FUJITSU ScanSnap iX500 flatbed scanner
device `escl:http://fd10:e07c:45dd:1:c98a:733d:a197:6f53:8090' is a ESCL FUJITSU ScanSnap iX500 flatbed scanner

Notice that the ESCL scanners (which shouldn't be visible after your change), add the word "flatbed".

elsiehupp commented 3 years ago

Oh, and the debug output when I scan via Image Capture is as follows:

[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:41 -0500] "GET /eSCL/ScannerCapabilities" 200 2419 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:41 -0500] "GET /eSCL/ScannerIcon" 200 48698 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:43 -0500] "GET /eSCL/ScannerStatus" 200 413 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:43 -0500] "GET /eSCL/ScannerStatus" 200 413 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
document format requested: image/jpeg
document format used: image/png
using grayscale gamma of 0.555555
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:43 -0500] "POST /eSCL/ScanJobs" 201 0 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:43 -0500] "GET /eSCL/ScannerStatus" 200 844 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
sane_init(nullptr, nullptr)
sane_open(fujitsu:ScanSnap iX500:1200753) -> 0x7f6730003780
[mode] := "Gray" -> reload options
[source] := "ADF Duplex" -> reload options
[resolution] := 300dpi -> reload options
[tl-x] := 0mm
[tl-y] := 0mm
[br-x] := 216mm
[br-y] := 279mm -> reload options
sane_start(0x7f6730003780) with options:
[a3-paper] = 0
[a4-paper] = 0
[ald] = 0
[b4-paper] = 0
[b5-paper] = 0
[bgcolor] = "Default"
[br-x] = 215.872mm
[br-y] = 279.004mm
[brightness] = 0
[buffermode] = "Off"
[contrast] = 0
[cover-open] = 0
[df-action] = "Default"
[double-feed] = 0
[dropoutcolor] = "Default"
[email] = 0
[emphasis] = 0
[error-code] = 0
[function] = 0
[lowmemory] = 0
[manual-feed] = 0
[mode] = "Gray"
[offtimer] = 240
[omr-df] = 0
[overscan] = "Default"
[page-height] = 279.364mm
[page-loaded] = 0
[page-width] = 215.872mm
[power-save] = 0
[prepick] = "Default"
[resolution] = 300dpi
[rif] = 0
[scan] = 0
[side] = 0
[skew-angle] = 0
[sleeptimer] = 0
[source] = "ADF Duplex"
[swcrop] = 0
[swdeskew] = 0
[swdespeck] = 0
[swskip] = 0%
[tl-x] = 0mm
[tl-y] = 0mm
[top-edge] = 0
[variance] = 0
sane_read(0x7f6730003780): End of file reached
sane_cancel(0x7f6730003780)
sane_close(0x7f6730003780)
sane_exit()
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:48 -0500] "GET /eSCL/ScanJobs/b7c27316-818c-5d4d-b204-77211b832494/NextDocument" 200 5058300 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:48 -0500] "GET /eSCL/ScanJobs/b7c27316-818c-5d4d-b204-77211b832494/NextDocument" 404 0 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:48 -0500] "GET /eSCL/ScannerStatus" 200 861 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:48 -0500] "GET /eSCL/ScannerStatus" 200 861 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:48 -0500] "DELETE /eSCL/ScanJobs/b7c27316-818c-5d4d-b204-77211b832494" 200 0 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:48 -0500] "GET /eSCL/ScannerStatus" 200 861 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"
[fd10:e07c:45dd:1:854a:aee0:8e57:b34b] - - [24/Dec/2020:08:33:49 -0500] "GET /eSCL/ScannerStatus" 200 861 "" "AirScanScanner/58CFNetwork/1209.1Darwin/20.2.0"

It's worth noting that the phantom second scanner does not appear on Image Capture, just in the airsaned web interface.

SimulPiscator commented 3 years ago

The multiple escl:.* scanners are detected by the SANE escl backend, not by AirSane. There is no loop any more. You might just ignore them, or disable the escl backend in your /etc/sane.d/dll.conf.

SimulPiscator commented 3 years ago

Regarding the ADF issue: AirSane does not work well with ADF scanners because I don't have one to test. Apple Image Capture should allow you to choose "Feeder" as a scan source, though. The log shows that SANE settings for the scan are indeed using "ADF Duplex" as a source for the scan.

How exactly does the scanner behave if you use scanimage with source set to ADF Duplex?

elsiehupp commented 3 years ago

The multiple escl:.* scanners are detected by the SANE escl backend, not by AirSane. There is no loop any more. You might just ignore them, or disable the escl backend in your /etc/sane.d/dll.conf.

While this does work for me, personally, it could be a problem for a larger network where a Linux desktop shared one or more local scanners over ESCL but also used one or more remote scanners over ESCL. While the issue of locally shared ESCL scanners being detected by SANE is presumably an issue with SANE (and indeed I could share this as a bug report with them), the fact that the local ESCL scanners picked up by SANE don't actually work seems like a problem with AirSane.

FWIW, running scanimage --help --device-name [name] gives me the following output:

$ scanimage --help --device-name 'fujitsu:ScanSnap iX500:1200753'

[standard --help output removed]

Options specific to device `fujitsu:ScanSnap iX500:1200753':
  Standard:
    --source ADF Front|ADF Back|ADF Duplex [ADF Front]
        Selects the scan source (such as a document-feeder).
    --mode Lineart|Gray|Color [Lineart]
        Selects the scan mode (e.g., lineart, monochrome, or color).
    --resolution 50..600dpi (in steps of 1) [600]
        Sets the resolution of the scanned image.
  Geometry:
    --page-width 0..221.121mm (in steps of 0.0211639) [215.872]
        Specifies the width of the media.  Required for automatic centering of
        sheet-fed scans.
    --page-height 0..876.695mm (in steps of 0.0211639) [279.364]
        Specifies the height of the media.
    -l 0..215.872mm (in steps of 0.0211639) [0]
        Top-left x position of scan area.
    -t 0..279.364mm (in steps of 0.0211639) [0]
        Top-left y position of scan area.
    -x 0..215.872mm (in steps of 0.0211639) [215.872]
        Width of scan-area.
    -y 0..279.364mm (in steps of 0.0211639) [279.364]
        Height of scan-area.
  Enhancement:
    --brightness -127..127 (in steps of 1) [0]
        Controls the brightness of the acquired image.
    --contrast -127..127 (in steps of 1) [0]
        Controls the contrast of the acquired image.
    --threshold 0..255 (in steps of 1) [0]
        Select minimum-brightness to get a white point
    --rif[=(yes|no)] [no]
        Reverse image format
    --ht-type Default|Dither|Diffusion [inactive]
        Control type of halftone filter
    --ht-pattern 0..3 (in steps of 1) [inactive]
        Control pattern of halftone filter
    --emphasis -128..127 (in steps of 1) [0]
        Negative to smooth or positive to sharpen image
    --variance 0..255 (in steps of 1) [0]
        Set SDTC variance rate (sensitivity), 0 equals 127
  Advanced:
    --ald[=(yes|no)] [no]
        Scanner detects paper lower edge. May confuse some frontends.
    --df-action Default|Continue|Stop [Default]
        Action following double feed error
    --df-skew[=(yes|no)] [inactive]
        Enable double feed error due to skew
    --df-thickness[=(yes|no)] [inactive]
        Enable double feed error due to paper thickness
    --df-length[=(yes|no)] [inactive]
        Enable double feed error due to paper length
    --df-diff Default|10mm|15mm|20mm [inactive]
        Difference in page length to trigger double feed error
    --bgcolor Default|White|Black [Default]
        Set color of background for scans. May conflict with overscan option
    --dropoutcolor Default|Red|Green|Blue [Default]
        One-pass scanners use only one color during gray or binary scanning,
        useful for colored paper or ink
    --buffermode Default|Off|On [Off]
        Request scanner to read pages quickly from ADF into internal memory
    --prepick Default|Off|On [Default]
        Request scanner to grab next page from ADF
    --overscan Default|Off|On [Default]
        Collect a few mm of background on top side of scan, before paper
        enters ADF, and increase maximum scan area beyond paper size, to allow
        collection on remaining sides. May conflict with bgcolor option
    --sleeptimer 0..60 (in steps of 1) [0]
        Time in minutes until the internal power supply switches to sleep mode
    --offtimer 0..960 (in steps of 1) [240]
        Time in minutes until the internal power supply switches the scanner
        off. Will be rounded to nearest 15 minutes. Zero means never power off.
    --lowmemory[=(yes|no)] [no]
        Limit driver memory usage for use in embedded systems. Causes some
        duplex transfers to alternate sides on each call to sane_read. Value of
        option 'side' can be used to determine correct image. This option
        should only be used with custom front-end software.
    --swdeskew[=(yes|no)] [no]
        Request driver to rotate skewed pages digitally.
    --swdespeck 0..9 (in steps of 1) [0]
        Maximum diameter of lone dots to remove from scan.
    --swcrop[=(yes|no)] [no]
        Request driver to remove border from pages digitally.
    --swskip 0..100% (in steps of 0.100006) [0]
        Request driver to discard pages with low percentage of dark pixels
  Sensors:

versus:

$ scanimage --help --device-name 'escl:http://127.0.0.1:8090'

[standard --help output removed]

Options specific to device `escl:http://127.0.0.1:8090':
  Scan mode:
    --mode Gray|Color [Gray]
        Selects the scan mode (e.g., lineart, monochrome, or color).
    --resolution 50|75|150|300|600dpi [50]
        Sets the resolution of the scanned image.
    --preview[=(yes|no)] [no]
        Request a preview-quality scan.
    --preview-in-gray[=(yes|no)] [no]
        Request that all previews are done in monochrome mode.  On a
        three-pass scanner this cuts down the number of passes to one and on a
        one-pass scanner, it reduces the memory requirements and scan-time of
        the preview.
  Geometry:
    -l 0..215.9mm [0]
        Top-left x position of scan area.
    -t 0..279.4mm [0]
        Top-left y position of scan area.
    -x 0..215.9mm [0]
        Width of scan-area.
    -y 0..279.4mm [0]
        Height of scan-area.

Meanwhile, running scanimage (and actually trying to run a scan) gives me the following:

$ scanimage -v -d 'fujitsu:ScanSnap iX500:1200753' -o scan.png
scanimage: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanimage: acquiring gray frame
scanimage: read 4204200 bytes in total

(Note that it only scanned a single one-sided page with the default settings. It did, however, seem to accurately detect the page size, which is a feature of this particular scanner.)

By contrast:

$ scanimage -v -d 'escl:http://127.0.0.1:8090' -o scan.png
Capability : [(null)]
Capability : [image/jpeg]
Capability : [image/png]
scanimage: rounded value of br-x from 0 to 0
scanimage: rounded value of br-y from 0 to 0
scanimage: sane_start: Invalid argument

This output suggests that however you've got ESCL broadcast set up doesn't work with SANE as the client device.

It's also notable that scanimage -L doesn't specify which scanner at a given ESCL host to use, so the reason it isn't working might be related to the second, "phantom" scanner that appears on the AirSane web interface.

You specifically asked for the scanimage ADF duplex output, so here goes:

$ scanimage -v -d 'fujitsu:ScanSnap iX500:1200753' --source 'ADF Duplex' -o scan.png
scanimage: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanimage: acquiring gray frame
scanimage: read 4204200 bytes in total

scanimage did not, in fact, actually create a duplex multi-page scan.

By contrast, here's the output of simple-scan (in which I had previously specified in the GUI that it should do a duplex ADF scan with specific settings): https://gist.github.com/elsiehupp/3f8d0ba14de1bb5695bfff3095d8cf75

(I manually saved the scan and quit simple-scan via the GUI after it was done.)

By contrast, here is the output when I chose one of the ESCL scanners, instead: https://gist.github.com/elsiehupp/7d3c08a85fe70acba05e2c17750b4ad6

Particularly worth noting (as a subset of the above) is how simple-scan lists the devices in its debug output:

[+7.00s] DEBUG: scanner.vala:341: sane_get_devices () -> SANE_STATUS_GOOD
[+7.00s] DEBUG: scanner.vala:353: Device: name="fujitsu:ScanSnap iX500:1200753" vendor="FUJITSU" model="ScanSnap iX500" type="scanner"
[+7.00s] DEBUG: scanner.vala:353: Device: name="escl:http://127.0.0.1:8090" vendor="ESCL" model="FUJITSU ScanSnap iX500" type="flatbed scanner"
[+7.00s] DEBUG: scanner.vala:353: Device: name="escl:http://192.168.4.34:8090" vendor="ESCL" model="FUJITSU ScanSnap iX500" type="flatbed scanner"
[+7.00s] DEBUG: scanner.vala:353: Device: name="escl:http://fd10:e07c:45dd:1:c98a:733d:a197:6f53:8090" vendor="ESCL" model="FUJITSU ScanSnap iX500" type="flatbed scanner"

Perhaps ESCL scans would work better if AirSane passed through SANE metadata that correctly matched the metadata for the local scanner? At the very least the ESCL scanning might work on Linux computers. It's also worth noting that the ESCL scanners are specifically marked as "flatbed scanner" rather than just "scanner".

You also asked about how it worked if I chose "ADF Feeder" as a scan source in Image Capture. Well, I can't. Image Capture thinks the scanner is a flatbed and doesn't offer the option. It does, however, cleverly append subsequent scans to an existing PDF file, which is exactly the sort of solution one would use for, you know, a flatbed scanner.

Here is a screenshot of how the scanner appears in Image Capture:

Screen Shot 2020-12-24 at 10 22 17 AM

It's entirely possible that fixing the ESCL metadata for Linux would also make Image Capture more cleverly recognize the scanner. (Though it's worth emphasizing that this particular scanner model does not have a TWAIN driver, which is why I have this whole setup in the first place. The bloated native scanning application for this scanner on macOS works perfectly fine; it's just bloatware.)

SimulPiscator commented 3 years ago

Thank you for giving so many details.

It's entirely possible that fixing the ESCL metadata for Linux would also make Image Capture more cleverly recognize the scanner.

I don't think there are many options left for improvement here. AirSane already recognizes that the scanner is an ADF scanner and reports it as such. Apple Image Capture does not know about different types of ADF (Front, Back, Duplex, whatever), so AirSane simply reports a single "Feeder" input source. I have no idea why the SANE escl backend detects a type of "Flatbed Scanner". At any rate, this has nothing to with the "metadata" sent by AirSane.

Getting the SANE escl backend to work with AirSane published scanners might be an interesting exercise but I don't see any use case for it.

SimulPiscator commented 3 years ago

Meanwhile, running scanimage (and actually trying to run a scan) gives me the following:

$ scanimage -v -d 'fujitsu:ScanSnap iX500:1200753' -o scan.png scanimage: scanning image of size 5096x6600 pixels at 1 bits/pixel scanimage: acquiring gray frame scanimage: read 4204200 bytes in total

(Note that it only scanned a single one-sided page with the default settings. It did, however, seem to accurately detect the page size, which is a feature of this particular scanner.)

Did you put paper in the ADF before executing the command? How does scanimage behave if you choose the "ADF Duplex" source?

SimulPiscator commented 3 years ago

the second, "phantom" scanner that appears on the AirSane web interface.

I've updated the source code. The phantom scanner should not appear any more.

elsiehupp commented 3 years ago

Considering the issue here seems to be the result of some underlying issue with sane-fujitsu or scanimage, I went ahead and opened an issue on the sane-project GitLab Issue tracker. Let's see what they have to say!

By the way, I did try specifying --source "ADF Duplex" in scanimage (see above), and it didn't work (i.e. it still ran as "ADF Front").

SimulPiscator commented 3 years ago

By the way, I did try specifying --source "ADF Duplex" in scanimage (see above), and it didn't work (i.e. it still ran as "ADF Front").

Did it accept paper and actually produce scan output?

elsiehupp commented 3 years ago

Did it accept paper and actually produce scan output?

Yes, it produced a single image. According to the folks over at sane, though, I was misusing the command; scanning more than a single image requires --batch instead of -o. The following did, in fact, produce the anticipated result:

$ scanimage -v -d 'fujitsu:ScanSnap iX500:1200753' --source 'ADF Duplex' --batch
Output format is not set, using pnm as a default.
Scanning infinity pages, incrementing by 1, numbering from 1
Scanning page 1
scanimage: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanimage: acquiring gray frame
scanimage: read 4204200 bytes in total
Scanned page 1. (scanner status = 5)
Scanning page 2
scanimage: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanimage: acquiring gray frame
scanimage: read 4204200 bytes in total
Scanned page 2. (scanner status = 5)
Scanning page 3
scanimage: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanimage: acquiring gray frame
scanimage: read 4204200 bytes in total
Scanned page 3. (scanner status = 5)
Scanning page 4
scanimage: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanimage: acquiring gray frame
scanimage: read 4204200 bytes in total
Scanned page 4. (scanner status = 5)
Scanning page 5
scanimage: sane_start: Document feeder out of documents
Batch terminated, 4 pages scanned

As did scanadf:

$ scanadf -v --source 'ADF Duplex'
scanadf: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanadf: acquiring gray frame
Scanned document image-0001
scanadf: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanadf: acquiring gray frame
Scanned document image-0002
scanadf: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanadf: acquiring gray frame
Scanned document image-0003
scanadf: scanning image of size 5096x6600 pixels at 1 bits/pixel
scanadf: acquiring gray frame
Scanned document image-0004
Scanned 4 pages

I don't know if this mistake on my part has any correlation with the issue of the scanner appearing to Image Capture as a flatbed scanner. I did an extremely superficial search through the source code, and it isn't immediately apparent to me how AirSane talks to the backend.

I have not been able to retest AirSane, though, as the daemon has been repeatedly crashing on my computer since I installed one of the recent updates:

$ systemctl status airsaned
● airsaned.service - AirSane Imaging Service
     Loaded: loaded (/lib/systemd/system/airsaned.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Mon 2021-01-04 11:58:20 EST; 1min 4s ago
   Main PID: 27842 (code=exited, status=255/EXCEPTION)

Jan 04 11:58:20 Alpha airsaned[27842]:  --hotplug=true        repeat scanner search on hotplug event
Jan 04 11:58:20 Alpha airsaned[27842]:  --mdns-announce=true        announce scanners via mDNS
Jan 04 11:58:20 Alpha airsaned[27842]:  --local-scanners-only=true        ignore SANE network scanners
Jan 04 11:58:20 Alpha airsaned[27842]:  --options-file=/etc/airsane/options.conf        location of device options file
Jan 04 11:58:20 Alpha airsaned[27842]:  --ignore-list=/etc/airsane/ignore.conf        location of device ignore list
Jan 04 11:58:20 Alpha airsaned[27842]:  --random-uuids=false        generate random UUIDs on startup
Jan 04 11:58:20 Alpha airsaned[27842]:  --debug=false        log debug information to stderr
Jan 04 11:58:20 Alpha airsaned[27842]:  --help        show this help
Jan 04 11:58:20 Alpha systemd[1]: airsaned.service: Main process exited, code=exited, status=255/EXCEPTION
Jan 04 11:58:20 Alpha systemd[1]: airsaned.service: Failed with result 'exit-code'.
elsiehupp commented 3 years ago

I'm not sure why the daemon was crashing previously, but I tried restarting it with sudo systemctl restart airsaned, and it hasn't seemed to crash, again, yet.

At localhost:8090 the link to the scanner is inexplicably sending me to a dead URL at localhost:8091 instead of showing me the web scanning interface as it had been previously, but the scanner is still appearing as a flatbed in Image Capture, and scanning individual simplex pages there is, in fact, still working.

SimulPiscator commented 3 years ago

localhost:8091 should show you the web scanning interface of the first scanner. There is now one port for each scanner.

I'm not sure why the scanner is appearing as "Flatbed". If it's ADF only, there won't be an option to choose the input because Apple Image Capture does not know about different types of ADFs. If there is no preview appearing, then Apple Image Capture is treating the scanner as an ADF scanner.

elsiehupp commented 3 years ago

I looked closer at the link that wasn't working; it was <hostname>:8091, not localhost:8091. <hostname>:8091 works correctly on my Mac, while only localhost:8091 works correctly on my AirSane host.

That said, the web interface is only showing a single type of ADF input, "Feeder", and scanning using the web interface only scans a single one-sided page at a time. Additionally, the web interface shows a preview on the right-hand side, and if you click "Preview", it feeds a page through the scanner and shows a preview in the preview pane. In other words, Apple Image Capture is (mostly) faithfully reproducing the options and behavior from the web interface. (The main change seems to be that it doesn't show a choice of input source when there is only one available.)

Looking through your source code, I see that "Feeder" is a dummy term that links to either ADF Simplex or ADF Duplex (should it be available):

https://github.com/SimulPiscator/AirSane/blob/68ff5705be0df113690fd633c96951a37a29da32/server/scanner.cpp#L473-L476

I specifically remember from my Epson ADF scanner that Apple Image Capture is able to specify a choice of ADF Simplex or ADF Duplex, so it's possible that concealing this complexity behind a dummy term rather than feeding it (no pun intended) the actual source list reported by SANE is making Apple Image Capture more confused than not. My Epson ADF was actually also a flatbed scanner, and Apple Image Capture was able to distinguish and choose between flatbed, simplex, and duplex.

Additionally, I see that the list of modes is hardcoded:

https://github.com/SimulPiscator/AirSane/blob/68ff5705be0df113690fd633c96951a37a29da32/server/scanner.cpp#L438-L442

I don't know if you experimented at all with passing the list of modes reported by the printer rather than hardcoding them, but it's notable that the ix500 does not report a "Preview" mode with scanimage --help.

None of this full explains why AirSane is not running a batch job through SANE. As best I can tell, this issue comes from the fact that you are only calling sane_start() a single time in your own start() function in scanjob.cpp:

https://github.com/SimulPiscator/AirSane/blob/68ff5705be0df113690fd633c96951a37a29da32/server/scanjob.cpp#L459-L505

...whereas it appears from the SANE API that sane_start() explicitly only captures a single image: https://sane-project.gitlab.io/standard/api.html#sane-start

The way to run a batch job is to have some sort of loop (it's not exactly clear to me what) until the scanner reports SANE_STATUS_NO_DOCS: https://sane-project.gitlab.io/standard/api.html#c.SANE_STATUS_NO_DOCS

Again, this is based on an extremely cursory reading of your code and of the SANE API, and I have very little experience in C++ specifically (I have much more experience in C#), so it's relatively difficult for me to pick apart the exact path the program is taking, and it is far beyond me to be able to make any meaningful changes to the core SANE API calls. However, it looks like the place where you'd want to run a loop is in beginTransfer() or wherever beginTransfer() is called:

https://github.com/SimulPiscator/AirSane/blob/68ff5705be0df113690fd633c96951a37a29da32/server/scanjob.cpp#L445-L451

This is based on my understanding that each page needs a call to sane_start() and a call to sane_read(); either that, or the two functions are supposed to be called in some complicated set of interlocking loops. Or something like that? I'm not sure.

Anyway, I did some digging in the source code for scanimage, and the core set of loops for scanimage --batch seem to start around here and run through the end of the file: https://gitlab.com/sane-project/backends/-/blob/master/frontend/scanimage.c#L2708

Much more simply, the core while loop in scanadf starts here: https://gitlab.com/sane-project/frontends/-/blob/master/src/scanadf.c#L1320

... and is all of 40 lines long:

while (end < 0 || start <= end) 
    {
      snprintf(fname, sizeof (fname), outfmt, start);

      /* does the filename already exist? */
      if (no_overwrite) 
    {
      res = stat (fname, &statbuf);
      if (res == 0) 
        {
          status = SANE_STATUS_INVAL;
          fprintf (stderr, "Filename %s already exists; will not overwrite\n", fname);
        }
    }

      /* Scan the document */
      if (status == SANE_STATUS_GOOD) 
    status = scan_it_raw(fname, raw, script, use_pipe);

      /* Any scan errors? */
      if (status == SANE_STATUS_NO_DOCS) 
    {
      /* out of paper in the hopper; this is our normal exit */
      status = SANE_STATUS_GOOD;
      break;
    }
      else if (status == SANE_STATUS_EOF)
    {
      /* done with this doc */
      status = SANE_STATUS_GOOD;
      fprintf(stderr, "Scanned document %s\n", fname);
      scannedPages++;
      start++;
    }
      else 
    {
      /* unexpected error */
      fprintf(stderr, "%s\n", sane_strstatus(status));
      break;
    }
    }

Presumably one or the other of these could be adapted for use within scanjob.cpp.

SimulPiscator commented 3 years ago

I looked closer at the link that wasn't working; it was :8091, not localhost:8091. :8091 works correctly on my Mac, while only localhost:8091 works correctly on my AirSane host.

Your AirSane host should definitely be able to resolve its own hostname. If not, consider an alias for localhost in /etc/hosts.

What you write about scanimage requiring batch mode for an ADF is quite helpful. Unfortunately, I have no idea how to send multiple pages to an eSCL client such as the Apple Airscan Scanner plugin for Apple Image Capture. Rather, the client should send NextDocument requests to the server until the ADF is empty, when operating in "batch mode". Therefore, no batch loop is necessary in AirSane.

I think your user experience in Apple Image Capture comes from a plugin specific to your scanner, which may behave differently from what the Airscan Scanner plugin does.

elsiehupp commented 3 years ago

Looking more closely at the Network Scan Service Semantic Model and Service Interface, I found a few more classes that might be useful/applicable here.

First, AirSane advertises my scanner's capabilities as following:

<scan:ScannerCapabilities>
    <pwg:Version>2.0</pwg:Version>
    <pwg:MakeAndModel>FUJITSU ScanSnap iX500</pwg:MakeAndModel>
    <scan:UUID>643027f6-f4aa-540e-beb3-2b8dd8266466</scan:UUID>
    <scan:AdminURI>http://Alpha:8091/</scan:AdminURI>
    <scan:IconURI>http://Alpha:8091/ScannerIcon</scan:IconURI>
    <scan:Adf>
        <scan:AdfDuplexInputCaps>
            <scan:MinWidth>0</scan:MinWidth>
            <scan:MinHeight>0</scan:MinHeight>
            <scan:MaxWidth>2550</scan:MaxWidth>
            <scan:MaxHeight>3300</scan:MaxHeight>
            <scan:MaxPhysicalWidth>2550</scan:MaxPhysicalWidth>
            <scan:MaxPhysicalHeight>3300</scan:MaxPhysicalHeight>
            <scan:MaxScanRegions>1</scan:MaxScanRegions>
            <scan:SettingProfiles>
                <scan:SettingProfile name="0">
                    <scan:ColorModes>
                        <scan:ColorMode>Grayscale8</scan:ColorMode>
                        <scan:ColorMode>RGB24</scan:ColorMode>
                    </scan:ColorModes>
                    <scan:ColorSpaces>
                        <scan:ColorSpace>RGB</scan:ColorSpace>
                    </scan:ColorSpaces>
                    <scan:SupportedResolutions>
                        <scan:DiscreteResolutions>
                            <scan:DiscreteResolution>
                                <scan:XResolution>50</scan:XResolution>
                                <scan:YResolution>50</scan:YResolution>
                            </scan:DiscreteResolution>
                            <scan:DiscreteResolution>
                                <scan:XResolution>75</scan:XResolution>
                                <scan:YResolution>75</scan:YResolution>
                            </scan:DiscreteResolution>
                            <scan:DiscreteResolution>
                                <scan:XResolution>150</scan:XResolution>
                                <scan:YResolution>150</scan:YResolution>
                            </scan:DiscreteResolution>
                            <scan:DiscreteResolution>
                                <scan:XResolution>300</scan:XResolution>
                                <scan:YResolution>300</scan:YResolution>
                            </scan:DiscreteResolution>
                            <scan:DiscreteResolution>
                                <scan:XResolution>600</scan:XResolution>
                                <scan:YResolution>600</scan:YResolution>
                            </scan:DiscreteResolution>
                        </scan:DiscreteResolutions>
                    </scan:SupportedResolutions>
                    <scan:DocumentFormats>
                        <pwg:DocumentFormat>application/pdf</pwg:DocumentFormat>
                        <pwg:DocumentFormat>image/jpeg</pwg:DocumentFormat>
                        <pwg:DocumentFormat>image/png</pwg:DocumentFormat>
                    </scan:DocumentFormats>
                </scan:SettingProfile>
            </scan:SettingProfiles>
            <scan:SupportedIntents>
                <scan:SupportedIntent>Preview</scan:SupportedIntent>
                <scan:SupportedIntent>TextAndGraphic</scan:SupportedIntent>
                <scan:SupportedIntent>Photo</scan:SupportedIntent>
            </scan:SupportedIntents>
        </scan:AdfDuplexInputCaps>
    </scan:Adf>
</scan:ScannerCapabilities>

As far as I can tell, Adf and AdfDuplexInputCaps are not part of the PWG schema, and I have not been able to find the eSCL schema online. One thing I did see in the PWG specification is that the keyword "Feeder" is correct, but it seems insufficient for getting eSCL to see a scanner as capable of batching.

From the PWG documentation, we have the following:

ScanJobProcessingCapabilities: https://www.pwg.org/mfd/navigate/PwgSmRev1-185_ScanJobProcessingCapabilities.html

Which has the members BatchMode: https://www.pwg.org/mfd/navigate/PwgSmRev1-185_BatchMode.html

(Which is a Boolean)

And DocumentOutputMode: https://www.pwg.org/mfd/navigate/PwgSmRev1-185_DocumentOutputMode.html

With the allowed values: SingleDocumentSingleFile SingleDocumentMultipleFile MultipleDocumentSingleFile MultipleDocumentMultipleFile

As well as ScanDocumentProcessingCapabilities: https://www.pwg.org/mfd/navigate/PwgSmRev1-185_ScanDocumentProcessingCapabilities.html

Which has the member Sides: https://www.pwg.org/mfd/navigate/PwgSmRev1-185_Sides.html

With the allowed values: OneSided TwoSidedLongEdge TwoSidedShortEdge

My thought here is that advertising some of these capabilities (and using the resulting feedback correctly) may be necessary for Apple Image Capture to be able to do batch/multi-page and duplex scans. The way the PWG specification is set up strongly suggests that eSCL expects printers to at least offer the ability to a single multi-page output file, which would presumably require AirSane to hook into a PDF library.

Also, again, AirSane advertises the capability to do preview scans, which sheet-fed scanners by definition don't do. Removing that advertised capability when the underlying SANE backend does not provide it might also help with getting Apple Image Capture to on some level treat ADF AirSane scanners correctly.

I actually gave my multifunction Epson scanner to my brother, and I've asked him to send me its Avahi broadcast as well as its XML capabilities document, so I will report back here once I hear back from him.

SimulPiscator commented 3 years ago

The PWG scanner schema is not really adopted by eSCL, just a few fields are taken from there at will. There is no eSCL specification available unless you license the protocol from Apple.

What I did in order to get information about supported field names and values was to disassemble the Apple AirScanScanner executable and the eSCL library contained therein (/System/Library/Image\ Capture/Devices/AirScanScanner.app). Also, a few XML documents produced by actual eSCL scanners are available on the web.

The advantage of the disassembling approach is that you are able to see whether field values are actually accessed after the XML has been parsed, i.e. whether they are used by the Apple side at all.