From 16eda79272a30c7ed1dc46319df7fb8dbcbccd50 Mon Sep 17 00:00:00 2001 From: xxjkzs Date: Tue, 31 Aug 2021 01:36:03 +0800 Subject: [PATCH 01/17] update Chinese locale --- packages/client/src/locales/zh.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/client/src/locales/zh.json b/packages/client/src/locales/zh.json index f73f63f2..b7c88eff 100644 --- a/packages/client/src/locales/zh.json +++ b/packages/client/src/locales/zh.json @@ -43,15 +43,15 @@ "filename": "文件名", "date": "日期", "size": "大小", - "items-per-page": "Files per page", - "items-per-page-all": "All", + "items-per-page": "每页文件数", + "items-per-page-all": "展示全部", "message:deleted": "已删除 {0}", - "message:renamed": "File renamed", - "button:delete-selected": "Delete Selected", - "dialog:rename": "Change file name", - "dialog:rename-cancel": "Cancel", - "dialog:rename-save": "Save", - "actions": "Actions" + "message:renamed": "文件已更名", + "button:delete-selected": "删除所选", + "dialog:rename": "更改文件名称", + "dialog:rename-cancel": "取消", + "dialog:rename-save": "保存", + "actions": "操作" }, "navigation": { @@ -121,8 +121,8 @@ "ledger": "Ledger", "junior-legal": "Junior legal", "half-letter": "Half letter", - "portrait": "Portrait", - "landscape": "Landscape" + "portrait": "纵向", + "landscape": "横向" }, "scan": { From 17cda36b3d798073a5247df003e88dff71ddb6b3 Mon Sep 17 00:00:00 2001 From: CooliMC Date: Mon, 6 Sep 2021 02:28:44 +0200 Subject: [PATCH 02/17] Update de.json Added missing and changes wrong/not correct translations. --- packages/client/src/locales/de.json | 64 ++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/client/src/locales/de.json b/packages/client/src/locales/de.json index f60752ce..a1cacade 100644 --- a/packages/client/src/locales/de.json +++ b/packages/client/src/locales/de.json @@ -10,26 +10,26 @@ }, "colors": { - "accent-4": "Default", - "red": "Red", + "accent-4": "Standard", + "red": "Rot", "pink": "Pink", - "purple": "Purple", - "deep-purple": "Deep purple", + "purple": "Lila", + "deep-purple": "Dunkellila", "indigo": "Indigo", - "blue": "Blue", - "light-blue": "Light blue", + "blue": "Blau", + "light-blue": "Hellblau", "cyan": "Cyan", - "teal": "Teal", - "green": "Green", - "light-green": "Light green", + "teal": "Blaugrün", + "green": "Grün", + "light-green": "Hellgrün", "lime": "Lime", - "yellow": "Yellow", - "amber": "Amber", + "yellow": "Gelb", + "amber": "Bernstein", "orange": "Orange", - "deep-orange": "Deep orange", - "brown": "Brown", - "blue-grey": "Blue grey", - "grey": "Grey" + "deep-orange": "Dunkelorange", + "brown": "Braun", + "blue-grey": "Blau-Grau", + "grey": "Grau" }, "batch-dialog": { @@ -43,8 +43,8 @@ "filename": "Dateiname", "date": "Datum", "size": "Größe", - "items-per-page": "Files per page", - "items-per-page-all": "All", + "items-per-page": "Anzahl der Dateien pro Seite", + "items-per-page-all": "Alle", "message:deleted": "Gelöscht {0}", "message:renamed": "Datei umbenannt", "button:delete-selected": "Auswahl löschen", @@ -77,9 +77,9 @@ }, "mode": { - "color": "Colour", - "halftone": "Halftone", - "gray": "Grey", + "color": "Farbe", + "halftone": "Rasterbild", + "gray": "Graustufen", "lineart": "Lineart", "24bitcolor":"@:mode.color", @@ -90,11 +90,11 @@ }, "source": { - "flatbed": "Flatbed", - "adf": "Automatic Document Feeder", - "auto": "Auto", - "left-aligned": "Left Aligned", - "centrally-aligned": "Centrally Aligned", + "flatbed": "Flachbrett", + "adf": "Dokumenteneinzug", + "auto": "Automatisch", + "left-aligned": "Linksbündig", + "centrally-aligned": "Zentriert", "duplex": "Duplex", "transparency unit": "Transparency Unit", @@ -144,7 +144,7 @@ "left": "Links", "width": "Breite", "height": "Höhe", - "paperSize": "Paper size", + "paperSize": "Papierformat", "brightness": "Helligkeit", "contrast": "Kontrast", "message:loading-devices": "Suche nach Geräten...", @@ -164,12 +164,12 @@ "theme:system": "Systemdesign", "theme:light": "Helles Design", "theme:dark": "Dunkles Design", - "color": "Colour", - "color:description": "Colour. This will change the colour of the top app bar.", - "devices": "Devices and storage", - "reset:description": "Clears stored scanner devices and forces a reload", + "color": "Hintergrund Design", + "color:description": "Hintergrund Design. Hier kann die Farbe der Menüleiste angepasst werden.", + "devices": "Geräte und Speicherorte", + "reset:description": "Alle gespeicherten Scanner löschen und die App neu laden", "reset": "Zurücksetzen", - "clear-storage:description": "Clears local storage of any cached parameters", - "clear-storage": "Clear" + "clear-storage:description": "All gespeicherten Parameter löschen", + "clear-storage": "Löschen" } } From 60f2bdd13d97bb6a2cdea2af1858db92c5b4cba9 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Tue, 7 Sep 2021 11:12:27 +0100 Subject: [PATCH 03/17] Docker build on release --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 087c76cc..05fb826c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,8 +9,8 @@ on: branches: - 'master' - 'staging' - tags: - - 'v*' + release: + types: [published] jobs: docker: From 6ff120a7ec6592f9a2cf4360142602f8a9f0f3e0 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Tue, 7 Sep 2021 11:26:02 +0100 Subject: [PATCH 04/17] Documentation updates --- README.md | 10 +++------- docs/integration.md | 27 +++++++++++++++++++++++++++ docs/proxy.md | 4 ++-- 3 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 docs/integration.md diff --git a/README.md b/README.md index 1141f0a1..48efb02b 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ It supports any * Linux host (or VM with necessary pass-through e.g. USB) * Software sane-utils, ImageMagick, Tesseract (optional) and nodejs -## Installation +## Documentation * [Manual installation](docs/install.md) * [Docker installation](docs/docker.md) @@ -65,12 +65,8 @@ It supports any * [Proxy setup](docs/proxy.md) * [Troubleshooting](docs/troubleshooting.md) * [Development notes](docs/development.md) - -## Configuration and device override - -If you want to override some specific configuration settings then you can do so -within `./config/config.local.js`. See [Configuration](docs/config.md) for more -detail. +* [Configuration and device override](docs/config.md) +* [Integration](docs/integration.md) ## Why? diff --git a/docs/integration.md b/docs/integration.md new file mode 100644 index 00000000..0d1dbd9a --- /dev/null +++ b/docs/integration.md @@ -0,0 +1,27 @@ +# Integration + +It's not uncommon to want to integrate scanservjs with other software - you may +wish to upload scans to Dropbox, paperless-ng or some other location. The +possibilities are endless and deep integration into the UI would add cruft for +the vast majority of users. + +Thankfully, the files just end up in a location on your filesystem so you are +free to integrate however you want. + +The recommended way is to create a script or program which scans the output +directory for files and then does something with them. + +## paperless-ng + +[This discussion](https://github.com/sbs20/scanservjs/issues/351#issuecomment-913858423) +about paperless-ng resulted in +[scantopl](https://github.com/Celedhrim/scantopl) + +## Dropbox + +You could integrate with Dropbox using +[Dropbox-Uploader](https://github.com/andreafabrizi/Dropbox-Uploader) + +## Other recipes? + +If you have other recipes then please share them. diff --git a/docs/proxy.md b/docs/proxy.md index 91e5dc22..bf85e7c5 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -1,9 +1,9 @@ -## Reverse proxy +# Reverse proxy scanservjs supports reverse proxying and uses relative paths throughout so no URL rewriting should be required. -### Apache +## Apache Example setup using a debian based distro. From 23f15d3b70fa317fd04bd2af428ba835a6334f86 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Tue, 21 Sep 2021 12:49:45 +0100 Subject: [PATCH 05/17] Device and feature parsing --- packages/server/src/device.js | 177 +++--------------- packages/server/src/devices.js | 14 +- packages/server/src/feature.js | 108 +++++++++++ packages/server/src/file-info.js | 2 +- packages/server/src/types.js | 2 +- packages/server/src/util.js | 17 ++ packages/server/test/device.test.js | 19 ++ packages/server/test/feature.test.js | 69 +++++++ .../server/test/resource/scanimage-a10.txt | 37 ++++ 9 files changed, 284 insertions(+), 161 deletions(-) create mode 100644 packages/server/src/feature.js create mode 100644 packages/server/test/feature.test.js create mode 100644 packages/server/test/resource/scanimage-a10.txt diff --git a/packages/server/src/device.js b/packages/server/src/device.js index db4133f6..fa43e559 100644 --- a/packages/server/src/device.js +++ b/packages/server/src/device.js @@ -1,162 +1,46 @@ -const extend = require('./util').extend; +const Feature = require('./feature'); +const Util = require('./util'); -/** - * @param {number} n - * @returns {number} - */ -function round(n) { - return Math.floor(n * 10) / 10; -} - -class Feature { - /** - * @param {string} string - * @param {string} delimiter - * @returns {number[]} - */ - static splitNumbers(string, delimiter) { - return string.replace(/[a-z%]/ig, '') - .split(delimiter) - .filter(s => s.length > 0) - .map(s => Number(s)); - } - - /** - * @param {ScanDeviceFeature} feature - */ - static resolution(feature) { - feature.options = [50, 75, 100, 150, 200, 300, 600, 1200]; - if (feature.parameters.indexOf('|') > -1) { - feature.options = Feature.splitNumbers(feature.parameters, '|'); - } else if (feature.parameters.indexOf('..') > -1) { - const limits = Feature.splitNumbers(feature.parameters, '..'); - feature.options = []; - for (let value = limits[1]; value > limits[0]; value /= 2) { - feature.options.push(value); - } - feature.options.push(limits[0]); - feature.options.sort((a, b) => a - b); - } - feature.default = Number(feature.default); - } - - /** - * @param {ScanDeviceFeature} feature - */ - static range(feature) { - feature.default = round(Number(feature.default)); - const range = /(.*?)(?:\s|$)/g.exec(feature.parameters); - feature.limits = Feature.splitNumbers(range[1], '..'); - const steps = /\(in steps of ([0-9]{1,2})\)/g.exec(feature.parameters); - feature.interval = steps ? Number(steps[1]) : 1; - } - - /** - * @param {ScanDeviceFeature} feature - */ - static geometry(feature) { - Feature.range(feature); - feature.limits[0] = round(feature.limits[0]); - feature.limits[1] = round(feature.limits[1]); - } - - /** - * @param {ScanDeviceFeature} feature - */ - static lighting(feature) { - Feature.range(feature); +/** @type {ScanDevice} */ +class Device { + constructor(string) { + this.id = ''; + this.features = {}; + this.string = string; + this.parse(); } -} -class Adapter { - /** - * @param {ScanDevice} device - * @returns {ScanDevice} - */ - static decorate(device) { - device.name = device.id; - for (const key in device.features) { - const feature = device.features[key]; - feature.parameters = feature.parameters.replace(/^auto\|/, ''); - switch (key) { - case '--mode': - case '--source': - feature.options = feature.parameters.split('|'); - break; - - case '--resolution': - Feature.resolution(feature); - break; - - case '-l': - case '-t': - case '-x': - case '-y': - Feature.geometry(feature); - break; - - case '--brightness': - case '--contrast': - Feature.lighting(feature); - break; - } - } - - return device; - } - /** - * Parses the response of scanimage -A into a dictionary - * @param {string} response - * @returns {ScanDevice} + * Parses the response of scanimage -A into a ScanDevice */ - static parse(response) { - if (response === null || response === '') { + parse() { + if (this.string === null || this.string === '') { throw new Error('No device found'); } - - /** @type {ScanDevice} */ - let device = { - 'id': '', - 'features': {} - }; - + // find // any number of spaces // match 1 or two hyphens with letters, numbers or hypen // match anything (until square brackets) // match anything inside square brackets - let pattern = /\s+([-]{1,2}[-a-zA-Z0-9]+) ?(.*) \[(.*)\]\n/g; - let match; - while ((match = pattern.exec(response)) !== null) { - if (!['inactive', 'read-only'].includes(match[3])) { - device.features[match[1]] = { - 'default': match[3], - 'parameters': match[2] - }; - } - } + Util.matchAll(/\s+([-]{1,2}[-a-zA-Z0-9]+ ?.* \[.*\])\n/g, this.string) + .map(m => m[1]) + .map(Feature.parse) + .filter(f => f.enabled) + .forEach(f => this.features[f.name] = f); - pattern = /All options specific to device `(.*)'/; - match = pattern.exec(response); + const match = /All options specific to device `(.*)'/.exec(this.string); if (match) { - device.id = match[1]; - } - - if (match === null) { + this.id = match[1]; + } else { throw new Error('Scanimage output contains no matching expressions'); } - - return device; - } -} -class Device { - constructor() { + this.validate(); } validate() { - const mandatory = ['--resolution', '-l', '-t', '-x', '-y']; + const mandatory = ['--resolution']; for (const feature of mandatory) { if (this.features[feature] === undefined) { throw `${feature} is missing from device`; @@ -165,19 +49,12 @@ class Device { } /** - * @param {any|string} o + * @param {string} s * @returns {ScanDevice} */ - static from(o) { - const device = new Device(); - if (typeof o === 'object') { - const decorated = Adapter.decorate(o); - extend(device, decorated); - device.validate(); - return device; - } else if (typeof o === 'string') { - const data = Adapter.parse(o); - return Device.from(data); + static from(s) { + if (typeof s === 'string') { + return new Device(s); } else { throw new Error('Unexpected data for Device'); } diff --git a/packages/server/src/devices.js b/packages/server/src/devices.js index bcb32965..ada335ab 100644 --- a/packages/server/src/devices.js +++ b/packages/server/src/devices.js @@ -5,16 +5,11 @@ const FileInfo = require('./file-info'); const userOptions = require('./user-options'); const Process = require('./process'); const Scanimage = require('./scanimage'); +const Util = require('./util'); class Devices { static _parseDevices(s) { - const deviceIds = []; - const pattern = /device `?(.*)'.*/g; - let match; - while ((match = pattern.exec(s)) !== null) { - deviceIds.push(match[1]); - } - return deviceIds; + return Util.matchAll(/device `?(.*)'.*/g, s).map(m => m[1]); } /** @@ -50,7 +45,7 @@ class Devices { let devices = null; if (file.exists()) { - devices = Devices.from(file.toJson()); + devices = Devices.from(file.parseJson()); if (devices.length === 0) { log.debug('devices.json contains no devices. Reloading'); devices = null; @@ -69,6 +64,7 @@ class Devices { deviceIds = deviceIds.concat(localDevices); } + /** @type {ScanDevice[]} */ devices = []; for (let deviceId of deviceIds) { try { @@ -79,7 +75,7 @@ class Devices { log.error(`Ignoring ${deviceId}. Error: ${error}`); } } - file.save(JSON.stringify(devices, null, 2)); + file.save(JSON.stringify(devices.map(d => d.string), null, 2)); } userOptions.applyToDevices(devices); diff --git a/packages/server/src/feature.js b/packages/server/src/feature.js new file mode 100644 index 00000000..baed067a --- /dev/null +++ b/packages/server/src/feature.js @@ -0,0 +1,108 @@ +/** + * @param {number} n + * @returns {number} + */ +function round(n) { + return Math.floor(n * 10) / 10; +} + +class Feature { + /** + * Constructor + */ + constructor() { + } + + get enabled() { + return !['inactive', 'read-only'].includes(this.default); + } + + /** + * @param {string} string + * @param {string} delimiter + * @returns {number[]} + */ + static splitNumbers(string, delimiter) { + return string.replace(/[a-z%]/ig, '') + .split(delimiter) + .filter(s => s.length > 0) + .map(s => Number(s)); + } + + range() { + this.default = round(Number(this.default)); + const range = /(.*?)(?:\s|$)/g.exec(this.parameters); + this.limits = Feature.splitNumbers(range[1], '..'); + const steps = /\(in steps of (\d+\.?\d*)\)/g.exec(this.parameters); + this.interval = steps ? Number(steps[1]) : 1; + } + + resolution() { + if (this.parameters.indexOf('|') > -1) { + this.options = Feature.splitNumbers(this.parameters, '|'); + this.default = Number(this.default); + + } else if (this.parameters.indexOf('..') > -1) { + // this.options = [50, 75, 100, 150, 200, 300, 600, 1200]; + this.range(this); + const limits = this.limits; + this.options = []; + for (let value = limits[1]; value > limits[0]; value /= 2) { + this.options.push(value); + } + this.options.push(limits[0]); + this.options.sort((a, b) => a - b); + } + } + + geometry() { + this.range(); + this.limits[0] = round(this.limits[0]); + this.limits[1] = round(this.limits[1]); + } + + lighting() { + this.range(); + } + + load() { + this.parameters = this.parameters.replace(/^auto\|/, ''); + if (this.enabled) { + switch (this.name) { + case '--mode': + case '--source': + this.options = this.parameters.split('|'); + break; + + case '--resolution': + this.resolution(); + break; + + case '-l': + case '-t': + case '-x': + case '-y': + this.geometry(); + break; + + case '--brightness': + case '--contrast': + this.lighting(); + break; + } + } + } + + static parse(s) { + const match = /^\s*([-]{1,2}[-a-zA-Z0-9]+) ?(.*) \[(.*)\]$/g.exec(s); + const feature = new Feature(); + feature.text = s; + feature.name = match[1]; + feature.default = match[3]; + feature.parameters = match[2]; + feature.load(); + return feature; + } +} + +module.exports = Feature; \ No newline at end of file diff --git a/packages/server/src/file-info.js b/packages/server/src/file-info.js index 3d74379b..8ad933e7 100644 --- a/packages/server/src/file-info.js +++ b/packages/server/src/file-info.js @@ -176,7 +176,7 @@ class FileInfo { /** * @returns {any} */ - toJson() { + parseJson() { return JSON.parse(this.toText()); } diff --git a/packages/server/src/types.js b/packages/server/src/types.js index 5bd1ca56..d6de2c65 100644 --- a/packages/server/src/types.js +++ b/packages/server/src/types.js @@ -24,7 +24,7 @@ * @typedef {Object} ScanDevice * @property {string} id * @property {string} name - * @property {string} version + * @property {string} string * @property {Object.} features */ diff --git a/packages/server/src/util.js b/packages/server/src/util.js index 554da335..a9704d51 100644 --- a/packages/server/src/util.js +++ b/packages/server/src/util.js @@ -1,6 +1,23 @@ const AdmZip = require('adm-zip'); const Util = { + /** + * Rough polyfill for str.matchAll() + * @param {RegExp} regex + * @param {string} string + * @returns {RegExpExecArray[]} + */ + matchAll(regex, string) { + /** @type {RegExpExecArray[]} */ + const result = []; + /** @type {RegExpExecArray} */ + let match; + while ((match = regex.exec(string)) !== null) { + result.push(match); + } + return result; + }, + extend() { const t = arguments[0]; for (let i = 1; i < arguments.length; i++) { diff --git a/packages/server/test/device.test.js b/packages/server/test/device.test.js index eb6517f0..40700e14 100644 --- a/packages/server/test/device.test.js +++ b/packages/server/test/device.test.js @@ -203,4 +203,23 @@ describe('Device', () => { assert.strictEqual(device.features['-y'].limits[1], 297.1); assert.strictEqual(device.features['-y'].default, 297.1); }); + + it('scanimage-a10.txt', () => { + const file = FileInfo.create('test/resource/scanimage-a10.txt'); + const device = Device.from(file.toText()); + + assert.strictEqual(device.id, 'epjitsu:libusb:001:003'); + assert.deepStrictEqual(device.features['--mode'].options, ['Lineart', 'Gray', 'Color']); + assert.strictEqual(device.features['--mode'].default, 'Lineart'); + assert.deepStrictEqual(device.features['--source'].options, ['ADF Front', 'ADF Back', 'ADF Duplex']); + assert.strictEqual(device.features['--source'].default, 'ADF Front'); + assert.deepStrictEqual(device.features['--resolution'].options, [50, 75, 150, 300, 600]); + assert.strictEqual(device.features['--resolution'].default, 300); + assert.strictEqual(device.features['-l'], undefined); + assert.strictEqual(device.features['-x'], undefined); + assert.deepStrictEqual(device.features['-t'].limits, [0, 289.3]); + assert.strictEqual(device.features['-t'].default, 0); + assert.strictEqual(device.features['-t'].interval, 0.0211639); + assert.strictEqual(device.features['-y'], undefined); + }); }); \ No newline at end of file diff --git a/packages/server/test/feature.test.js b/packages/server/test/feature.test.js new file mode 100644 index 00000000..e3e607c2 --- /dev/null +++ b/packages/server/test/feature.test.js @@ -0,0 +1,69 @@ +/* eslint-env mocha */ +const assert = require('assert'); +const Feature = require('../src/feature'); + +describe('Feature', () => { + it('range-brightness', () => { + const f = Feature.parse('--brightness -127..127 (in steps of 1) [0]'); + assert.strictEqual(f.name, '--brightness'); + assert.strictEqual(f.interval, 1); + assert.strictEqual(f.default, 0); + assert.strictEqual(f.enabled, true); + assert.deepStrictEqual(f.limits, [-127, 127]); + }); + + it('range-contrast', () => { + const f = Feature.parse(' --contrast 0..255 (in steps of 1) [120]'); + assert.strictEqual(f.name, '--contrast'); + assert.strictEqual(f.interval, 1); + assert.strictEqual(f.default, 120); + assert.strictEqual(f.enabled, true); + assert.deepStrictEqual(f.limits, [0, 255]); + }); + + it('range-geometry1', () => { + const f = Feature.parse(' -t 0..289.353mm (in steps of 0.0211639) [0]'); + assert.strictEqual(f.name, '-t'); + assert.strictEqual(f.interval, 0.0211639); + assert.strictEqual(f.default, 0); + assert.strictEqual(f.enabled, true); + assert.deepStrictEqual(f.limits, [0, 289.3]); + }); + + it('range-geometry2', () => { + const f = Feature.parse(' -l 0..215mm [0]'); + assert.strictEqual(f.name, '-l'); + assert.strictEqual(f.interval, 1); + assert.strictEqual(f.default, 0); + assert.strictEqual(f.enabled, true); + assert.deepStrictEqual(f.limits, [0, 215]); + }); + + it('range-source-inactive', () => { + const f = Feature.parse(' --source Normal|Transparency|Negative [inactive]'); + assert.strictEqual(f.name, '--source'); + assert.strictEqual(f.interval, undefined); + assert.strictEqual(f.default, 'inactive'); + assert.strictEqual(f.enabled, false); + assert.strictEqual(f.options, undefined); + }); + + it('range-resolution-range', () => { + const f = Feature.parse('--resolution 50..1200dpi [50]'); + assert.strictEqual(f.name, '--resolution'); + assert.strictEqual(f.interval, 1); + assert.strictEqual(f.default, 50); + assert.strictEqual(f.enabled, true); + assert.deepStrictEqual(f.options, [50, 75, 150, 300, 600, 1200]); + }); + + it('range-resolution-options', () => { + const f = Feature.parse('--resolution 75|300|600|1200dpi [75]'); + assert.strictEqual(f.name, '--resolution'); + assert.strictEqual(f.interval, undefined); + assert.strictEqual(f.default, 75); + assert.strictEqual(f.enabled, true); + assert.deepStrictEqual(f.options, [75, 300, 600, 1200]); + }); + +}); \ No newline at end of file diff --git a/packages/server/test/resource/scanimage-a10.txt b/packages/server/test/resource/scanimage-a10.txt new file mode 100644 index 00000000..a40cbe2c --- /dev/null +++ b/packages/server/test/resource/scanimage-a10.txt @@ -0,0 +1,37 @@ +All options specific to device `epjitsu:libusb:001:003': + Scan Mode: + --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) [300] + Sets the resolution of the scanned image. + Geometry: + -t 0..289.353mm (in steps of 0.0211639) [0] + Top-left y position of scan area. + --page-width 2.70898..219.428mm (in steps of 0.0211639) [215.872] + Specifies the width of the media. Required for automatic centering of + sheet-fed scans. + --page-height 0..450.707mm (in steps of 0.0211639) [292.062] + Specifies the height of the media, 0 will auto-detect. + 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) [120] + Select minimum-brightness to get a white point + --threshold-curve 0..127 (in steps of 1) [55] + Dynamic threshold curve, from light to dark, normally 50-65 + Sensors: + --scan[=(yes|no)] [no] [hardware] + Scan button + --page-loaded[=(yes|no)] [no] [hardware] + Page loaded + --top-edge[=(yes|no)] [no] [hardware] + Paper is pulled partly into adf + --cover-open[=(yes|no)] [no] [hardware] + Cover open + --power-save[=(yes|no)] [no] [hardware] + Scanner in power saving mode + From 9acdf3b7b5a1a255aaea134e0e1f67c8993a449e Mon Sep 17 00:00:00 2001 From: sbs20 Date: Tue, 21 Sep 2021 13:27:43 +0100 Subject: [PATCH 06/17] Optional geometry #350 --- packages/client/src/classes/request.js | 10 +++-- packages/client/src/components/Scan.vue | 49 +++++++++++++++---------- packages/server/src/request.js | 10 +++-- packages/server/src/scanimage.js | 16 +++++--- 4 files changed, 51 insertions(+), 34 deletions(-) diff --git a/packages/client/src/classes/request.js b/packages/client/src/classes/request.js index ea8a83fa..5eeb7e1c 100644 --- a/packages/client/src/classes/request.js +++ b/packages/client/src/classes/request.js @@ -16,10 +16,6 @@ export default class Request { version: Constants.Version, params: { deviceId: device.id, - top: request.params.top || device.features['-t'].default, - left: request.params.left || device.features['-l'].default, - width: request.params.width || device.features['-x'].default, - height: request.params.height || device.features['-y'].default, resolution: request.params.resolution || device.features['--resolution'].default }, filters: request.filters || [], @@ -28,6 +24,12 @@ export default class Request { index: 1 }; + if (['-x', '-y', '-l', '-t'].every(s => s in device.features)) { + obj.params.top = request.params.top || device.features['-t'].default; + obj.params.left = request.params.left || device.features['-l'].default; + obj.params.width = request.params.width || device.features['-x'].default; + obj.params.height = request.params.height || device.features['-y'].default; + } if ('--mode' in device.features) { obj.params.mode = request.params.mode || device.features['--mode'].default; } diff --git a/packages/client/src/components/Scan.vue b/packages/client/src/components/Scan.vue index 987878b2..cd2729c9 100644 --- a/packages/client/src/components/Scan.vue +++ b/packages/client/src/components/Scan.vue @@ -58,30 +58,33 @@ - + - - - - - - - - - - {{ item.name }} - - - +
s in this.device.features); + }, + deviceSize() { return { width: this.device.features['-x'].limits[1], @@ -272,7 +279,9 @@ export default { methods: { _resizePreview() { - const paperRatio = this.deviceSize.width / this.deviceSize.height; + const paperRatio = this.geometry + ? this.deviceSize.width / this.deviceSize.height + : 210 / 297; // This only makes a difference when the col-width="auto" - so md+ const mdBreakpoint = 960; diff --git a/packages/server/src/request.js b/packages/server/src/request.js index 52c103bc..30bd0bc0 100644 --- a/packages/server/src/request.js +++ b/packages/server/src/request.js @@ -26,10 +26,6 @@ class Request { extend(this, { params: { deviceId: device.id, - top: bound(data.params.top, features['-t'].limits[0], features['-t'].limits[1], 0), - left: bound(data.params.left, features['-l'].limits[0], features['-l'].limits[1], 0), - width: bound(data.params.width, features['-x'].limits[0], features['-x'].limits[1], features['-x'].limits[1]), - height: bound(data.params.height, features['-y'].limits[0], features['-y'].limits[1], features['-y'].limits[1]), resolution: data.params.resolution || features['--resolution'].default, format: 'tiff' }, @@ -39,6 +35,12 @@ class Request { index: data.index || 1 }); + if (['-x', '-y', '-l', '-t'].every(s => s in features)) { + this.params.top = bound(data.params.top, features['-t'].limits[0], features['-t'].limits[1], 0); + this.params.left = bound(data.params.left, features['-l'].limits[0], features['-l'].limits[1], 0); + this.params.width = bound(data.params.width, features['-x'].limits[0], features['-x'].limits[1], features['-x'].limits[1]); + this.params.height = bound(data.params.height, features['-y'].limits[0], features['-y'].limits[1], features['-y'].limits[1]); + } if ('--mode' in features) { this.params.mode = data.params.mode || features['--mode'].default; } diff --git a/packages/server/src/scanimage.js b/packages/server/src/scanimage.js index 994b1659..e7bb68c3 100644 --- a/packages/server/src/scanimage.js +++ b/packages/server/src/scanimage.js @@ -53,12 +53,16 @@ class Scanimage { cmdBuilder.arg('--source', params.source); } - cmdBuilder.arg('--resolution', params.resolution) - .arg('-l', params.left) - .arg('-t', params.top) - .arg('-x', params.width) - .arg('-y', params.height) - .arg('--format', params.format); + cmdBuilder.arg('--resolution', params.resolution); + + if (['-x', '-y', '-l', '-t'].every(s => s in params)) { + cmdBuilder.arg('-l', params.left) + .arg('-t', params.top) + .arg('-x', params.width) + .arg('-y', params.height); + } + + cmdBuilder.arg('--format', params.format); if ('depth' in params) { cmdBuilder.arg('--depth', params.depth); From ed3d36a482f3441601cdff5b227080c854f6d94a Mon Sep 17 00:00:00 2001 From: sbs20 Date: Tue, 21 Sep 2021 13:27:52 +0100 Subject: [PATCH 07/17] Build bump --- package-lock.json | 4 ++-- package.json | 2 +- packages/client/package-lock.json | 4 ++-- packages/client/package.json | 2 +- packages/server/package-lock.json | 4 ++-- packages/server/package.json | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index be7ed200..3c97918e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanservjs", - "version": "2.17.1", + "version": "2.17.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scanservjs", - "version": "2.17.1", + "version": "2.17.2", "hasInstallScript": true, "license": "GPL-2.0" } diff --git a/package.json b/package.json index 8bc045e0..ffd4a573 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scanservjs", - "version": "2.17.1", + "version": "2.17.2", "description": "scanservjs is a simple web-based UI for SANE which allows you to share a scanner on a network without the need for drivers or complicated installation.", "scripts": { "clean": "rm -rf ./dist", diff --git a/packages/client/package-lock.json b/packages/client/package-lock.json index d889a6db..67204caa 100644 --- a/packages/client/package-lock.json +++ b/packages/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanservjs", - "version": "2.17.1", + "version": "2.17.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scanservjs", - "version": "2.17.1", + "version": "2.17.2", "license": "GPL-2.0", "dependencies": { "@mdi/font": "^5.9.55", diff --git a/packages/client/package.json b/packages/client/package.json index 6ec9a97d..3c561643 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "scanservjs", - "version": "2.17.1", + "version": "2.17.2", "description": "scanservjs is a simple web-based UI for SANE which allows you to share a scanner on a network without the need for drivers or complicated installation.", "author": "Sam Strachan", "scripts": { diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json index 99152825..a387ccde 100644 --- a/packages/server/package-lock.json +++ b/packages/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanservjs-server", - "version": "2.17.1", + "version": "2.17.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scanservjs-server", - "version": "2.17.1", + "version": "2.17.2", "license": "GPL-2.0", "dependencies": { "adm-zip": "^0.5.5", diff --git a/packages/server/package.json b/packages/server/package.json index d1a268ac..18409740 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "scanservjs-server", - "version": "2.17.1", + "version": "2.17.2", "description": "scanservjs is a simple web-based UI for SANE which allows you to share a scanner on a network without the need for drivers or complicated installation.", "scripts": { "lint": "gulp lint", From 87a99aa9e07d4e348b085d1abec3a66fe41ad87b Mon Sep 17 00:00:00 2001 From: rourke Date: Tue, 21 Sep 2021 22:24:37 +0200 Subject: [PATCH 08/17] Create nl.json Dutch translation file --- packages/client/src/locales/nl.json | 190 ++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 packages/client/src/locales/nl.json diff --git a/packages/client/src/locales/nl.json b/packages/client/src/locales/nl.json new file mode 100644 index 00000000..ea643e19 --- /dev/null +++ b/packages/client/src/locales/nl.json @@ -0,0 +1,190 @@ +{ + "global": { + "application-name": "scanservjs" + }, + + "about": { + "main": "scanservjs is een simpel web-based UI voor je scanner. Hiermee kun je een of meer scanners (met behulp van SANE) op een netwerk delen zonder dat er stuurprogramma's of een ingewikkelde installatie nodig is. Het kan in TIF, JPG, PNG, PDF en TXT (met Tesseract OCR) opslaan met verschillende compressie-instellingen, wat allemaal kan worden geconfigureerd. Het ondersteunt scannen van meerdere pagina's en alle SANE-compatibele apparaten.", + "issue": "Probleem indienen of bekijk de broncode:", + "system-info": "Systeem informatie" + }, + + "colors": { + "accent-4": "Standaard", + "red": "Rood", + "pink": "Roze", + "purple": "Paars", + "deep-purple": "Donkerpaars", + "indigo": "Indigo", + "blue": "Blauw", + "light-blue": "Lichtblauw", + "cyan": "Cyaan", + "teal": "Groenblauw", + "green": "Groen", + "light-green": "Lichtgroen", + "lime": "Limoen", + "yellow": "Geel", + "amber": "Amber", + "orange": "Oranje", + "deep-orange": "Donkeroranje", + "brown": "Bruin", + "blue-grey": "Blauwgrijs", + "grey": "Grijs" + }, + + "batch-dialog": { + "btn-cancel": "Annuleren", + "btn-finish": "Afronden", + "btn-rescan": "Pagina opnieuw scannen", + "btn-next": "Volgende" + }, + + "files": { + "filename": "Bestandsnaam", + "date": "Datum", + "size": "Grootte", + "items-per-page": "Bestanden per pagina", + "items-per-page-all": "Alles", + "message:deleted": "Verwijderd {0}", + "message:renamed": "Bestand hernoemd", + "button:delete-selected": "Verwijderen geselecteerde bestanden", + "dialog:rename": "Pas bestandsnaam aan", + "dialog:rename-cancel": "Annuleren", + "dialog:rename-save": "Opslaan", + "actions": "Acties" + }, + + "navigation": { + "scan": "Scan", + "files": "Bestanden", + "settings": "Instellingen", + "about": "Over", + "version": "Versie" + }, + + "batch-mode": { + "none": "Geen", + "manual": "Handmatig (met bevestiging)", + "auto": "Auto (Documentinvoer)", + "auto-collate-standard": "Auto (Sorteren 1, 3... 4, 2)", + "auto-collate-reverse": "Auto (Omgedraaid 1, 3... 2, 4)" + }, + + "filter": { + "auto-level": "Auto nivelleren", + "threshold": "Threshold", + "blur": "Vervagen" + }, + + "mode": { + "color": "Kleur", + "halftone": "Halftoon", + "gray": "Grijswaarde", + "lineart": "Lijntekening", + + "24bitcolor":"@:mode.color", + "black & white": "@:mode.lineart", + "gray(error diffusion)": "@:mode.halftone", + "true gray": "@:mode.gray", + "24bit color(fast)": "@:mode.color" + }, + + "source": { + "flatbed": "Flatbed", + "adf": "ADF", + "auto": "Auto", + "left-aligned": "Links uitgelijnd", + "centrally-aligned": "Centraal uitgelijnd", + "duplex": "Duplex", + "transparency unit": "Transparantie-eenheid", + + "automatic document feeder": "@:source.adf", + "automatic document feeder(left aligned)": "@:source.adf (@:source.left-aligned)", + "automatic document feeder(left aligned,duplex)": "@:source.adf (@:source.left-aligned, @:source.duplex)", + "automatic document feeder(centrally aligned)": "@:source.adf (@:source.centrally-aligned)", + "automatic document feeder(centrally aligned,duplex)": "@:source.adf (@:source.centrally-aligned, @:source.duplex)" + }, + + "pipeline": { + "high-quality": "Hoge kwaliteit", + "medium-quality": "Gemiddelde kwaliteit", + "low-quality": "Lage kwaliteit", + "uncompressed": "Ongecomprimeerd", + "lzw-compressed": "LZW gecomprimeerd", + "ocr": "OCR", + "text-file": "Text bestand" + }, + + "paper-size": { + "letter": "Letter", + "legal": "Legal", + "tabloid": "Tabloid", + "ledger": "Ledger", + "junior-legal": "Junior legal", + "half-letter": "Half letter", + "portrait": "Portret", + "landscape": "Landschap" + }, + + "scan": { + "device": "Apparaat", + "source": "Bron", + "resolution": "Resolutie", + "mode": "Modus", + "dynamic-lineart": "Dynamische Lijntekening", + "dynamic-lineart:enabled": "Ingeschakeld", + "dynamic-lineart:disabled": "Uitgeschakeld", + "batch": "Batch", + "filters": "Filters", + "format": "Formaat", + "btn-preview": "Voorbeeld", + "btn-clear": "Reset", + "btn-scan": "Scan", + "top": "Boven", + "left": "Links", + "width": "Breedte", + "height": "Hoogte", + "paperSize": "Papier formaat", + "brightness": "Helderheid", + "contrast": "Contrast", + "message:loading-devices": "Apparaten laden...", + "message:no-devices": "Geen apparaten gevonden", + "message:deleted-preview": "Verwijderd voorbeeld", + "message:turn-documents": "Draai document om", + "message:preview-of-page": "Voorbeeld van pagina" + }, + + "settings": { + "title": "@:navigation.settings", + "behaviour-ui": "Werking en UI", + "locale": "Taal", + "locale:description": "Kies je taal", + "theme": "Thema", + "theme:description": "Thema. Als je het systeem thema gebruikt and dit aanpast, dien je de app te herladen.", + "theme:system": "Systeem", + "theme:light": "Licht", + "theme:dark": "Donker", + "color": "Kleur", + "color:description": "Kleur. Dit past de kleur aan van de balk bovenin.", + "devices": "Apparaten en opslag", + "reset:description": "Wist opgeslagen scanner apparaten en forceert tot heladen", + "reset": "Reset", + "clear-storage:description": "Wist lokale opslag van alle parameters in de cache", + "clear-storage": "Wissen" + }, + + "locales": { + "cs": "Čeština", + "de": "Deutsche", + "en": "English", + "es": "Español", + "fr": "Français", + "it": "Italiano", + "nl": "Nederlands", + "pl": "Polski", + "pt-BR": "Portuguese (Brazilian)", + "ru": "Russian", + "zh": "简体中文", + "test": "Test" + } +} From e09a2ca6f7674e8a27b266e12e70bceb34ca08b3 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 14:44:08 +0100 Subject: [PATCH 09/17] Optional geometry / doc scanners #350 --- packages/client/src/components/Scan.vue | 2 +- packages/server/src/device.js | 7 +++++++ packages/server/src/request.js | 2 +- packages/server/src/scan-controller.js | 25 +++++++++++++------------ packages/server/src/types.js | 1 + 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/client/src/components/Scan.vue b/packages/client/src/components/Scan.vue index cd2729c9..ab6cda9b 100644 --- a/packages/client/src/components/Scan.vue +++ b/packages/client/src/components/Scan.vue @@ -52,7 +52,7 @@
{{ $t('scan.btn-scan') }} mdi-camera - {{ $t('scan.btn-preview') }} mdi-magnify + {{ $t('scan.btn-preview') }} mdi-magnify {{ $t('scan.btn-clear') }} mdi-delete
diff --git a/packages/server/src/device.js b/packages/server/src/device.js index fa43e559..aa86a4cf 100644 --- a/packages/server/src/device.js +++ b/packages/server/src/device.js @@ -10,6 +10,13 @@ class Device { this.parse(); } + /** + * @returns {boolean} + */ + get geometry() { + return ['-x', '-y', '-l', '-t'].every(s => s in this.features); + } + /** * Parses the response of scanimage -A into a ScanDevice */ diff --git a/packages/server/src/request.js b/packages/server/src/request.js index 30bd0bc0..c9dedb6e 100644 --- a/packages/server/src/request.js +++ b/packages/server/src/request.js @@ -35,7 +35,7 @@ class Request { index: data.index || 1 }); - if (['-x', '-y', '-l', '-t'].every(s => s in features)) { + if (device.geometry) { this.params.top = bound(data.params.top, features['-t'].limits[0], features['-t'].limits[1], 0); this.params.left = bound(data.params.left, features['-l'].limits[0], features['-l'].limits[1], 0); this.params.width = bound(data.params.width, features['-x'].limits[0], features['-x'].limits[1], features['-x'].limits[1]); diff --git a/packages/server/src/scan-controller.js b/packages/server/src/scan-controller.js index 461659fd..dc20c141 100644 --- a/packages/server/src/scan-controller.js +++ b/packages/server/src/scan-controller.js @@ -131,18 +131,19 @@ class ScanController { async updatePreview(filename) { const dpmm = this.request.params.resolution / 25.4; const device = this.context.getDevice(this.request.params.deviceId); - const geometry = { - width: device.features['-x'].limits[1] * dpmm, - height: device.features['-y'].limits[1] * dpmm, - left: this.request.params.left * dpmm, - top: this.request.params.top * dpmm - }; - - const cmd = new CmdBuilder(Config.convert) - .arg(`'${Config.tempDirectory}/${filename}'`) - .arg('-background', '#808080') - .arg('-extent', `${geometry.width}x${geometry.height}-${geometry.left}-${geometry.top}`) - .arg('-resize', 868) + const cmd = new CmdBuilder(Config.convert).arg(`'${Config.tempDirectory}/${filename}'`); + if (device.geometry) { + const geometry = { + width: device.features['-x'].limits[1] * dpmm, + height: device.features['-y'].limits[1] * dpmm, + left: this.request.params.left * dpmm, + top: this.request.params.top * dpmm + }; + cmd.arg('-background', '#808080') + .arg('-extent', `${geometry.width}x${geometry.height}-${geometry.left}-${geometry.top}`); + } + + cmd.arg('-resize', 868) .arg(`'${Config.previewDirectory}/preview.tif'`) .build(); diff --git a/packages/server/src/types.js b/packages/server/src/types.js index d6de2c65..6db5b8eb 100644 --- a/packages/server/src/types.js +++ b/packages/server/src/types.js @@ -25,6 +25,7 @@ * @property {string} id * @property {string} name * @property {string} string + * @property {boolean} geometry * @property {Object.} features */ From ac18453810256ffb8b0091b78a232621d3bd7d63 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 14:50:14 +0100 Subject: [PATCH 10/17] Merge Dutch translation #154 --- README.md | 4 ++-- packages/client/src/classes/constants.js | 1 + packages/client/src/locales/en.json | 1 + packages/client/src/locales/nl.json | 15 --------------- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 48efb02b..7109f74f 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ complicated installation. * Filters: Autolevels, Threshold, Blur * Configurable overrides for all defaults as well as filters and formats * Multipage scanning (with collation for double sided scans) -* International translations: Czech, French, German, Italian, Mandarin, Polish, - Portuguese (BR), Russian, Spanish; +* International translations: Czech, Dutch, French, German, Italian, Mandarin, + Polish, Portuguese (BR), Russian, Spanish; [Help requested](https://github.com/sbs20/scanservjs/issues/154) * Light and dark mode * Responsive design diff --git a/packages/client/src/classes/constants.js b/packages/client/src/classes/constants.js index 02f615cf..0dff5f3c 100644 --- a/packages/client/src/classes/constants.js +++ b/packages/client/src/classes/constants.js @@ -25,6 +25,7 @@ const Constants = { 'es', 'fr', 'it', + 'nl', 'pl', 'pt-BR', 'ru', diff --git a/packages/client/src/locales/en.json b/packages/client/src/locales/en.json index ecb2f2d2..e13ed92c 100644 --- a/packages/client/src/locales/en.json +++ b/packages/client/src/locales/en.json @@ -180,6 +180,7 @@ "es": "Español", "fr": "Français", "it": "Italiano", + "nl": "Nederlands", "pl": "Polski", "pt-BR": "Portuguese (Brazilian)", "ru": "Russian", diff --git a/packages/client/src/locales/nl.json b/packages/client/src/locales/nl.json index ea643e19..2ad3bcb2 100644 --- a/packages/client/src/locales/nl.json +++ b/packages/client/src/locales/nl.json @@ -171,20 +171,5 @@ "reset": "Reset", "clear-storage:description": "Wist lokale opslag van alle parameters in de cache", "clear-storage": "Wissen" - }, - - "locales": { - "cs": "Čeština", - "de": "Deutsche", - "en": "English", - "es": "Español", - "fr": "Français", - "it": "Italiano", - "nl": "Nederlands", - "pl": "Polski", - "pt-BR": "Portuguese (Brazilian)", - "ru": "Russian", - "zh": "简体中文", - "test": "Test" } } From 6e3957c8588fdc16b8114d89e58095ca27c2dcd0 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 14:57:27 +0100 Subject: [PATCH 11/17] Scan2Mail recipe #360 --- docs/integration.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/integration.md b/docs/integration.md index 0d1dbd9a..8274f1e9 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -22,6 +22,42 @@ about paperless-ng resulted in You could integrate with Dropbox using [Dropbox-Uploader](https://github.com/andreafabrizi/Dropbox-Uploader) +## Scan2Mail + +1. Setup and configure [msmtp](https://wiki.debian.org/msmtp) and msmtp-mta as + described + [here](https://decatec.de/linux/linux-einfach-e-mails-versenden-mit-msmtp/) +2. Install the MIME packer [mpack](https://linux.die.net/man/1/mpack) with + `sudo apt install mpack` to send the scanned files +3. Setup [OCRmyPDF](https://github.com/jbarlow83/OCRmyPDF) as described + [here](https://ocrmypdf.readthedocs.io/en/latest/installation.html) + +Now create the following pipeline in your `config/config.local.js` + +```javascript +config.pipelines.push({ + extension: 'pdf', + description: 'ocrmypdf (Scan2Mail email@address.tld)', + get commands() { + return [ + 'convert @- -quality 92 tmp-%04d.jpg && ls tmp-*.jpg', + 'convert @- pdf:-', + `file="scan_$(date +"%d_%m_%Y-%H_%M").pdf" && ocrmypdf -l ${config.ocrLanguage} --deskew --rotate-pages --force-ocr - "$file" && mpack -s "Document from Scanner@Office" "$file" email@address.tld`, + 'ls scan_*.*' + ]; + } +}); +``` + +The important `Scan2Mail` line is: + +``` +file="scan_$(date +"%d_%m_%Y-%H_%M").pdf" && ocrmypdf -l ${config.ocrLanguage} --deskew --rotate-pages --force-ocr - "$file" && mpack -s "Document from Scanner@Office" "$file" email@address.tld +``` + +This sets a time-based filename, then OCRs and finally sends to +email@address.tld + ## Other recipes? If you have other recipes then please share them. From 0b99c11722b5fc49ebd2ab62084b965602663e9d Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 15:01:52 +0100 Subject: [PATCH 12/17] Fix cache resources exhausted in docker #356 --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 76ab05e9..9d61710f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,9 @@ RUN apt-get update \ && sed -i \ 's/policy domain="coder" rights="none" pattern="PDF"/policy domain="coder" rights="read | write" pattern="PDF"'/ \ /etc/ImageMagick-6/policy.xml \ + && sed -i \ + 's/policy domain="resource" name="disk" value="1GiB"/policy domain="resource" name="disk" value="8GiB"'/ \ + /etc/ImageMagick-6/policy.xml \ && npm install -g npm@7.11.2 # Create a known user From 17718d3226c918c98170a321474609a86640dd1d Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 15:26:23 +0100 Subject: [PATCH 13/17] Fix device name regression --- packages/server/src/device.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/server/src/device.js b/packages/server/src/device.js index aa86a4cf..6cef24dd 100644 --- a/packages/server/src/device.js +++ b/packages/server/src/device.js @@ -5,6 +5,7 @@ const Util = require('./util'); class Device { constructor(string) { this.id = ''; + this.name = ''; this.features = {}; this.string = string; this.parse(); @@ -39,6 +40,7 @@ class Device { const match = /All options specific to device `(.*)'/.exec(this.string); if (match) { this.id = match[1]; + this.name = this.id; } else { throw new Error('Scanimage output contains no matching expressions'); } From a93f66ad86c81298a3f319803c3aa13b9a4b16ab Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 16:46:00 +0100 Subject: [PATCH 14/17] Geometry bug fixes #350 --- packages/client/src/components/Scan.vue | 2 +- packages/server/src/scan-controller.js | 11 +++++------ packages/server/src/scanimage.js | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/client/src/components/Scan.vue b/packages/client/src/components/Scan.vue index ab6cda9b..f807222b 100644 --- a/packages/client/src/components/Scan.vue +++ b/packages/client/src/components/Scan.vue @@ -61,7 +61,7 @@ - + diff --git a/packages/server/src/scan-controller.js b/packages/server/src/scan-controller.js index dc20c141..bf1394af 100644 --- a/packages/server/src/scan-controller.js +++ b/packages/server/src/scan-controller.js @@ -131,7 +131,7 @@ class ScanController { async updatePreview(filename) { const dpmm = this.request.params.resolution / 25.4; const device = this.context.getDevice(this.request.params.deviceId); - const cmd = new CmdBuilder(Config.convert).arg(`'${Config.tempDirectory}/${filename}'`); + const cmdBuilder = new CmdBuilder(Config.convert).arg(`'${Config.tempDirectory}/${filename}'`); if (device.geometry) { const geometry = { width: device.features['-x'].limits[1] * dpmm, @@ -139,15 +139,14 @@ class ScanController { left: this.request.params.left * dpmm, top: this.request.params.top * dpmm }; - cmd.arg('-background', '#808080') + cmdBuilder.arg('-background', '#808080') .arg('-extent', `${geometry.width}x${geometry.height}-${geometry.left}-${geometry.top}`); } - cmd.arg('-resize', 868) - .arg(`'${Config.previewDirectory}/preview.tif'`) - .build(); + cmdBuilder.arg('-resize', 868) + .arg(`'${Config.previewDirectory}/preview.tif'`); - await Process.spawn(cmd); + await Process.spawn(cmdBuilder.build()); } /** diff --git a/packages/server/src/scanimage.js b/packages/server/src/scanimage.js index e7bb68c3..7da5a6d8 100644 --- a/packages/server/src/scanimage.js +++ b/packages/server/src/scanimage.js @@ -55,7 +55,7 @@ class Scanimage { cmdBuilder.arg('--resolution', params.resolution); - if (['-x', '-y', '-l', '-t'].every(s => s in params)) { + if (['left', 'top', 'width', 'height'].every(s => s in params)) { cmdBuilder.arg('-l', params.left) .arg('-t', params.top) .arg('-x', params.width) From 180ee41002992ba29dfac46e9cd0dae37fdaa261 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 20:57:05 +0100 Subject: [PATCH 15/17] Version bump --- README.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- packages/client/package-lock.json | 4 ++-- packages/client/package.json | 2 +- packages/server/package-lock.json | 4 ++-- packages/server/package.json | 2 +- 7 files changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7109f74f..eb8b496f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,11 @@ Copyright 2016-2021 [Sam Strachan](https://github.com/sbs20) > broken, meaning the device is useless by itself because one cannot trigger > scans, but with this project I can trigger it remotely just fine. + +> Absolutely love untethering my scanner from my laptop. Also means that I know +> it will work "forever", regardless of OS updates, since its all just a docker +> container. + ## About scanservjs is a web UI frontend for your scanner. It allows you to share one or diff --git a/package-lock.json b/package-lock.json index 3c97918e..2abaccb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanservjs", - "version": "2.17.2", + "version": "2.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scanservjs", - "version": "2.17.2", + "version": "2.18.0", "hasInstallScript": true, "license": "GPL-2.0" } diff --git a/package.json b/package.json index ffd4a573..c02e25d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scanservjs", - "version": "2.17.2", + "version": "2.18.0", "description": "scanservjs is a simple web-based UI for SANE which allows you to share a scanner on a network without the need for drivers or complicated installation.", "scripts": { "clean": "rm -rf ./dist", diff --git a/packages/client/package-lock.json b/packages/client/package-lock.json index 67204caa..6a5a4b05 100644 --- a/packages/client/package-lock.json +++ b/packages/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanservjs", - "version": "2.17.2", + "version": "2.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scanservjs", - "version": "2.17.2", + "version": "2.18.0", "license": "GPL-2.0", "dependencies": { "@mdi/font": "^5.9.55", diff --git a/packages/client/package.json b/packages/client/package.json index 3c561643..db896874 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "scanservjs", - "version": "2.17.2", + "version": "2.18.0", "description": "scanservjs is a simple web-based UI for SANE which allows you to share a scanner on a network without the need for drivers or complicated installation.", "author": "Sam Strachan", "scripts": { diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json index a387ccde..eeaac868 100644 --- a/packages/server/package-lock.json +++ b/packages/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanservjs-server", - "version": "2.17.2", + "version": "2.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scanservjs-server", - "version": "2.17.2", + "version": "2.18.0", "license": "GPL-2.0", "dependencies": { "adm-zip": "^0.5.5", diff --git a/packages/server/package.json b/packages/server/package.json index 18409740..9a7fb3ab 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "scanservjs-server", - "version": "2.17.2", + "version": "2.18.0", "description": "scanservjs is a simple web-based UI for SANE which allows you to share a scanner on a network without the need for drivers or complicated installation.", "scripts": { "lint": "gulp lint", From f024aff0dfb1e65c8d2b852b48148f5b3a92e384 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Wed, 29 Sep 2021 21:05:24 +0100 Subject: [PATCH 16/17] Remove commented code --- packages/server/src/feature.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/server/src/feature.js b/packages/server/src/feature.js index baed067a..c68d1446 100644 --- a/packages/server/src/feature.js +++ b/packages/server/src/feature.js @@ -43,7 +43,6 @@ class Feature { this.default = Number(this.default); } else if (this.parameters.indexOf('..') > -1) { - // this.options = [50, 75, 100, 150, 200, 300, 600, 1200]; this.range(this); const limits = this.limits; this.options = []; @@ -105,4 +104,4 @@ class Feature { } } -module.exports = Feature; \ No newline at end of file +module.exports = Feature; From c7ae280b717cfd8c70795f8bdd3948108cdd8065 Mon Sep 17 00:00:00 2001 From: sbs20 Date: Sun, 3 Oct 2021 14:49:09 +0100 Subject: [PATCH 17/17] docs --- docs/install.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 6da197cd..f78e90d0 100644 --- a/docs/install.md +++ b/docs/install.md @@ -2,7 +2,7 @@ ## One line install -* If you don't already have your scanner working, then you need to get +* If you don't already have your scanner working, then you must get [SANE installed and working](./sane.md) and check permissions etc. Your scanner can be attached to a different server / device if you're using saned. * If you're using a debian based distro then you can just use the installer @@ -11,6 +11,7 @@ ```sh curl -s https://raw.githubusercontent.com/sbs20/scanservjs/master/packages/server/installer.sh | sudo bash -s -- -a ``` + Note: the installer script will always install from the master branch. * If you're using Arch, then [@dadosch](https://github.com/dadosch) created a PKGBUILD script in Arch's AUR which allows Arch-distro-based users to quickly install and update scanservjs with any AUR helper, for example: