Skip to content

Commit

Permalink
Add merge2 command which uses mutool
Browse files Browse the repository at this point in the history
  • Loading branch information
bader-nasser committed Oct 27, 2023
1 parent bee922b commit 670cd23
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 11 deletions.
27 changes: 16 additions & 11 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,43 +34,48 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

- name: install pdftk-java (windows only)
- name: install deps (windows only)
if: matrix.os == 'windows-latest'
run: |
choco install pdftk-java --params "'/AddToUserPath:yes /AddToSystemPath:yes'"
choco install mupdf
- name: install pdftk-java (macos only)
if: matrix.os == 'macos-latest'
run: brew install pdftk-java

- name: install pdftk-java (ubuntu only)
- name: install deps (ubuntu only)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt update
sudo apt install pdftk-java
sudo apt install mupdf-tools
- name: install deps (macos only)
if: matrix.os == 'macos-latest'
run: |
brew install pdftk-java
brew install mupdf
- run: pnpm install

- name: test
shell: bash
run: |
export PATH="/c/tools/pdftk-java:$PATH"
# alias pdftk=pdftk.bat
pnpm test-mocha
pnpm lint || pnpm fmt
shell: bash
- name: Pack cli
run: pnpm pack

- name: Install cli
run: npm i -g ./bader-nasser-pdftools-*.tgz
shell: bash
run: npm i -g ./bader-nasser-pdftools-*.tgz

- name: Use cli
shell: bash
run: |
export PATH="/c/tools/pdftk-java:$PATH"
pdf-tools -h
pdf-tools p -h
pdf-tools p test/docs/data.json
pdftools m -i test/pdfs/input-*.pdf -o output.pdf -c
shell: bash
pdftools m2 -i test/pdfs/input-*.pdf -o output2.pdf -c
pdftools m2 -i test/pdfs/input-*.pdf test/pdfs/input-2.pdf 2 -o output3.pdf -cl
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ to get some help in your [editor](https://json-schema.org/implementations.html#e
- [`pdftools extract`](#pdftools-extract)
- [`pdftools help [COMMANDS]`](#pdftools-help-commands)
- [`pdftools merge`](#pdftools-merge)
- [`pdftools merge2`](#pdftools-merge2)
- [`pdftools plugins`](#pdftools-plugins)
- [`pdftools plugins:inspect PLUGIN...`](#pdftools-pluginsinspect-plugin)
- [`pdftools plugins:install PLUGIN...`](#pdftools-pluginsinstall-plugin)
Expand Down Expand Up @@ -313,6 +314,59 @@ EXAMPLES

_See code: [src/commands/merge/index.ts](https://github.com/bader-nasser/pdftools/blob/v3.0.0/src/commands/merge/index.ts)_

## `pdftools merge2`

Merge PDFs (mutool)

```
USAGE
$ pdftools merge2 -i <value> -o <value> [-D] [-s] [-c | -d] [--compress-fonts | ] [--compress-images | ]
[-l] [-g] [--garbage-compact] [--garbage-deduplicate] [-k]
FLAGS
-D, --dry-run Pretend to work!
-c, --compress Compress all streams
-d, --decompress Decompress all streams (except compress-fonts/images)
-g, --garbage Garbage collect unused objects
-i, --input=<value>... (required) PDF files followed by comma-seperated page numbers or ranges
(e.g. cover.pdf part-*.pdf file.pdf 2,11,4-6,10-8 otherfile.pdf)
-k, --keep Keep output's name
-l, --linearize Optimize for web browsers (ALIASES: --optimize)
-o, --output=<value> (required) Output file
-s, --silent Work silently unless there is an error!
--compress-fonts Compress embedded fonts (ALIASES: --cf)
--compress-images Compress images (ALIASES: --ci)
--garbage-compact ... and compact cross reference table (ALIASES: --gc, --compact)
--garbage-deduplicate ... and remove duplicate objects (ALIASES: --gd, --deduplicate)
DESCRIPTION
Merge PDFs (mutool)
ALIASES
$ pdftools m2
$ pdftools join2
$ pdftools j2
EXAMPLES
Merge all .pdf files
$ pdftools merge2 -i *.pdf -o output.pdf
Merge all .pdf files that start with input- & compress the output
$ pdftools merge2 -i input-*.pdf -o output.pdf -c
Merge cover.pdf with all .pdf files that start with input-, and notes.pdf
$ pdftools merge2 -i cover.pdf input-*.pdf notes.pdf -o output.pdf
Merge all .pdf files and optimize the output for web browsers
$ pdftools merge2 -i input-*.pdf -o output -l
```

_See code: [src/commands/merge2/index.ts](https://github.com/bader-nasser/pdftools/blob/v3.0.0/src/commands/merge2/index.ts)_

## `pdftools plugins`

List installed plugins.
Expand Down
204 changes: 204 additions & 0 deletions src/commands/merge2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import path from 'node:path';
import {Flags} from '@oclif/core';
import fs from 'fs-extra';
import {globby} from 'globby';
import {
addExtension,
removeExtension,
// Removing the extension will make the built cli crash
} from '../../utils.js';
import {BaseCommandWithCompression} from '../../base-command-with-compression.js';

export default class Merge2 extends BaseCommandWithCompression {
static aliases = ['m2', 'join2', 'j2'];

static description = 'Merge PDFs (mutool)';

static examples = [
{
description: 'Merge all .pdf files',
command: '<%= config.bin %> <%= command.id %> -i *.pdf -o output.pdf',
},
{
description:
'Merge all .pdf files that start with input- & compress the output',
command: `<%= config.bin %> <%= command.id %> -i input-*.pdf -o output.pdf -c`,
},
{
description:
'Merge cover.pdf with all .pdf files that start with input-, and notes.pdf',
command: `<%= config.bin %> <%= command.id %> -i cover.pdf input-*.pdf notes.pdf -o output.pdf`,
},
{
description:
'Merge all .pdf files and optimize the output for web browsers',
command: `<%= config.bin %> <%= command.id %> -i input-*.pdf -o output -l`,
},
];

static flags = {
input: Flags.string({
char: 'i',
description: `PDF files followed by comma-seperated page numbers or ranges
(e.g. cover.pdf part-*.pdf file.pdf 2,11,4-6,10-8 otherfile.pdf)`,
required: true,
multiple: true,
}),
output: Flags.string({
char: 'o',
description: 'Output file',
required: true,
}),
decompress: Flags.boolean({
char: 'd',
description: 'Decompress all streams (except compress-fonts/images)',
exclusive: ['compress', 'compress-fonts', 'compress-images'],
}),
compress: Flags.boolean({
char: 'c',
description: 'Compress all streams',
exclusive: ['decompress'],
}),
'compress-fonts': Flags.boolean({
aliases: ['cf'],
description: 'Compress embedded fonts (ALIASES: --cf)',
exclusive: ['decompress'],
}),
'compress-images': Flags.boolean({
aliases: ['ci'],
description: 'Compress images (ALIASES: --ci)',
exclusive: ['decompress'],
}),
linearize: Flags.boolean({
char: 'l',
aliases: ['optimize'],
description: 'Optimize for web browsers (ALIASES: --optimize)',
}),
garbage: Flags.boolean({
char: 'g',
description: 'Garbage collect unused objects',
}),
'garbage-compact': Flags.boolean({
aliases: ['compact', 'gc'],
description:
'... and compact cross reference table (ALIASES: --gc, --compact)',
}),
'garbage-deduplicate': Flags.boolean({
aliases: ['deduplicate', 'gd'],
description:
'... and remove duplicate objects (ALIASES: --gd, --deduplicate)',
}),
keep: Flags.boolean({
char: 'k',
description: `Keep output's name`,
}),
};

async run(): Promise<void> {
const {flags} = await this.parse(Merge2);
const {
input,
output,
'dry-run': dryRun,
silent,
decompress,
compress,
'compress-fonts': compressFonts,
'compress-images': compressImages,
garbage,
'garbage-compact': garbageCompact,
'garbage-deduplicate': garbageDeduplicate,
linearize,
keep,
} = flags;
let finalOutput = removeExtension(output);

try {
const outputDirname = path.dirname(finalOutput);
await fs.ensureDir(outputDirname);
} catch (error) {
console.error(error);
this.exit(1);
}

const outputOptions = [];

if (linearize) {
outputOptions.push('linearize');
if (!keep) {
finalOutput = `${finalOutput}-linearized`;
}
}

if (compress) {
outputOptions.push('compress');
if (!keep) {
finalOutput = `${finalOutput}-compressed`;
}
}

if (compressFonts) {
outputOptions.push('compress-fonts');
if (!keep) {
finalOutput = `${finalOutput}-cf`;
}
}

if (compressImages) {
outputOptions.push('compress-images');
if (!keep) {
finalOutput = `${finalOutput}-ci`;
}
}

if (decompress) {
outputOptions.push('decompress');
if (!keep) {
finalOutput = `${finalOutput}-decompressed`;
}
}

if (garbage) {
outputOptions.push('garbage');
if (!keep) {
finalOutput = `${finalOutput}-garbaged`;
}
}

if (garbageCompact) {
outputOptions.push('garbage=compact');
if (!keep) {
finalOutput = `${finalOutput}-gc`;
}
}

if (garbageDeduplicate) {
outputOptions.push('garbage=deduplicate');
if (!keep) {
finalOutput = `${finalOutput}-gd`;
}
}

finalOutput = addExtension(finalOutput);
const inputWithPages: string[] = [];
for (const fileOrPage of input) {
const files = await globby(fileOrPage);
if (files.length > 0) {
inputWithPages.push(...files);
} else {
inputWithPages.push(fileOrPage);
}
}

const args = ['merge', '-o', finalOutput];
if (outputOptions.length > 0) {
args.push('-O', outputOptions.join(','));
}

args.push(...inputWithPages);

this.logger(`Creating ${finalOutput}...`, silent);
await this.execute('mutool', args, dryRun);
this.logger('Done.', silent);
}
}

0 comments on commit 670cd23

Please sign in to comment.