pdf2epub 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -110
- package/package.json +9 -44
- package/pdf2epub.js +624 -0
- package/LICENSE +0 -22
- package/bin/pdf2epub.js +0 -14
- package/lib/cli.js +0 -134
- package/lib/paths.js +0 -44
- package/lib/python.js +0 -76
- package/lib/setup.js +0 -152
- package/vendor/pdf2epub/LICENSE +0 -9
- package/vendor/pdf2epub/README.md +0 -217
- package/vendor/pdf2epub/UPSTREAM.json +0 -8
- package/vendor/pdf2epub/main.py +0 -118
- package/vendor/pdf2epub/modules/__init__.py +0 -0
- package/vendor/pdf2epub/modules/mark2epub.py +0 -696
- package/vendor/pdf2epub/modules/pdf2md.py +0 -172
- package/vendor/pdf2epub/modules/postprocessing/__init__.py +0 -0
- package/vendor/pdf2epub/modules/postprocessing/prompt.txt +0 -41
- package/vendor/pdf2epub/modules/postprocessing/template.py +0 -177
- package/vendor/pdf2epub/requirements.txt +0 -8
package/lib/cli.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const { spawnSync } = require('child_process');
|
|
5
|
-
|
|
6
|
-
const paths = require('./paths');
|
|
7
|
-
const setup = require('./setup');
|
|
8
|
-
const { findPython, findUv } = require('./python');
|
|
9
|
-
|
|
10
|
-
const WRAPPER_COMMANDS = new Set(['setup', 'info', 'clean', 'help', '--help', '-h', '--version', '-V']);
|
|
11
|
-
|
|
12
|
-
function versionLine() {
|
|
13
|
-
const up = setup.upstreamInfo();
|
|
14
|
-
return `pdf2epub ${paths.pkg.version} (npm wrapper) / upstream ${up.repo}@${up.ref} ${String(up.commit).slice(0, 7)}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function usage() {
|
|
18
|
-
return `${versionLine()}
|
|
19
|
-
|
|
20
|
-
Convert PDF files to structured Markdown and EPUB (via marker-pdf).
|
|
21
|
-
|
|
22
|
-
Usage:
|
|
23
|
-
pdf2epub [input_path] [output_path] [options]
|
|
24
|
-
pdf2epub <command>
|
|
25
|
-
|
|
26
|
-
Upstream options (passed through to pdf2epub's main.py):
|
|
27
|
-
input_path PDF file or directory (default: ./input/*.pdf)
|
|
28
|
-
output_path Output directory (default: directory named after the PDF)
|
|
29
|
-
--max-pages INT Maximum number of pages to process
|
|
30
|
-
--start-page INT Page number to start from (requires --max-pages)
|
|
31
|
-
--skip-epub Only create Markdown; no interactive EPUB prompts
|
|
32
|
-
--skip-md Reuse existing Markdown, only build the EPUB
|
|
33
|
-
|
|
34
|
-
Wrapper commands:
|
|
35
|
-
setup [--force] Create (or recreate) the Python environment now
|
|
36
|
-
info Show install location, Python, and upstream version
|
|
37
|
-
clean Delete the Python environment (${paths.homeDir()})
|
|
38
|
-
help, --help, -h Show this help
|
|
39
|
-
--version, -V Show versions
|
|
40
|
-
|
|
41
|
-
Use "pdf2epub -- <args>" to pass arguments straight to upstream, e.g.
|
|
42
|
-
"pdf2epub -- --help" or when your input path is literally named "setup".
|
|
43
|
-
|
|
44
|
-
Environment:
|
|
45
|
-
PDF2EPUB_HOME Where the Python venv lives (default: ~/.pdf2epub)
|
|
46
|
-
PDF2EPUB_PYTHON Python interpreter (or version for uv) to use
|
|
47
|
-
PDF2EPUB_INSTALLER "auto" (default: uv if available, else pip) or "pip"
|
|
48
|
-
HF_HOME Where marker-pdf caches its models (downloaded on first run)
|
|
49
|
-
|
|
50
|
-
Notes:
|
|
51
|
-
The first run installs PyTorch and marker-pdf into a private venv and marker
|
|
52
|
-
downloads its models; both happen once. EPUB generation prompts for metadata
|
|
53
|
-
interactively, so it needs a terminal; use --skip-epub for non-interactive use.
|
|
54
|
-
`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function info() {
|
|
58
|
-
const up = setup.upstreamInfo();
|
|
59
|
-
const state = setup.readState();
|
|
60
|
-
const lines = [
|
|
61
|
-
versionLine(),
|
|
62
|
-
`home: ${paths.homeDir()}`,
|
|
63
|
-
`venv: ${paths.venvDir()}${fs.existsSync(paths.venvPython()) ? '' : ' (not created)'}`,
|
|
64
|
-
`installed: ${setup.isInstalled() ? 'yes' : state ? 'stale (run: pdf2epub setup)' : 'no (runs automatically on first use)'}`,
|
|
65
|
-
`upstream: https://github.com/${up.repo}/tree/${up.commit || up.ref}`,
|
|
66
|
-
`vendor: ${paths.vendorDir}`,
|
|
67
|
-
];
|
|
68
|
-
if (state) {
|
|
69
|
-
lines.push(`installer: ${state.installer}`, `installedAt: ${state.installedAt}`);
|
|
70
|
-
} else {
|
|
71
|
-
const uv = findUv();
|
|
72
|
-
const py = findPython();
|
|
73
|
-
lines.push(`uv: ${uv ? uv.version : 'not found'}`);
|
|
74
|
-
lines.push(`python: ${py ? `${py.version} (${[py.cmd, ...py.args].join(' ')})` : 'no suitable interpreter found (need 3.10-3.13)'}`);
|
|
75
|
-
}
|
|
76
|
-
return lines.join('\n') + '\n';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function runUpstream(args) {
|
|
80
|
-
const python = setup.ensureInstalled();
|
|
81
|
-
const res = spawnSync(python, [paths.mainPy, ...args], {
|
|
82
|
-
stdio: 'inherit',
|
|
83
|
-
env: {
|
|
84
|
-
...process.env,
|
|
85
|
-
PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED || '1',
|
|
86
|
-
// The vendored source may live in a read-only global node_modules; keep it pristine.
|
|
87
|
-
PYTHONDONTWRITEBYTECODE: process.env.PYTHONDONTWRITEBYTECODE || '1',
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
if (res.error) throw res.error;
|
|
91
|
-
if (res.signal) return 128 + (res.signal === 'SIGINT' ? 2 : 1);
|
|
92
|
-
return res.status == null ? 1 : res.status;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function main(argv) {
|
|
96
|
-
const [first, ...rest] = argv;
|
|
97
|
-
|
|
98
|
-
if (first === '--') return runUpstream(rest);
|
|
99
|
-
|
|
100
|
-
if (!WRAPPER_COMMANDS.has(first)) return runUpstream(argv);
|
|
101
|
-
|
|
102
|
-
switch (first) {
|
|
103
|
-
case '--version':
|
|
104
|
-
case '-V':
|
|
105
|
-
process.stdout.write(versionLine() + '\n');
|
|
106
|
-
return 0;
|
|
107
|
-
case 'help':
|
|
108
|
-
case '--help':
|
|
109
|
-
case '-h':
|
|
110
|
-
process.stdout.write(usage());
|
|
111
|
-
return 0;
|
|
112
|
-
case 'info':
|
|
113
|
-
process.stdout.write(info());
|
|
114
|
-
return 0;
|
|
115
|
-
case 'setup': {
|
|
116
|
-
const force = rest.includes('--force');
|
|
117
|
-
if (!force && setup.isInstalled()) {
|
|
118
|
-
setup.log(`Already set up in ${paths.homeDir()} (use --force to reinstall).`);
|
|
119
|
-
return 0;
|
|
120
|
-
}
|
|
121
|
-
setup.ensureInstalled({ force });
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
case 'clean': {
|
|
125
|
-
const removed = setup.clean();
|
|
126
|
-
setup.log(removed ? `Removed ${paths.homeDir()}` : `Nothing to remove at ${paths.homeDir()}`);
|
|
127
|
-
return 0;
|
|
128
|
-
}
|
|
129
|
-
default:
|
|
130
|
-
return runUpstream(argv);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
module.exports = { main, usage, info, versionLine };
|
package/lib/paths.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
const pkg = require('../package.json');
|
|
7
|
-
|
|
8
|
-
const isWindows = process.platform === 'win32';
|
|
9
|
-
|
|
10
|
-
function homeDir() {
|
|
11
|
-
return process.env.PDF2EPUB_HOME || path.join(os.homedir(), '.pdf2epub');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function venvDir() {
|
|
15
|
-
return path.join(homeDir(), 'venv');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function venvPython() {
|
|
19
|
-
return isWindows
|
|
20
|
-
? path.join(venvDir(), 'Scripts', 'python.exe')
|
|
21
|
-
: path.join(venvDir(), 'bin', 'python');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function stateFile() {
|
|
25
|
-
return path.join(homeDir(), 'state.json');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const vendorDir = path.resolve(__dirname, '..', 'vendor', 'pdf2epub');
|
|
29
|
-
const mainPy = path.join(vendorDir, 'main.py');
|
|
30
|
-
const requirementsFile = path.join(vendorDir, 'requirements.txt');
|
|
31
|
-
const upstreamFile = path.join(vendorDir, 'UPSTREAM.json');
|
|
32
|
-
|
|
33
|
-
module.exports = {
|
|
34
|
-
pkg,
|
|
35
|
-
isWindows,
|
|
36
|
-
homeDir,
|
|
37
|
-
venvDir,
|
|
38
|
-
venvPython,
|
|
39
|
-
stateFile,
|
|
40
|
-
vendorDir,
|
|
41
|
-
mainPy,
|
|
42
|
-
requirementsFile,
|
|
43
|
-
upstreamFile,
|
|
44
|
-
};
|
package/lib/python.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { spawnSync } = require('child_process');
|
|
4
|
-
const { isWindows } = require('./paths');
|
|
5
|
-
|
|
6
|
-
// Upstream supports Python 3.10-3.14; 3.13 is recommended. On 3.14 Pillow has
|
|
7
|
-
// to be built from source (marker-pdf pins Pillow<11), so treat it as a last
|
|
8
|
-
// resort.
|
|
9
|
-
const MIN = [3, 10];
|
|
10
|
-
const PREFERRED_MAX = [3, 13];
|
|
11
|
-
const HARD_MAX = [3, 14];
|
|
12
|
-
|
|
13
|
-
function cmp(a, b) {
|
|
14
|
-
return a[0] - b[0] || a[1] - b[1];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function probe(cmd, args) {
|
|
18
|
-
const res = spawnSync(
|
|
19
|
-
cmd,
|
|
20
|
-
[...args, '-c', 'import sys; print("%d.%d.%d" % sys.version_info[:3])'],
|
|
21
|
-
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 15000 }
|
|
22
|
-
);
|
|
23
|
-
if (res.error || res.status !== 0) return null;
|
|
24
|
-
const m = /^(\d+)\.(\d+)\.(\d+)/.exec((res.stdout || '').trim());
|
|
25
|
-
if (!m) return null;
|
|
26
|
-
return {
|
|
27
|
-
cmd,
|
|
28
|
-
args,
|
|
29
|
-
version: m[0],
|
|
30
|
-
tuple: [Number(m[1]), Number(m[2])],
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function candidates() {
|
|
35
|
-
const list = [];
|
|
36
|
-
if (process.env.PDF2EPUB_PYTHON) {
|
|
37
|
-
list.push({ cmd: process.env.PDF2EPUB_PYTHON, args: [] });
|
|
38
|
-
return list;
|
|
39
|
-
}
|
|
40
|
-
for (const v of ['3.13', '3.12', '3.11', '3.10', '3.14']) {
|
|
41
|
-
list.push({ cmd: `python${v}`, args: [] });
|
|
42
|
-
if (isWindows) list.push({ cmd: 'py', args: [`-${v}`] });
|
|
43
|
-
}
|
|
44
|
-
list.push({ cmd: 'python3', args: [] });
|
|
45
|
-
list.push({ cmd: 'python', args: [] });
|
|
46
|
-
if (isWindows) list.push({ cmd: 'py', args: ['-3'] });
|
|
47
|
-
return list;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Find a Python interpreter suitable for the upstream project.
|
|
52
|
-
* Returns { cmd, args, version, tuple, fallback } or null.
|
|
53
|
-
*/
|
|
54
|
-
function findPython() {
|
|
55
|
-
let fallback = null;
|
|
56
|
-
for (const c of candidates()) {
|
|
57
|
-
const p = probe(c.cmd, c.args);
|
|
58
|
-
if (!p) continue;
|
|
59
|
-
if (cmp(p.tuple, MIN) < 0) continue;
|
|
60
|
-
if (cmp(p.tuple, PREFERRED_MAX) <= 0) return { ...p, fallback: false };
|
|
61
|
-
if (cmp(p.tuple, HARD_MAX) <= 0 && !fallback) fallback = { ...p, fallback: true };
|
|
62
|
-
}
|
|
63
|
-
return fallback;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function findUv() {
|
|
67
|
-
const res = spawnSync('uv', ['--version'], {
|
|
68
|
-
encoding: 'utf8',
|
|
69
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
70
|
-
timeout: 15000,
|
|
71
|
-
});
|
|
72
|
-
if (res.error || res.status !== 0) return null;
|
|
73
|
-
return { cmd: 'uv', version: (res.stdout || '').trim() };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
module.exports = { findPython, findUv, probe, MIN, PREFERRED_MAX, HARD_MAX };
|
package/lib/setup.js
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const crypto = require('crypto');
|
|
5
|
-
const { spawnSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
const paths = require('./paths');
|
|
8
|
-
const { findPython, findUv } = require('./python');
|
|
9
|
-
|
|
10
|
-
function log(msg) {
|
|
11
|
-
process.stderr.write(`[pdf2epub] ${msg}\n`);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function readJson(file, fallback) {
|
|
15
|
-
try {
|
|
16
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
17
|
-
} catch {
|
|
18
|
-
return fallback;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function upstreamInfo() {
|
|
23
|
-
return readJson(paths.upstreamFile, paths.pkg.pdf2epub.upstream);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Hash of everything that, when changed, requires reinstalling the venv. */
|
|
27
|
-
function installHash() {
|
|
28
|
-
const h = crypto.createHash('sha256');
|
|
29
|
-
h.update(fs.readFileSync(paths.requirementsFile));
|
|
30
|
-
h.update('\n');
|
|
31
|
-
h.update(String(upstreamInfo().commit || ''));
|
|
32
|
-
return h.digest('hex');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function readState() {
|
|
36
|
-
return readJson(paths.stateFile(), null);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isInstalled() {
|
|
40
|
-
const state = readState();
|
|
41
|
-
return Boolean(state && fs.existsSync(paths.venvPython()) && state.hash === installHash());
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function run(cmd, args, opts = {}) {
|
|
45
|
-
log(`$ ${[cmd, ...args].join(' ')}`);
|
|
46
|
-
const res = spawnSync(cmd, args, { stdio: 'inherit', ...opts });
|
|
47
|
-
if (res.error) throw res.error;
|
|
48
|
-
return res.status;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function installWithUv(uv) {
|
|
52
|
-
const pyArg = process.env.PDF2EPUB_PYTHON || '3.13';
|
|
53
|
-
if (run(uv.cmd, ['venv', '--python', pyArg, paths.venvDir()]) !== 0) return false;
|
|
54
|
-
return (
|
|
55
|
-
run(uv.cmd, [
|
|
56
|
-
'pip',
|
|
57
|
-
'install',
|
|
58
|
-
'--python',
|
|
59
|
-
paths.venvPython(),
|
|
60
|
-
'-r',
|
|
61
|
-
paths.requirementsFile,
|
|
62
|
-
]) === 0
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function installWithPip(python) {
|
|
67
|
-
if (run(python.cmd, [...python.args, '-m', 'venv', paths.venvDir()]) !== 0) return false;
|
|
68
|
-
const vp = paths.venvPython();
|
|
69
|
-
// Failing to upgrade pip is not fatal.
|
|
70
|
-
run(vp, ['-m', 'pip', 'install', '--upgrade', 'pip']);
|
|
71
|
-
return run(vp, ['-m', 'pip', 'install', '-r', paths.requirementsFile]) === 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Make sure the Python environment exists and matches the bundled upstream.
|
|
76
|
-
* Returns the path to the venv's python executable.
|
|
77
|
-
*/
|
|
78
|
-
function ensureInstalled({ force = false } = {}) {
|
|
79
|
-
if (!force && isInstalled()) return paths.venvPython();
|
|
80
|
-
|
|
81
|
-
const up = upstreamInfo();
|
|
82
|
-
log(`Setting up Python environment in ${paths.homeDir()}`);
|
|
83
|
-
log(`Upstream: ${up.repo}@${up.ref} (${String(up.commit).slice(0, 7)})`);
|
|
84
|
-
log('This installs PyTorch and marker-pdf (several hundred MB); it only happens once.');
|
|
85
|
-
|
|
86
|
-
if (fs.existsSync(paths.venvDir())) {
|
|
87
|
-
log('Removing existing environment');
|
|
88
|
-
fs.rmSync(paths.venvDir(), { recursive: true, force: true });
|
|
89
|
-
}
|
|
90
|
-
fs.mkdirSync(paths.homeDir(), { recursive: true });
|
|
91
|
-
|
|
92
|
-
const installer = (process.env.PDF2EPUB_INSTALLER || 'auto').toLowerCase();
|
|
93
|
-
let ok = false;
|
|
94
|
-
let used = null;
|
|
95
|
-
|
|
96
|
-
const uv = installer === 'pip' ? null : findUv();
|
|
97
|
-
if (uv) {
|
|
98
|
-
log(`Using ${uv.version}`);
|
|
99
|
-
ok = installWithUv(uv);
|
|
100
|
-
used = 'uv';
|
|
101
|
-
if (!ok) {
|
|
102
|
-
log('uv install failed; falling back to venv + pip');
|
|
103
|
-
fs.rmSync(paths.venvDir(), { recursive: true, force: true });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!ok) {
|
|
108
|
-
const python = findPython();
|
|
109
|
-
if (!python) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
'No suitable Python found. pdf2epub needs Python 3.10-3.13 (3.13 recommended).\n' +
|
|
112
|
-
' Install Python 3.13 (https://www.python.org/downloads/) or uv (https://docs.astral.sh/uv/),\n' +
|
|
113
|
-
' or point PDF2EPUB_PYTHON at an interpreter.'
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
if (python.fallback) {
|
|
117
|
-
log(
|
|
118
|
-
`Warning: using Python ${python.version}; 3.13 is recommended. ` +
|
|
119
|
-
'Pillow will be built from source, which needs a C toolchain and image headers.'
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
log(`Using Python ${python.version} (${[python.cmd, ...python.args].join(' ')})`);
|
|
123
|
-
ok = installWithPip(python);
|
|
124
|
-
used = 'pip';
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!ok) {
|
|
128
|
-
throw new Error(`Python dependency installation failed. See output above. Retry with: pdf2epub setup --force`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const state = {
|
|
132
|
-
hash: installHash(),
|
|
133
|
-
upstream: up,
|
|
134
|
-
wrapperVersion: paths.pkg.version,
|
|
135
|
-
installer: used,
|
|
136
|
-
python: paths.venvPython(),
|
|
137
|
-
installedAt: new Date().toISOString(),
|
|
138
|
-
};
|
|
139
|
-
fs.writeFileSync(paths.stateFile(), JSON.stringify(state, null, 2) + '\n');
|
|
140
|
-
log('Setup complete.');
|
|
141
|
-
return paths.venvPython();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function clean() {
|
|
145
|
-
if (fs.existsSync(paths.homeDir())) {
|
|
146
|
-
fs.rmSync(paths.homeDir(), { recursive: true, force: true });
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
module.exports = { ensureInstalled, isInstalled, installHash, readState, upstreamInfo, clean, log };
|
package/vendor/pdf2epub/LICENSE
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 overcuriousity
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
-
|
|
7
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
-
|
|
9
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
# PDF2EPUB 📚
|
|
2
|
-
|
|
3
|
-
Convert PDF files to nicely structured Markdown and EPUB format with intelligent layout detection.
|
|
4
|
-
|
|
5
|
-
## ✨ Features
|
|
6
|
-
|
|
7
|
-
- 📖 Smart layout detection for books and academic papers
|
|
8
|
-
- 🔍 Advanced text extraction and OCR capabilities
|
|
9
|
-
- 📊 Table detection and formatting
|
|
10
|
-
- 🖼️ Image extraction and optimization
|
|
11
|
-
- 📝 Clean markdown output with preserved structure
|
|
12
|
-
- 📱 EPUB generation with customizable styling
|
|
13
|
-
- 🌍 Multi-language support
|
|
14
|
-
- 🚀 GPU acceleration support (NVIDIA & AMD)
|
|
15
|
-
- 🍎 Apple Silicon support
|
|
16
|
-
|
|
17
|
-
## 🛠️ Dependencies
|
|
18
|
-
|
|
19
|
-
- Python 3.10–3.14 (3.13 recommended, see below)
|
|
20
|
-
- PyTorch (with CUDA/ROCm support for GPU acceleration)
|
|
21
|
-
- marker-pdf==1.10.2
|
|
22
|
-
- transformers==4.57.6
|
|
23
|
-
- markdown==3.10.2
|
|
24
|
-
- latex2mathml==3.81.0
|
|
25
|
-
|
|
26
|
-
### ⚠️ Python version
|
|
27
|
-
|
|
28
|
-
Python 3.13 is recommended.
|
|
29
|
-
|
|
30
|
-
`marker-pdf` constrains `Pillow<11.0.0`, and Pillow only ships Python 3.14
|
|
31
|
-
wheels from 11.3.0 onward. On Python 3.10–3.13 every dependency installs as a
|
|
32
|
-
prebuilt wheel. On 3.14, pip has to build Pillow from source instead: this
|
|
33
|
-
works, but it is slower and requires a working C toolchain plus the image
|
|
34
|
-
library headers Pillow links against. On Debian/Ubuntu install them first:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
sudo apt install libjpeg-dev zlib1g-dev libtiff-dev libfreetype6-dev libwebp-dev
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Without these headers the install fails with
|
|
41
|
-
`RequiredDependencyException: The headers or library files could not be found for jpeg`.
|
|
42
|
-
|
|
43
|
-
## 💻 Installation
|
|
44
|
-
|
|
45
|
-
1. Create and activate a virtual environment.
|
|
46
|
-
|
|
47
|
-
On Linux/Mac:
|
|
48
|
-
```bash
|
|
49
|
-
python3.13 -m venv .venv
|
|
50
|
-
source .venv/bin/activate
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
On Windows:
|
|
54
|
-
```powershell
|
|
55
|
-
py -3.13 -m venv .venv
|
|
56
|
-
.venv\Scripts\activate
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
2. Install Python dependencies (this installs PyTorch as well):
|
|
60
|
-
```bash
|
|
61
|
-
pip install -r requirements.txt
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
3. GPU acceleration (optional):
|
|
65
|
-
|
|
66
|
-
PyTorch is installed as a dependency in step 2. On Apple Silicon that wheel
|
|
67
|
-
already supports MPS, so no further action is needed. For a specific CUDA or
|
|
68
|
-
ROCm build, reinstall PyTorch using the selector at
|
|
69
|
-
[pytorch.org/get-started/locally](https://pytorch.org/get-started/locally/).
|
|
70
|
-
For example, for AMD GPUs with ROCm:
|
|
71
|
-
```bash
|
|
72
|
-
pip uninstall torch torchvision torchaudio
|
|
73
|
-
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.2
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
4. Verify GPU support:
|
|
77
|
-
```python
|
|
78
|
-
import torch
|
|
79
|
-
print(torch.__version__) # PyTorch version
|
|
80
|
-
print(torch.cuda.is_available()) # Should return True for NVIDIA
|
|
81
|
-
print(torch.backends.mps.is_available()) # Should return True for Apple Silicon
|
|
82
|
-
print(torch.version.hip) # Should print ROCm version for AMD
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### 🐳 Docker
|
|
86
|
-
|
|
87
|
-
A CPU-only image can be built from the included `Dockerfile`:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
docker build -t pdf2epub .
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Run it with your PDFs mounted at `/data` and a model cache volume (marker-pdf
|
|
94
|
-
downloads its models on first run):
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
docker run -it --rm \
|
|
98
|
-
-v "$(pwd)":/data \
|
|
99
|
-
-v pdf2epub-models:/models \
|
|
100
|
-
pdf2epub input.pdf
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
`-it` is required for EPUB generation because metadata is prompted
|
|
104
|
-
interactively; with `--skip-epub` it can run non-interactively.
|
|
105
|
-
|
|
106
|
-
Tagged releases are also published to
|
|
107
|
-
`ghcr.io/overcuriousity/pdf2epub` by the Docker workflow.
|
|
108
|
-
|
|
109
|
-
## 🚀 Usage
|
|
110
|
-
|
|
111
|
-
### Basic Usage
|
|
112
|
-
|
|
113
|
-
Convert a single PDF file:
|
|
114
|
-
```bash
|
|
115
|
-
python main.py input.pdf
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Convert all PDFs in a directory:
|
|
119
|
-
```bash
|
|
120
|
-
python main.py input_directory/
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
EPUB generation prompts interactively for metadata (title, author, language,
|
|
124
|
-
and so on; press Enter to accept each default). It therefore needs a terminal —
|
|
125
|
-
run it non-interactively and it will fail with `EOFError`. Use `--skip-epub` to
|
|
126
|
-
produce only markdown without any prompts.
|
|
127
|
-
|
|
128
|
-
### Advanced Options
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
python main.py [input_path] [output_path] [options]
|
|
132
|
-
|
|
133
|
-
Options:
|
|
134
|
-
--max-pages INT Maximum number of pages to process
|
|
135
|
-
--start-page INT Page number to start from
|
|
136
|
-
--skip-epub Skip EPUB generation, only create markdown
|
|
137
|
-
--skip-md Skip markdown generation, use existing markdown files
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
If `input_path` is omitted, all PDFs in `./input/` are processed.
|
|
141
|
-
|
|
142
|
-
### Examples
|
|
143
|
-
|
|
144
|
-
Process a specific range of pages:
|
|
145
|
-
```bash
|
|
146
|
-
python main.py book.pdf --start-page 10 --max-pages 50
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Convert to markdown only:
|
|
150
|
-
```bash
|
|
151
|
-
python main.py thesis.pdf --skip-epub
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Output Structure
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
output_directory/
|
|
158
|
-
├── document_name/
|
|
159
|
-
│ ├── document_name.md
|
|
160
|
-
│ ├── document_name.epub
|
|
161
|
-
│ ├── document_name_metadata.json
|
|
162
|
-
│ └── images/
|
|
163
|
-
│ ├── image1.png
|
|
164
|
-
│ ├── image2.jpg
|
|
165
|
-
│ └── ...
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## 🤝 Contributing
|
|
169
|
-
|
|
170
|
-
Contributions are welcome! Here's how you can help:
|
|
171
|
-
|
|
172
|
-
1. Fork the repository
|
|
173
|
-
2. Create a new branch for your feature
|
|
174
|
-
3. Commit your changes
|
|
175
|
-
4. Push to your branch
|
|
176
|
-
5. Create a Pull Request
|
|
177
|
-
|
|
178
|
-
Please ensure your code follows the existing style and includes appropriate tests.
|
|
179
|
-
|
|
180
|
-
### Development Setup
|
|
181
|
-
|
|
182
|
-
1. Clone the repository:
|
|
183
|
-
```bash
|
|
184
|
-
git clone https://github.com/overcuriousity/pdf2epub.git
|
|
185
|
-
cd pdf2epub
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
2. Create a virtual environment:
|
|
189
|
-
```bash
|
|
190
|
-
python -m venv venv
|
|
191
|
-
source venv/bin/activate # Linux/Mac
|
|
192
|
-
venv\Scripts\activate # Windows
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
3. Install development dependencies:
|
|
196
|
-
```bash
|
|
197
|
-
pip install -r requirements.txt
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## 📄 License
|
|
201
|
-
|
|
202
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
203
|
-
|
|
204
|
-
## 🐛 Known Issues
|
|
205
|
-
|
|
206
|
-
- Some image embedding might need manual adjustment
|
|
207
|
-
- Some complex mathematical equations might not be perfectly converted
|
|
208
|
-
- Certain PDF layouts with multiple columns may require manual adjustment
|
|
209
|
-
- Font detection might be imperfect in some cases
|
|
210
|
-
|
|
211
|
-
## 🙏 Acknowledgments
|
|
212
|
-
|
|
213
|
-
This project builds upon several excellent open-source libraries:
|
|
214
|
-
- [marker-pdf](https://github.com/VikParuchuri/marker) for PDF processing
|
|
215
|
-
- [mark2epub](https://github.com/AlexPof/mark2epub) for markdown conversion
|
|
216
|
-
- [PyTorch](https://pytorch.org/) for GPU acceleration
|
|
217
|
-
- [Transformers](https://huggingface.co/transformers) for advanced text processing
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"repo": "overcuriousity/pdf2epub",
|
|
3
|
-
"ref": "v1.1.0",
|
|
4
|
-
"commit": "5eae669d1c897b4735f369fb2ff4086237db3a81",
|
|
5
|
-
"commitDate": "2026-07-16T18:17:11Z",
|
|
6
|
-
"url": "https://github.com/overcuriousity/pdf2epub/tree/5eae669d1c897b4735f369fb2ff4086237db3a81",
|
|
7
|
-
"syncedAt": "2026-09-07T09:36:39.940Z"
|
|
8
|
-
}
|