diff --git a/README.md b/README.md index 7c51bcfb..335b0852 100644 --- a/README.md +++ b/README.md @@ -50,58 +50,14 @@ Copyright 2016-2021 [Sam Strachan](https://github.com/sbs20) * [Manual installation](docs/install.md) * [Docker installation](docs/docker.md) +* [Scanner and SANE setup](docs/sane.md) * [Development notes](docs/development.md) -* [Configuring the scanner and SANE](docs/sane.md) ## Configuration and device override -If you want to override some specific configuration setting then you can do so -within `./config/config.local.js`. Take a copy of `./config/config.default.js` -and override the sections you want. Using docker you will need to map the volume -using `-v /my/local/path:/app/config` then create a file in your directory -called `config.local.js`. See [example source](./server/config/config.local.js) -for more options. - -```javascript -module.exports = { - /** - * @param {Configuration} config - */ - afterConfig(config) { - // Set default preview resolution - config.previewResolution = 150; - - // Add a custom print pipeline - config.pipelines.push({ - extension: 'pdf', - description: 'Print PDF', - commands: [ - 'convert @- -quality 92 tmp-%04d.jpg && ls tmp-*.jpg', - 'convert @- scan-0000.pdf', - 'lp -d MY_PRINTER scan-0000.pdf', - 'ls scan-*.*' - ] - }); - }, - - /** - * @param {ScanDevice[]} devices - */ - afterDevices(devices) { - // Override the defaults for plustek scanners - const device = devices.filter(d => d.id.startsWith('plustek'))[0]; - if (device) { - device.features['--mode'].default = 'Color'; - device.features['--resolution'].default = 150; - device.features['--resolution'].options = [75, 150, 300, 600]; - device.features['--brightness'].default = 0; - device.features['--contrast'].default = 5; - device.features['-x'].default = 215; - device.features['-y'].default = 297; - } - } -}; -``` +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. ## Why? diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 00000000..ed43a9d4 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,165 @@ +# Configuration overrides + +Sometimes scanners don't return quite what you want them to. Likewise, perhaps +scanservjs doesn't provide the defaults you want. Furtunately it's possible to +override most things you might want to. + +The two things you can modify are: +* `config`: These are the global settings which include things like: + * Server port + * Log level + * Preview resolution + * Output filename + * Pipelines (output format) +* `devices`: The device definitions which are reported by SANE which include + scanner dimensions and geometry, modes, resolutions and sources. + +TL;DR; copy `./config/config.default.js` to `config/config.local.js` and +override the sections you want. + +If you are using docker, then you will want to map the configuration directory +e.g. `-v /my/local/path:/app/config`. + +## How it works + +scanservjs looks for a file called `config/config.local.js` and attempts to call +two functions at different stages in the processing: +* `afterConfig(config)`: whenever a config is read, the result is passed to this + function before being either used or sent down tot he browser. +* `afterDevices(devices)`: whenever the devices are read, the result is passed + to this function before being used. +* See [example source](../server/config/config.default.js) for more options. + +## Example file + +```javascript +module.exports = { + /** + * @param {Configuration} config + */ + afterConfig(config) { + // Set default preview resolution + config.previewResolution = 150; + + // Add a custom print pipeline + config.pipelines.push({ + extension: 'pdf', + description: 'Print PDF', + commands: [ + 'convert @- -quality 92 tmp-%04d.jpg && ls tmp-*.jpg', + 'convert @- scan-0000.pdf', + 'lp -d MY_PRINTER scan-0000.pdf', + 'ls scan-*.*' + ] + }); + }, + + /** + * @param {ScanDevice[]} devices + */ + afterDevices(devices) { + // Override the defaults for plustek scanners + const device = devices.filter(d => d.id.startsWith('plustek'))[0]; + if (device) { + device.features['--mode'].default = 'Color'; + device.features['--resolution'].default = 150; + device.features['--resolution'].options = [75, 150, 300, 600]; + device.features['--brightness'].default = 0; + device.features['--contrast'].default = 5; + device.features['-x'].default = 215; + device.features['-y'].default = 297; + } + } +}; +``` + +## Recipes + +### Override default width, height and resolution + +You have an old Canon flatbed but it returns daft default values for width and +height. You also want to change the default resolution and limit the resolution +options. + +```javascript + /** + * @param {ScanDevice[]} devices + */ + afterDevices(devices) { + // Override the defaults for plustek scanners + const device = devices.filter(d => d.id.startsWith('plustek'))[0]; + if (device) { + device.features['--resolution'].default = 150; + device.features['--resolution'].options = [75, 150, 300, 600]; + device.features['-x'].default = 215; + device.features['-y'].default = 297; + } + } +``` + +### Override scanner dimensions + +Some scanners (I'm looking at you, Brother) report their dimensions +[incorrectly](https://github.com/sbs20/scanservjs/issues/103). This throws off +the cropping logic because scanservjs incorrectly trusts the SANE output. + +```javascript + afterDevices(devices) { + const device = devices.filter(d => d.id.includes('brother'))[0]; + if (device) { + device.features['-l'].limits = [0, 215]; + device.features['-t'].limits = [0, 297]; + device.features['-x'].default = 215; + device.features['-x'].limits = [0, 215]; + device.features['-y'].default = 297; + device.features['-y'].limits = [0, 297]; + } + } +``` + +### Insert your own pipelines + +You may wish to add your own custom pipelines. Pipelines are arrays of shell +commands which run after scans. To learn more read the +[example source](../server/config/config.default.js). This will insert your own +pipelines at the top of the list. + +```javascript + afterConfig(config) { + const pipelines = [ + { + extension: 'jpg', + description: 'TEST PIPELINE | Terrible quality', + commands: [ + 'convert @- -quality 20 scan-%04d.jpg', + 'ls scan-*.*' + ] + }, + { + extension: 'jpg', + description: 'TEST PIPELINE 2 | Silly quality', + commands: [ + 'convert @- -quality 99 scan-%04d.jpg', + 'ls scan-*.*' + ] + } + ]; + + config.pipelines.splice(0, 0, ...pipelines); + }, +``` + +### Change the log level and default scan filename + +```javascript +const dayjs = require('dayjs'); +module.exports = { + afterConfig(config) { + config.filename = () => { + return `my_filestem_${dayjs().format('DD-MM-YYYY HH-mm-ss')}`; + }; + + config.log.level = 'DEBUG'; + } +} +``` diff --git a/docs/docker.md b/docs/docker.md index c8269efb..35a41520 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -52,6 +52,10 @@ Depending on your setup you have a number of options. * Both translate to `/dev/bus/usb/001/003`. * The docker argument would be `--device=/dev/bus/usb/001/003:/dev/bus/usb/001/003` + * You may also need to adjust permissions on the USB port of the host e.g. + `chmod a+rw dev/bus/usb/001/003` - see + [this](https://github.com/sbs20/scanservjs/issues/221#issuecomment-828757430) + helpful answer for more. * If your scanner is driverless over the network, then [sane-airscan](https://github.com/alexpevzner/sane-airscan) should be able to diff --git a/server/package-lock.json b/server/package-lock.json index adcd0ade..6f378440 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,6 +1,6 @@ { "name": "scanservjs-server", - "version": "2.11.0", + "version": "2.11.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/server/package.json b/server/package.json index a6e16f5c..754c8896 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "scanservjs-server", - "version": "2.11.0", + "version": "2.11.1", "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. scanserv does not do image conversion or manipulation (beyond the bare minimum necessary for the purposes of browser preview) or OCR.", "scripts": { "serve": "nodemon --exec 'vue-cli-service serve'", diff --git a/server/src/config.js b/server/src/config.js index 9e31cd77..31353242 100644 --- a/server/src/config.js +++ b/server/src/config.js @@ -1,5 +1,6 @@ const dayjs = require('dayjs'); const userOptions = require('./user-options'); +const Constants = require('./constants'); const Package = require('../package.json'); let instance = null; @@ -54,6 +55,13 @@ class Config { ] }, + batchModes: [ + Constants.BATCH_NONE, + Constants.BATCH_MANUAL, + Constants.BATCH_AUTO, + Constants.BATCH_COLLATE_STANDARD + ], + filters: [ { description: 'filter.auto-level', diff --git a/server/src/context.js b/server/src/context.js index 46e5d01e..c747985d 100644 --- a/server/src/context.js +++ b/server/src/context.js @@ -30,6 +30,9 @@ class Context { /** @type {PaperSize[]} */ this.paperSizes = Config.paperSizes; + + /** @type {string[]} */ + this.batchModes = Config.batchModes; } /** diff --git a/server/src/types.js b/server/src/types.js index 57008489..d42988ee 100644 --- a/server/src/types.js +++ b/server/src/types.js @@ -71,6 +71,7 @@ * @property {Filter[]} filters * @property {Pipeline[]} pipelines * @property {PaperSize[]} paperSizes + * @property {string[]} batchModes */ /** diff --git a/webui/src/components/BatchDialog.vue b/webui/src/components/BatchDialog.vue index ce1fdacc..deeb2941 100644 --- a/webui/src/components/BatchDialog.vue +++ b/webui/src/components/BatchDialog.vue @@ -6,6 +6,7 @@ + {{ $t('batch-dialog.btn-cancel') }} {{ $t('batch-dialog.btn-finish') }} {{ $t('batch-dialog.btn-rescan') }} {{ $t('batch-dialog.btn-next') }} diff --git a/webui/src/components/Scan.vue b/webui/src/components/Scan.vue index 7d4d3810..e8b2de98 100644 --- a/webui/src/components/Scan.vue +++ b/webui/src/components/Scan.vue @@ -23,19 +23,12 @@ + { value: false, text: $t('scan.dynamic-lineart:disabled') }, + { value: true, text: $t('scan.dynamic-lineart:enabled') }]" + item-value="value" item-text="text"> + :items="batchModes" item-value="value" item-text="text"> { + const key = `batch-mode.${sanitiseLocaleKey(mode)}`; + let translation = this.$t(key); + return { + text: translation === key ? mode : translation, + value: mode + }; + }); + }, + filters() { return this.context.filters.map(f => { return { @@ -400,7 +405,7 @@ export default { // The cropper changes even when coordinates are set manually. This will // result in manually set values being overwritten because of rounding. // If someone is taking the trouble to set values manually then they - // should be preserved. We should only update the values if they breaach + // should be preserved. We should only update the values if they breach // a threshold or the scanner dimensions const scanner = this.deviceSize; const params = this.request.params; diff --git a/webui/src/locales/cs.json b/webui/src/locales/cs.json index e040e70e..905f646a 100644 --- a/webui/src/locales/cs.json +++ b/webui/src/locales/cs.json @@ -30,6 +30,14 @@ "version": "Verze" }, + "batch-mode": { + "none": "Vypnuto", + "manual": "Manuální (s výzvou)", + "auto": "Automatický (Podavač dokumentů)", + "auto-collate-standard": "Automatický (Kompletovat 1, 3... 4, 2)", + "auto-collate-reverse": "Automatický (Obrátit 1, 3... 2, 4)" + }, + "filter": { "auto-level": "Automatické zarovnání", "threshold": "Práh", @@ -38,7 +46,7 @@ "mode": { "color": "Barevný", - "halftone": "Halftone", + "halftone": "Polotón", "gray": "Černobílý", "lineart": "Perokresba", @@ -51,11 +59,11 @@ "source": { "flatbed": "Skenovací deska", - "adf": "ADF", - "auto": "Auto", - "left-aligned": "Left Aligned", - "centrally-aligned": "Centrally Aligned", - "duplex": "Duplex", + "adf": "Automatický podavač", + "auto": "Automaticky", + "left-aligned": "zarovnáno vlevo", + "centrally-aligned": "zarovnáno na střed", + "duplex": "oboustranně", "automatic document feeder": "@:source.adf", "automatic document feeder(left aligned)": "@:source.adf (@:source.left-aligned)", @@ -83,11 +91,6 @@ "dynamic-lineart:enabled": "Zapnuto", "dynamic-lineart:disabled": "Vypnuto", "batch": "Vícestránkový sken", - "batch:none": "Vypnuto", - "batch:manual": "Manuální (s výzvou)", - "batch:auto": "Automatický (Podavač dokumentů)", - "batch:auto-collate-standard": "Automatický (Kompletovat 1, 3... 4, 2)", - "batch:auto-collate-reverse": "Automatický (Obrátit 1, 3... 2, 4)", "filters": "Filtry", "format": "Formát", "btn-preview": "Náhled", diff --git a/webui/src/locales/de.json b/webui/src/locales/de.json index 4eb26349..57e83a61 100644 --- a/webui/src/locales/de.json +++ b/webui/src/locales/de.json @@ -30,6 +30,14 @@ "version": "Version" }, + "batch-mode": { + "none": "Aus", + "manual": "Manuell (mit Nachfrage)", + "auto": "Automatisch (Dokumenteneinzug)", + "auto-collate-standard": "Automatisch (Sortiert 1, 3... 4, 2)", + "auto-collate-reverse": "Automatisch (umgekehrte Reihenfolge 1, 3... 2, 4)" + }, + "filter": { "auto-level": "Automatische Farbjustierung", "threshold": "Schwellwert", @@ -83,11 +91,6 @@ "dynamic-lineart:enabled": "Aktiviert", "dynamic-lineart:disabled": "Deaktiviert", "batch": "Mehrseitiges Scannen", - "batch:none": "Aus", - "batch:manual": "Manuell (mit Nachfrage)", - "batch:auto": "Automatisch (Dokumenteneinzug)", - "batch:auto-collate-standard": "Automatisch (Sortiert 1, 3... 4, 2)", - "batch:auto-collate-reverse": "Automatisch (umgekehrte Reihenfolge 1, 3... 2, 4)", "filters": "Filter", "format": "Format", "btn-preview": "Vorschau", diff --git a/webui/src/locales/en.json b/webui/src/locales/en.json index 7e2fc324..5d8b9012 100644 --- a/webui/src/locales/en.json +++ b/webui/src/locales/en.json @@ -30,6 +30,14 @@ "version": "Version" }, + "batch-mode": { + "none": "None", + "manual": "Manual (with prompt)", + "auto": "Auto (Document feeder)", + "auto-collate-standard": "Auto (Collate 1, 3... 4, 2)", + "auto-collate-reverse": "Auto (Reverse 1, 3... 2, 4)" + }, + "filter": { "auto-level": "Auto level", "threshold": "Threshold", @@ -83,11 +91,6 @@ "dynamic-lineart:enabled": "Enabled", "dynamic-lineart:disabled": "Disabled", "batch": "Batch", - "batch:none": "None", - "batch:manual": "Manual (with prompt)", - "batch:auto": "Auto (Document feeder)", - "batch:auto-collate-standard": "Auto (Collate 1, 3... 4, 2)", - "batch:auto-collate-reverse": "Auto (Reverse 1, 3... 2, 4)", "filters": "Filters", "format": "Format", "btn-preview": "Preview", diff --git a/webui/src/locales/es.json b/webui/src/locales/es.json index 327a291c..da2ca9ea 100644 --- a/webui/src/locales/es.json +++ b/webui/src/locales/es.json @@ -30,6 +30,14 @@ "version": "Versión" }, + "batch-mode": { + "none": "Nada", + "manual": "Manual (con aviso)", + "auto": "Auto (alimentador automático)", + "auto-collate-standard": "Auto (ordenación 1, 3... 4, 2)", + "auto-collate-reverse": "Auto (inversa 1, 3... 2, 4)" + }, + "filter": { "auto-level": "Autonivelar", "threshold": "Umbral", @@ -83,11 +91,6 @@ "dynamic-lineart:enabled": "Activado", "dynamic-lineart:disabled": "Desactivado", "batch": "Lote", - "batch:none": "Nada", - "batch:manual": "Manual (con aviso)", - "batch:auto": "Auto (alimentador automático)", - "batch:auto-collate-standard": "Auto (ordenación 1, 3... 4, 2)", - "batch:auto-collate-reverse": "Auto (inversa 1, 3... 2, 4)", "filters": "Filtros", "format": "Formato", "btn-preview": "Vista previa", diff --git a/webui/src/locales/fr.json b/webui/src/locales/fr.json index bc394bf7..bb2c5c45 100644 --- a/webui/src/locales/fr.json +++ b/webui/src/locales/fr.json @@ -10,8 +10,8 @@ "batch-dialog": { "btn-cancel": "Annuler", - "btn-finish": "Finish", - "btn-rescan": "Rescan page", + "btn-finish": "Terminer", + "btn-rescan": "Re-numériser page", "btn-next": "Suivant" }, @@ -30,6 +30,14 @@ "version": "Version" }, + "batch-mode": { + "none": "Sans", + "manual": "Manuel (avec popup)", + "auto": "Auto (Chargeur de documents)", + "auto-collate-standard": "Assemblage auto (Standard 1, 3... 4, 2)", + "auto-collate-reverse": "Assemblage auto (Inversé 1, 3... 2, 4)" + }, + "filter": { "auto-level": "Automatique", "threshold": "Seuil", @@ -83,16 +91,11 @@ "dynamic-lineart:enabled": "Activé", "dynamic-lineart:disabled": "Désactivé", "batch": "Scan groupé", - "batch:none": "Sans", - "batch:manual": "Manuel (avec popup)", - "batch:auto": "Auto (Chargeur de documents)", - "batch:auto-collate-standard": "Assemblage auto (Standard 1, 3... 4, 2)", - "batch:auto-collate-reverse": "Assemblage auto (Inversé 1, 3... 2, 4)", "filters": "Filtres", "format": "Format", - "btn-preview": "Pré-visualiser", + "btn-preview": "Aperçu", "btn-clear": "Effacer", - "btn-scan": "Scanner", + "btn-scan": "Numériser", "btn-reset": "Réinitialiser", "top": "Haut", "left": "Gauche", diff --git a/webui/src/locales/it.json b/webui/src/locales/it.json index 16aedf3f..608e2f70 100644 --- a/webui/src/locales/it.json +++ b/webui/src/locales/it.json @@ -30,6 +30,14 @@ "version": "Versione" }, + "batch-mode": { + "none": "No", + "manual": "Manuale (Con finestra guidata)", + "auto": "Automatico (Cassetto dei documenti)", + "auto-collate-standard": "Automatico (Fascicola 1, 3... 4, 2)", + "auto-collate-reverse": "Automatico (Inverso 1, 3... 2, 4)" + }, + "filter": { "auto-level": "Livello automatico", "threshold": "Soglia", @@ -83,11 +91,6 @@ "dynamic-lineart:enabled": "Abilitato", "dynamic-lineart:disabled": "Disabilitato", "batch": "Raggruppa", - "batch:none": "No", - "batch:manual": "Manuale (Con finestra guidata)", - "batch:auto": "Automatico (Cassetto dei documenti)", - "batch:auto-collate-standard": "Automatico (Fascicola 1, 3... 4, 2)", - "batch:auto-collate-reverse": "Automatico (Inverso 1, 3... 2, 4)", "filters": "Filtri", "format": "Formato", "btn-preview": "Anteprima", diff --git a/webui/src/locales/ru.json b/webui/src/locales/ru.json index 55249d37..68609d1d 100644 --- a/webui/src/locales/ru.json +++ b/webui/src/locales/ru.json @@ -30,6 +30,14 @@ "version": "Версия" }, + "batch-mode": { + "none": "Не выбрано", + "manual": "Вручную (с предпросмотром)", + "auto": "Авто (в порядке сканирование)", + "auto-collate-standard": "Авто (Сопоставить 1, 3... 4, 2)", + "auto-collate-reverse": "Авто (В обратном порядке 1, 3... 2, 4)" + }, + "filter": { "auto-level": "Автоматическое выравнивание", "threshold": "Порог", @@ -83,11 +91,6 @@ "dynamic-lineart:enabled": "Включено", "dynamic-lineart:disabled": "Выключено", "batch": "Пакетный режим", - "batch:none": "Не выбрано", - "batch:manual": "Вручную (с предпросмотром)", - "batch:auto": "Авто (в порядке сканирование)", - "batch:auto-collate-standard": "Авто (Сопоставить 1, 3... 4, 2)", - "batch:auto-collate-reverse": "Авто (В обратном порядке 1, 3... 2, 4)", "filters": "Фильтры", "format": "Формат", "btn-preview": "Предпросмотр", diff --git a/webui/src/locales/test.json b/webui/src/locales/test.json index 3e36c518..2a993766 100644 --- a/webui/src/locales/test.json +++ b/webui/src/locales/test.json @@ -31,6 +31,14 @@ "version": "##VERSION" }, + "batch-mode": { + "none": "##NONE", + "manual": "##MANUAL", + "auto": "##AUTO", + "auto-collate-standard": "##COLLATE-STANDARD", + "auto-collate-reverse": "##COLLATE-REVERSE" + }, + "filter": { "auto-level": "##SCAN.FILTERS:AUTO-LEVEL", "threshold": "##SCAN.FILTERS:THRESHOLD", @@ -84,11 +92,6 @@ "dynamic-lineart:enabled": "##ENABLED", "dynamic-lineart:disabled": "##DISABLED", "batch": "##SCAN.BATCH", - "batch:none": "##NONE", - "batch:manual": "##MANUAL", - "batch:auto": "##AUTO", - "batch:auto-collate-standard": "##COLLATE-STANDARD", - "batch:auto-collate-reverse": "##COLLATE-REVERSE", "filters": "##SCAN.FILTERS", "format": "##SCAN.FORMAT", "btn-preview": "##PREVIEW", diff --git a/webui/src/locales/zh.json b/webui/src/locales/zh.json index 64f5d816..f8f537f1 100644 --- a/webui/src/locales/zh.json +++ b/webui/src/locales/zh.json @@ -30,6 +30,14 @@ "version": "版本" }, + "batch-mode": { + "none": "无", + "manual": "手动 (有提示)", + "auto": "自动 (进纸器)", + "auto-collate-standard": "自动 (标准校对 1, 3... 4, 2)", + "auto-collate-reverse": "自动 (逆向校对 1, 3... 2, 4)" + }, + "filter": { "auto-level": "自动调整", "threshold": "阈值", @@ -83,11 +91,6 @@ "dynamic-lineart:enabled": "开启", "dynamic-lineart:disabled": "关闭", "batch": "批量", - "batch:none": "无", - "batch:manual": "手动 (有提示)", - "batch:auto": "自动 (进纸器)", - "batch:auto-collate-standard": "自动 (标准校对 1, 3... 4, 2)", - "batch:auto-collate-reverse": "自动 (逆向校对 1, 3... 2, 4)", "filters": "滤镜", "format": "格式", "btn-preview": "预览",