Repository navigation
Expand file tree
/
Copy pathcli.py
More file actions
225 lines (181 loc) · 6.44 KB
/
Copy pathcli.py
File metadata and controls
225 lines (181 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/usr/bin/env python3
"""
Command-line interface for PDF translator.
This CLI matches the behavior of the original translate.py script.
"""
import argparse
import logging
import sys
from pathlib import Path
from dotenv import load_dotenv
from main import translate_pdf, resolve_pdf_path
load_dotenv()
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
SCRIPT_DIR = Path(__file__).resolve().parent
def parse_args():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(
description="PDF Translator with MinerU - High-accuracy layout-preserving translation",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Basic usage - auto-finds in pdfs/ folder (English to Spanish, uses MinerU + OpenAI by default)
python translate_cli.py document.pdf
python translate_cli.py document # .pdf extension optional
# Fast PyMuPDF extraction (simple, fast, good for text-based PDFs)
python translate_cli.py document.pdf --extractor pymupdf
# Free local GPU translation with MarianMT (no API costs!)
python translate_cli.py document.pdf --translator marianmt
# Local LLM translation with Ollama
python translate_cli.py document.pdf --translator ollama
# Fast extraction + free translation (best for simple PDFs)
python translate_cli.py document.pdf --extractor pymupdf --translator marianmt
# MinerU extraction with different backend (accurate, handles formulas/tables)
python translate_cli.py document.pdf --extractor mineru --backend vlm-auto-engine
# Force re-extraction (ignores cached middle.json, only for MinerU)
python translate_cli.py document.pdf --f
# Different language pair
python translate_cli.py document.pdf --source-lang en --target-lang fr
Environment Variables (for OpenAI translator):
OPENAI_API_KEY - OpenAI API key (required for --translator openai)
OPENAI_API_BASE - Custom API base URL (optional)
OPENAI_MODEL - Model name (default: gpt-4o-mini)
Environment Variables (for Ollama translator):
OLLAMA_API_KEY - Ollama API key (required for --translator ollama)
OLLAMA_BASE_URL - Custom API base URL (optional)
OLLAMA_MODEL - Model name (default: llama3.2)
Notes:
- PDF search order: direct path -> pdfs/filename -> with .pdf extension
- Output: <input>_translated_<lang>.pdf (same folder as input)
- Cached extraction: ./output/<stem>/hybrid_auto/<stem>_middle.json
- Translation cache: ./.translation_cache/
""",
)
parser.add_argument(
"pdf",
help="PDF filename (auto-searches in pdfs/) or full path"
)
parser.add_argument(
"--source-lang",
default="en",
help="Source language (default: en)",
)
parser.add_argument(
"--target-lang",
default="es",
help="Target language (default: es)",
)
parser.add_argument(
"--extractor",
default="mineru",
choices=["pymupdf", "mineru", "docling"],
help="Extraction method: pymupdf (fast, simple), mineru (accurate, complex) or docling (accurate, complex) (default: mineru)",
)
parser.add_argument(
"--translator",
default="openai",
choices=["openai", "marianmt", "ollama"],
help="Translation backend: openai (API, high quality) or marianmt (local GPU, free) (default: openai)",
)
parser.add_argument(
"--f",
action="store_true",
help="Force re-extraction (ignore cached middle.json, only for mineru)",
)
parser.add_argument(
"--backend",
default="hybrid-auto-engine",
choices=[
"pipeline",
"hybrid-auto-engine",
"hybrid-http-client",
"vlm-auto-engine",
"vlm-http-client",
],
help="MinerU backend (default: hybrid-auto-engine, only for mineru extractor)",
)
parser.add_argument(
"--api-key",
help="OpenAI API key (overrides env var). For Ollama, set OLLAMA_API_KEY or leave it empty",
)
parser.add_argument(
"--base-url",
help="OpenAI API base URL (overrides env var). For Ollama, set OLLAMA_BASE_URL",
)
parser.add_argument(
"--model",
help="OpenAI model (default: gpt-4o-mini). For Ollama, set OLLAMA_MODEL",
)
parser.add_argument(
"--no-cache",
action="store_true",
help="Disable translation caching",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Verbose logging (DEBUG level)",
)
parser.add_argument(
"-o",
"--output",
help="Output PDF path (default: <input>_translated_<lang>.pdf)",
)
parser.add_argument(
"--renderer",
default="overlay",
choices=["overlay", "adaptive_overlay"],
help="Rendering method (default: overlay)",
)
parser.add_argument(
"--font-path",
help="Path to TTF/OTF/TTC font used by adaptive renderer",
)
return parser.parse_args()
def main():
"""Main CLI entry point."""
args = parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
try:
# Resolve input PDF path
input_pdf = resolve_pdf_path(args.pdf, SCRIPT_DIR)
logger.info(f"Found PDF: {input_pdf}")
# Determine output path
output_pdf = Path(args.output) if args.output else None
# Run translation
stats = translate_pdf(
pdf_path=input_pdf,
output_pdf=output_pdf,
source_lang=args.source_lang,
target_lang=args.target_lang,
extractor=args.extractor,
translator=args.translator,
backend=args.backend,
api_key=args.api_key,
base_url=args.base_url,
model=args.model,
use_cache=not args.no_cache,
force_extract=args.f,
renderer=args.renderer,
)
print(f"\n✓ Success! Translated PDF: {stats['output_pdf']}")
print(f"⏱️ Total time: {stats['elapsed_time']:.2f} seconds")
except KeyboardInterrupt:
print("\n\nCancelled by user")
sys.exit(1)
except FileNotFoundError as e:
logger.error(str(e))
sys.exit(1)
except Exception as e:
logger.error(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()