147 lines
3.9 KiB
TypeScript
147 lines
3.9 KiB
TypeScript
import { parse } from "std/flags";
|
|
import { iterateReader } from "std/streams/iterate_reader";
|
|
|
|
const getRandomString = () => `${+(new Date())}-${Math.round(Math.random() * 100000)}`;
|
|
|
|
const decoder = new TextDecoder();
|
|
const encoder = new TextEncoder();
|
|
|
|
(async () => {
|
|
const args = parse(Deno.args);
|
|
|
|
if (args.help || args.h) {
|
|
console.log(`Usage: scan2paperless [OPTION] [NAME]
|
|
|
|
Scan one or multiple pages into a PDF document ready for consumption by paperless
|
|
|
|
NAME will be the name of the document without extension.
|
|
|
|
Available options:
|
|
-d | --device=DEVICE Scanner device to use. Check \`scanimage -L\` for available devices.
|
|
This defaults to \$SCANNER_DEFAULT_DEVICE
|
|
-n | --pages=NUMBER Number of pages to scan. If you don't specify this, scan2paperless
|
|
will ask you if you want to continue.
|
|
-a | --auto Don't pause between pages. Only has an effect if --pages is set.
|
|
-o | --output-dir=DIR Directory to write final PDF to. This defaults to \$SCANNER_OUTPUT_DIR
|
|
-h | --help show this help message
|
|
|
|
Example:
|
|
|
|
scan2paperless \\
|
|
-d hp3900:libusb:005:002 \\
|
|
-n 5 \\
|
|
-o /var/lib/paperless/consume \\
|
|
bank-statement-01-2023
|
|
`);
|
|
|
|
Deno.exit(0);
|
|
}
|
|
|
|
const tempDir = await Deno.makeTempDir();
|
|
const device = args.d || args.device || Deno.env.get('SCANNER_DEFAULT_DEVICE');
|
|
const outputDir = args.d || args.device || Deno.env.get('SCANNER_OUTPUT_DIR');
|
|
const outputName = args._[0] || getRandomString();
|
|
|
|
const pagesToScanStr = args.n || args.pages;
|
|
const pagesToScan = (() => {
|
|
if (!pagesToScanStr) {
|
|
return Infinity;
|
|
}
|
|
|
|
const pagesToScanInt = parseInt(pagesToScanStr, 10);
|
|
|
|
if (isNaN(pagesToScanInt)) {
|
|
console.error(`Number of pages to scan does not appear to be a number: ${pagesToScanStr}`);
|
|
Deno.exit(1);
|
|
}
|
|
|
|
return pagesToScanInt;
|
|
})();
|
|
|
|
const automatic = (!!(args.a || args.auto) && pagesToScan < Infinity);
|
|
|
|
const files = [];
|
|
let pagesScanned = 0;
|
|
let cancelled = false;
|
|
|
|
do {
|
|
const fileName = `${tempDir}/${getRandomString()}.jpg`;
|
|
|
|
console.log(`Start scanning page ${pagesScanned + 1}`);
|
|
console.log(`Writing to ${fileName}`);
|
|
|
|
const scanimageProcess = Deno.run({
|
|
cmd: [
|
|
"scanimage",
|
|
"-d", device,
|
|
"--format=jpeg",
|
|
"--resolution", "300",
|
|
"--progress",
|
|
"-o", fileName,
|
|
],
|
|
|
|
stderr: 'piped',
|
|
});
|
|
|
|
let inScan = false;
|
|
for await (const chunk of iterateReader(scanimageProcess.stderr)) {
|
|
const line = decoder.decode(chunk);
|
|
|
|
if (
|
|
!automatic
|
|
&& pagesScanned !== 0
|
|
&& !inScan
|
|
&& line.startsWith('Progress: ')
|
|
) {
|
|
inScan = true;
|
|
|
|
Deno.kill(scanimageProcess.pid, 'SIGSTOP');
|
|
|
|
const answer = prompt(`Run another scan? (number ${pagesScanned + 1}) [Y/n]`);
|
|
|
|
if (answer && !['y', 'yes'].includes(answer.toLowerCase())) {
|
|
Deno.kill(scanimageProcess.pid, 'SIGKILL');
|
|
cancelled = true;
|
|
break;
|
|
} else {
|
|
Deno.kill(scanimageProcess.pid, 'SIGCONT');
|
|
cancelled = false;
|
|
}
|
|
}
|
|
|
|
Deno.stdout.write(encoder.encode(`\r${line}`));
|
|
}
|
|
|
|
const status = await scanimageProcess.status();
|
|
scanimageProcess.stderr.close();
|
|
|
|
if (!cancelled && status.code !== 0) {
|
|
console.error('Scan seems to have failed with status ', status.code);
|
|
}
|
|
|
|
pagesScanned++;
|
|
|
|
if (!cancelled) {
|
|
files.push(fileName);
|
|
}
|
|
} while(!cancelled && (automatic ? pagesScanned < pagesToScan : true));
|
|
|
|
const pdfFile = `${outputDir}/${outputName}.pdf`;
|
|
const img2pdfProcess = Deno.run({
|
|
cmd: [
|
|
"img2pdf",
|
|
"--output", pdfFile,
|
|
...files,
|
|
],
|
|
});
|
|
|
|
const pdfStatus = await img2pdfProcess.status();
|
|
|
|
if (pdfStatus.code !== 0) {
|
|
console.error('Error writing PDF');
|
|
Deno.exit(2);
|
|
}
|
|
|
|
console.log(`PDF written to ${pdfFile}`);
|
|
})();
|