Skip to content

Commit

Permalink
Merge pull request #87 from sbs20/development
Browse files Browse the repository at this point in the history
Airscan, multiple files -> zip etc
  • Loading branch information
sbs20 authored Oct 12, 2020
2 parents ce1f13c + 94b97ca commit e368ab1
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 59 deletions.
30 changes: 23 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,38 @@ WORKDIR "$APP_DIR"
COPY package*.json "$APP_DIR/"
RUN npm install
COPY . "$APP_DIR"
RUN npm run server-build
RUN npm run client-build
RUN npm run server-build && npm run client-build

# production image
FROM node:buster-slim
ENV APP_DIR=/app
WORKDIR "$APP_DIR"
# Install sane
RUN apt-get update && apt-get install -yq sane sane-utils imagemagick tesseract-ocr
RUN sed -i 's/policy domain="coder" rights="none" pattern="PDF"/policy domain="coder" rights="read | write" pattern="PDF"'/ /etc/ImageMagick-6/policy.xml
RUN apt-get update && \
apt-get install -yq curl gpg && \
echo 'deb http://download.opensuse.org/repositories/home:/pzz/Debian_10/ /' | tee /etc/apt/sources.list.d/home:pzz.list && \
curl -fsSL https://download.opensuse.org/repositories/home:pzz/Debian_10/Release.key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/home:pzz.gpg > /dev/null && \
apt-get update && \
apt-get install -yq sane sane-utils imagemagick tesseract-ocr sane-airscan && \
sed -i 's/policy domain="coder" rights="none" pattern="PDF"/policy domain="coder" rights="read | write" pattern="PDF"'/ /etc/ImageMagick-6/policy.xml

COPY --from=builder "$APP_DIR/dist" "$APP_DIR/"

# Install dependencies
RUN npm install --production

ENV NET_HOST=""
# This goes into /etc/sane.d/net.conf
ENV SANED_NET_HOSTS=""

# This gets added to /etc/sane.d/airscan.conf
ENV AIRSCAN_DEVICES=""

# This directs scanserv not to bother querying `scanimage -L`
ENV SCANIMAGE_LIST_IGNORE=""

# This gets added to scanservjs/config/config.js:devices
ENV DEVICES=""

# Override OCR language
ENV OCR_LANG=""

# Copy entry point
COPY run.sh /run.sh
Expand Down
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ in these cases**
```console
docker pull sbs20/scanservjs:latest
docker rm --force scanservjs-container 2> /dev/null
docker run -d -p 8080:8080 --restart unless-stopped --name scanservjs-container --privileged sbs20/scanservjs:latest
docker run -d -p 8080:8080 -v /var/run/dbus:/var/run/dbus --restart unless-stopped --name scanservjs-container --privileged sbs20/scanservjs:latest
```
(`--privileged` is required for the container to access the host's devices, to
allow it to talk to the scanner)
Expand All @@ -42,7 +42,7 @@ If you want to install the latest staging branch (this may contain newer code)
```console
docker pull sbs20/scanservjs:staging
docker rm --force scanservjs-container 2> /dev/null
docker run -d -p 8080:8080 --restart unless-stopped --name scanservjs-container --privileged sbs20/scanservjs:staging
docker run -d -p 8080:8080 -v /var/run/dbus:/var/run/dbus --restart unless-stopped --name scanservjs-container --privileged sbs20/scanservjs:staging
```

More installation options:
Expand All @@ -51,6 +51,51 @@ More installation options:
* [Development notes](docs/development.md)
* [Configuring the scanner and SANE](docs/sane.md)

## Environment variables

* `SANED_NET_HOSTS`: If you want to use a
[SaneOverNetwork](https://wiki.debian.org/SaneOverNetwork#Server_Configuration)
scanner then to perform the equivalent of adding hosts to
`/etc/sane.d/net.conf` specify a list of ip addresses separated by semicolons
in the `SANED_NET_HOSTS` environment variable.
* `AIRSCAN_DEVICES`: If you want to specifically add `sane-airscan` devices to
your `/etc/sane.d/airscan.conf` then use the `AIRSCAN_DEVICES` environment
variable (semicolon delimited).
* `DEVICES`: Force add devices use `DEVICES` (semicolon delimited)
* `SCANIMAGE_LIST_IGNORE`: To force ignore `scanimage -L`

## Airscan
[sane-airscan](https://github.com/alexpevzner/sane-airscan) uses Avahi /
Zeroconf / Bonjour to discover devices on the local network. If you are running
docker you will want to share dbus to make it work
(`-v /var/run/dbus:/var/run/dbus`).

## Example docker run

### Use airscan and a locally detected scanner
This should support most use cases

```console
docker run -d -p 8080:8080 \
-v /var/run/dbus:/var/run/dbus \
--name scanservjs-container --privileged scanservjs-image
```

### Complicated
Add two net hosts to sane, use airscan to connect to two remote scanners, don't
use `scanimage -L`, force a list of devices and override the OCR language

```console
docker run -d -p 8080:8080 \
-e SANED_HOSTS="10.0.100.30;10.0.100.31" \
-e AIRSCAN_DEVICES='"Canon MFD" = "http://192.168.0.10/eSCL";"EPSON MFD" = "http://192.168.0.11/eSCL"' \
-e SCANIMAGE_LIST_IGNORE=true \
-e DEVICES="net:10.0.100.30:plustek:libusb:001:003;net:10.0.100.31:plustek:libusb:001:003;airscan:e0:Canon TR8500 series;airscan:e1:EPSON Cool Series" \
-e OCR_LANG="fra" \
-v /var/run/dbus:/var/run/dbus \
--name scanservjs-container --privileged scanservjs-image
```

## Why?
This is yet another scanimage-web-front-end. Why? It originally started as an
adaptation of phpsane - just to make everything a bit newer, give it a refresh
Expand Down
22 changes: 15 additions & 7 deletions client/components/Scanserv.vue
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,21 @@ export default {
return this._fetch(url).then(context => {
this.context = context;
this.device = context.devices[0];
this.$refs.toastr.i(`Found device ${this.device.id}`);
this.request = this.readRequest();
for (let test of context.diagnostics) {
const toast = test.success ? this.$refs.toastr.s : this.$refs.toastr.e;
toast(test.message);
if (context.devices.length > 0) {
for (let device of context.devices) {
this.$refs.toastr.i(`Found device ${device.id}`);
}
this.device = context.devices[0];
this.request = this.readRequest();
for (let test of context.diagnostics) {
const toast = test.success ? this.$refs.toastr.s : this.$refs.toastr.e;
toast(test.message);
}
} else {
this.$refs.toastr.e('Found no devices');
}
if (force) {
this.clear();
this.readPreview();
Expand Down Expand Up @@ -483,7 +491,7 @@ export default {
}
}).then((data) => {
if (data && 'page' in data) {
if (window.confirm('Scan another page?')) {
if (window.confirm(`Scan page ${data.page}?`)) {
this.request.page = data.page;
this.scan();
} else {
Expand Down
99 changes: 70 additions & 29 deletions config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ const Config = {};

// Things to change
Config.port = 8080;

// scanservjs will attempt to find scanners locally using `scanimage -L` but you
// will need to manually add network devices here which will be appended. e.g.
// Config.devices = ['net:192.168.0.10:airscan:e0:Canon TR8500 series'];
Config.devices = [];
Config.ocrLanguage = 'eng';
Config.log = {};
Expand Down Expand Up @@ -46,29 +42,38 @@ Config.previewPipeline = {
]
};

/*
When all scans are complete, the filenames are all piped into stdin to the
pipeline commands. It would be nicer to pipe the binary output of scanimage but
that doesn't work with multipage scans so we have no choice but to write to the
filesystem.
/* When all scans are complete, the filenames are all piped into stdin of the
first pipeline command. It would be nicer to pipe the binary output of scanimage
but that doesn't work with multipage scans so we have no choice but to write to
the filesystem.
The stdout of each pipeline feeds into the stdin of the next. Although clumsy in
some respects (especially where we have to write temporary files and then list
them) it at least provides a means of user configuration with "just" shell
scripting.
The overall output of the pipelines (i.e. the last pipeline output) must be a
list of the files you want kept. The convention is to output files of the form
`scan-0000.ext` but it's convention only. You can output whatever you want. If
multiple files are output then the results will be zipped into a single file.
Each command is executed with the CWD set to the temporary location so no
directory traversal is required. Pipeline commands are always read from this
file (and never from the browser request, even though it is sent). It would be
possible to subvert these commands for malicious use, but it doesn't give any
further privilege than the user account running scanservjs. You obviously should
not be running as root.
further privilege than the user account running scanservjs and still requires
access to this file. You obviously should not be running as root.
Some useful pointers:
- `convert` can read a list of files from a file with the @ argument. The `-`
file is stdin. So `convert @- -argument output` performs the conversion om
file is stdin. So `convert @- -argument output` performs the conversion on
each file piped into stdin
- `tesseract` has a similar feature using `-c stream_filelist=true`
- `convert` can also output multiple files if you use an output filename with
`%d` in it. C string style formatting is available so you can do things like
output to `scan-%04d.jpg`. Formats which do not support multiple pages must
use this option. Multi-page formats including PDF and TIF do not use this
option.
- if you just wanted to take a filename from stdin and have its content read out
you could `xargs cat` provided there were no spaces or commas in the filename
(which there won't be)
Expand All @@ -78,81 +83,92 @@ Config.pipelines = [
extension: 'jpg',
description: 'JPG | High quality',
commands: [
'convert @- -quality 92 jpg:-'
'convert @- -quality 92 scan-%04d.jpg',
'ls scan-*.*'
]
},
{
extension: 'jpg',
description: 'JPG | Medium quality',
commands: [
'convert @- -quality 75 jpg:-'
'convert @- -quality 75 scan-%04d.jpg',
'ls scan-*.*'
]
},
{
extension: 'jpg',
description: 'JPG | Low quality',
commands: [
'convert @- -quality 50 jpg:-'
'convert @- -quality 50 scan-%04d.jpg',
'ls scan-*.*'
]
},
{
extension: 'png',
description: 'PNG',
commands: [
'convert @- -quality 75 png:-'
'convert @- -quality 75 scan-%04d.png',
'ls scan-*.*'
]
},
{
extension: 'tif',
description: 'TIF | Uncompressed',
commands: [
'convert @- tif:-'
'convert @- scan-0000.tif',
'ls scan-*.*'
]
},
{
extension: 'tif',
description: 'TIF | LZW compression',
commands: [
'convert @- -compress lzw tif:-'
'convert @- -compress lzw scan-0000.tif',
'ls scan-*.*'
]
},
{
extension: 'pdf',
description: 'PDF (TIF | Uncompressed)',
commands: [
'convert @- pdf:-'
'convert @- scan-0000.pdf',
'ls scan-*.*'
]
},
{
extension: 'pdf',
description: 'PDF (TIF | LZW Compression)',
commands: [
'convert @- -compress lzw tmp-%d.tif && ls tmp-*.tif',
'convert @- pdf:-'
'convert @- -compress lzw tmp-%04d.tif && ls tmp-*.tif',
'convert @- scan-0000.pdf',
'ls scan-*.*'
]
},
{
extension: 'pdf',
description: 'PDF (JPG | High quality)',
commands: [
'convert @- -quality 92 tmp-%d.jpg && ls tmp-*.jpg',
'convert @- pdf:-'
'convert @- -quality 92 tmp-%04d.jpg && ls tmp-*.jpg',
'convert @- scan-0000.pdf',
'ls scan-*.*'
]
},
{
extension: 'pdf',
description: 'PDF (JPG | Medium quality)',
commands: [
'convert @- -quality 75 tmp-%d.jpg && ls tmp-*.jpg',
'convert @- pdf:-'
'convert @- -quality 75 tmp-%04d.jpg && ls tmp-*.jpg',
'convert @- scan-0000.pdf',
'ls scan-*.*'
]
},
{
extension: 'pdf',
description: 'PDF (JPG | Low quality)',
commands: [
'convert @- -quality 50 tmp-%d.jpg && ls tmp-*.jpg',
'convert @- pdf:-'
'convert @- -quality 50 tmp-%04d.jpg && ls tmp-*.jpg',
'convert @- scan-0000.pdf',
'ls scan-*.*'
]
}
];
Expand All @@ -164,17 +180,42 @@ if (Config.tesseract) {
description: 'OCR | PDF (JPG | High quality)',
commands: [
'convert @- -quality 92 tmp-%d.jpg && ls tmp-*.jpg',
`${Config.tesseract} -l ${Config.ocrLanguage} -c stream_filelist=true - - pdf && rm -f tmp-*.jpg`
`${Config.tesseract} -l ${Config.ocrLanguage} -c stream_filelist=true - - pdf > scan-0001.pdf`,
'ls scan-*.*'
]
},
{
extension: 'txt',
description: 'OCR | Text file',
commands: [
`${Config.tesseract} -l ${Config.ocrLanguage} -c stream_filelist=true - - txt && rm -f tmp-*.tif`
`${Config.tesseract} -l ${Config.ocrLanguage} -c stream_filelist=true - - txt > scan-0001.txt`,
'ls scan-*.*'
]
}
]);
}

// Process environment variables

// scanservjs will attempt to find scanners locally using `scanimage -L` but
// sometimes you may need to manually add network devices here if they're not
// found e.g.
// Config.devices = ['net:192.168.0.10:airscan:e0:Canon TR8500 series'];
// This is done with an environment variable. Multiple entries are separated by
// semicolons
if (process.env.DEVICES !== undefined && process.env.DEVICES.length > 0) {
Config.devices = process.env.DEVICES.split(';');
}

// scanservjs will attempt to find scanners locally using `scanimage -L` but
// sometimes it will return nothing. If you are specifying devices manually you
// may also with to turn off the find.
Config.devicesFind = process.env.SCANIMAGE_LIST_IGNORE === undefined
|| process.env.SCANIMAGE_LIST_IGNORE.length === 0;

// Override the OCR language here
if (process.env.OCR_LANG !== undefined && process.env.OCR_LANG.length > 0) {
Config.ocrLanguage = process.env.OCR_LANG;
}

module.exports = Config;
10 changes: 9 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ gulp release
Install docker
```
sudo apt install docker.io
sudo systemctl unmask docker
sudo systemctl start docker
# Hack to make docker accessible.
sudo chmod 666 /var/run/docker.sock
```

Expand All @@ -62,7 +66,11 @@ Useful commands
# Build and run
docker build -t scanservjs-image .
docker rm --force scanservjs-container 2> /dev/null
docker run -d -p 8080:8080 --name scanservjs-container --privileged scanservjs-image
docker run -d -p 8080:8080 -v /var/run/dbus:/var/run/dbus --name scanservjs-container --privileged scanservjs-image

# Copy image
docker save -o scanservjs-image.tar scanservjs-image
docker load -i scanservjs-image.tar

# Debug
docker run -it --entrypoint=/bin/bash scanservjs-container
Expand Down
Loading

0 comments on commit e368ab1

Please sign in to comment.