-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpdf_toolkit_v2.py
More file actions
712 lines (582 loc) · 31.7 KB
/
Copy pathpdf_toolkit_v2.py
File metadata and controls
712 lines (582 loc) · 31.7 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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
#!/usr/bin/env python3
"""
PDF Toolkit - Combines PDF restriction removal and image extraction in an interactive tool.
"""
import os
import sys
import argparse
from pathlib import Path # Keep pathlib just in case, though os.path is mostly used
import fitz # PyMuPDF
from tqdm import tqdm # For progress bars
import pikepdf
# Removed import shutil - not used
class PDFToolkit:
def __init__(self):
"""Initializes the PDF Toolkit state."""
self.current_pdf = None
self.output_dir = None
self.is_unrestricted = False # Tracks if the *current* loaded PDF is believed to be unrestricted
def interactive_mode(self):
"""Run the tool in interactive command-line mode."""
print("\n" + "="*60)
print("PDF TOOLKIT - Restriction Removal & Image Extraction".center(60))
print("="*60 + "\n")
while True:
self._show_status()
print("\nAVAILABLE COMMANDS:")
print("1. open - Open a PDF file")
print("2. unrestrict - Remove PDF restrictions (requires password)")
print("3. extract - Extract images from current PDF")
print("4. info - Show detailed info about the current PDF")
print("5. setup - Configure output directory")
print("6. help - Show this help message")
print("7. quit - Exit the program")
choice = input("\nEnter command: ").strip().lower()
if choice in ('q', 'quit', 'exit', '7'): # Added '7'
print("Exiting PDF Toolkit. Goodbye!") # Added exit message
break
elif choice in ('1', 'open'):
self._cmd_open_pdf()
elif choice in ('2', 'unrestrict'):
self._cmd_unrestrict_pdf()
elif choice in ('3', 'extract'):
self._cmd_extract_images()
elif choice in ('4', 'info'):
self._cmd_show_pdf_info()
elif choice in ('5', 'setup'):
self._cmd_setup_output()
elif choice in ('help', '6', '?'):
# Show status and menu again
continue
else:
print("Unknown command. Please try again.")
def _show_status(self):
"""Show current status of the toolkit."""
print("\n" + "-"*60)
print("CURRENT STATUS:")
if self.current_pdf:
print(f" Current PDF: {os.path.basename(self.current_pdf)}")
# More detailed status based on initial check or unrestriction success
status = "Unknown"
if self.is_unrestricted is True:
status = "Unrestricted (based on last operation)"
elif self.is_unrestricted is False: # This implies encrypted upon opening
status = "Encrypted/Restricted (password needed)"
# If self.is_unrestricted is None (initial state before opening)
print(f" Restriction Status: {status}")
else:
print(" No PDF file currently loaded")
if self.output_dir:
print(f" Output Directory: {self.output_dir}")
else:
print(" Output Directory: Not set (will use PDF directory)")
print("-"*60)
def _cmd_open_pdf(self):
"""Interactive command to open a PDF file."""
pdf_path = input("Enter path to PDF file: ").strip()
if not pdf_path:
print("Operation cancelled.")
return
pdf_path = os.path.expanduser(pdf_path)
if not os.path.exists(pdf_path):
print(f"Error: File not found: {pdf_path}")
return
try:
# Just check if we can open it and get basic info
doc = fitz.open(pdf_path)
page_count = doc.page_count
is_encrypted = doc.is_encrypted
doc.close()
self.current_pdf = pdf_path
self.is_unrestricted = not is_encrypted # Set status based on encryption upon opening
print(f"Successfully opened '{os.path.basename(pdf_path)}'")
print(f" Pages: {page_count}")
print(f" Encrypted: {is_encrypted}")
if is_encrypted:
print("This PDF appears to be encrypted. Use 'unrestrict' command to remove restrictions.")
except Exception as e:
# Removed the duplicate except block
print(f"Error opening PDF: {e}")
# Optionally reset state if opening fails
self.current_pdf = None
self.is_unrestricted = False # Or None
def _cmd_unrestrict_pdf(self):
"""Interactive command to remove PDF restrictions."""
if not self.current_pdf:
print("Error: No PDF currently loaded. Use 'open' command first.")
return
# Clarified prompt
password = input("Enter the password for the PDF (user or owner, or press Enter to cancel): ")
if not password:
print("Operation cancelled.")
return
output_filename = input("Enter output filename (or press Enter for default): ").strip() # Strip whitespace
if not output_filename:
# Create default name
base_path = os.path.splitext(self.current_pdf)[0]
output_filename = f"{base_path}_unrestricted.pdf"
else:
output_filename = os.path.expanduser(output_filename)
# Optional: Check if output file exists and ask to overwrite?
if os.path.exists(output_filename):
overwrite = input(f"Output file '{output_filename}' already exists. Overwrite? (y/n): ").lower()
if not overwrite.startswith('y'):
print("Operation cancelled.")
return
try:
print(f"Attempting to remove restrictions from: {os.path.basename(self.current_pdf)}")
# Open with password
pdf = pikepdf.open(self.current_pdf, password=password)
# Save without restrictions
pdf.save(output_filename)
pdf.close()
print(f"Success! Saved unrestricted PDF to: {output_filename}")
# Update current PDF to the unrestricted version
replace = input("Use this unrestricted version as current file? (y/n): ").lower()
if replace.startswith('y'):
self.current_pdf = output_filename
self.is_unrestricted = True # Explicitly set to True after success
print("Now using the unrestricted version.")
else:
# If not using the new file, status might be ambiguous, reset or keep old
# Keeping old status seems reasonable if the user didn't switch
pass # self.is_unrestricted remains what it was for the *original* file
except pikepdf.PasswordError:
print("Error: Incorrect password provided.")
except Exception as e:
print(f"Error removing restrictions: {e}")
# Reset status if unrestriction failed unexpectedly?
# self.is_unrestricted = False # Or None, depending on desired behavior
def _cmd_extract_images(self):
"""Interactive command to extract images."""
if not self.current_pdf:
print("Error: No PDF currently loaded. Use 'open' command first.")
return
# Determine default output directory
if self.output_dir:
default_output_dir = self.output_dir
else:
pdf_dir = os.path.dirname(self.current_pdf)
pdf_name = os.path.splitext(os.path.basename(self.current_pdf))[0]
default_output_dir = os.path.join(pdf_dir, f"{pdf_name}_images")
# Ask for customization
output_dir = input(f"Output directory [default: {default_output_dir}]: ").strip() or default_output_dir
output_dir = os.path.expanduser(output_dir)
# Page range input and parsing
page_range_str = input("Extract from specific pages? (e.g., '1-5,8,10-12' or press Enter for all): ").strip()
pages_to_extract = None # Default to None to signal 'all pages'
if page_range_str:
try:
parsed_pages = []
doc = fitz.open(self.current_pdf) # Open temporarily to get page count for validation
max_page = doc.page_count
doc.close()
for part in page_range_str.split(','):
part = part.strip()
if not part: continue # Skip empty parts
if '-' in part:
start_str, end_str = part.split('-', 1) # Split only once
try:
start = int(start_str.strip())
end = int(end_str.strip())
# Validate range values
if start < 1 or end < start or end > max_page:
print(f"Warning: Invalid page range '{part}'. Pages must be between 1 and {max_page}. Skipping this part.")
continue
# Convert to 0-based and extend
parsed_pages.extend(range(start-1, end))
except ValueError:
print(f"Warning: Invalid number format in range '{part}'. Skipping this part.")
continue
else:
try:
page_num = int(part.strip())
# Validate page number
if page_num < 1 or page_num > max_page:
print(f"Warning: Invalid page number '{part}'. Pages must be between 1 and {max_page}. Skipping this part.")
continue
# Convert to 0-based
parsed_pages.append(page_num-1)
except ValueError:
print(f"Warning: Invalid page number format '{part}'. Skipping this part.")
continue
# Remove duplicates and sort the list of pages
pages_to_extract = sorted(list(set(parsed_pages)))
if not pages_to_extract and page_range_str:
print("No valid pages found in the range input. Extracting all pages instead.")
pages_to_extract = None # Explicitly set back to None
elif pages_to_extract:
print(f"Will extract from {len(pages_to_extract)} specified pages.")
except Exception as e: # Catch errors during range parsing/doc open
print(f"Error parsing page range or reading PDF structure: {e}")
print("Extracting all pages instead.")
pages_to_extract = None # Explicitly set to None on any parsing error
# Min image size input and parsing
min_size_str = input("Minimum image size in pixels (e.g., '100x100' or press Enter for no filter): ").strip().lower()
min_width, min_height = 0, 0
if min_size_str:
try:
parts = min_size_str.split('x')
if len(parts) == 2:
min_width = int(parts[0])
min_height = int(parts[1])
if min_width < 0 or min_height < 0:
raise ValueError("Negative dimensions not allowed")
else:
raise ValueError("Invalid format")
except (ValueError, IndexError) as e:
print(f"Invalid size format '{min_size_str}'. Expected WxH (e.g., 100x100). Using no size filter.")
min_width, min_height = 0, 0
else:
print(f"Will apply minimum size filter: {min_width}x{min_height}")
# Create output directory
try:
os.makedirs(output_dir, exist_ok=True)
print(f"Ensured output directory exists: {output_dir}")
except Exception as e:
print(f"Error creating output directory {output_dir}: {e}")
return # Cannot proceed without directory
# Extract images
# Pass None if pages_to_extract is empty or wasn't successfully parsed
self._extract_images(self.current_pdf, output_dir, pages=pages_to_extract, min_width=min_width, min_height=min_height)
def _extract_images(self, pdf_path, output_dir, pages=None, min_width=0, min_height=0):
"""
Extract images from PDF with filtering options.
Args:
pdf_path (str): Path to the PDF file.
output_dir (str): Directory to save extracted images.
pages (list or None): List of page numbers (0-based) to extract from,
or None for all pages.
min_width (int): Minimum image width in pixels. Images smaller than this
width will be skipped. Set to 0 to disable.
min_height (int): Minimum image height in pixels. Images smaller than this
height will be skipped. Set to 0 to disable.
"""
try:
doc = fitz.open(pdf_path)
if pages is None: # Use 'is None' for clarity with default argument
pages_to_process = range(len(doc))
print("Extracting images from all pages...")
else:
# Ensure pages are valid indices and are unique (already done in caller, but a safety check)
pages_to_process = sorted(list(set([p for p in pages if 0 <= p < len(doc)])))
if not pages_to_process:
print("No valid pages specified after filtering. No images will be extracted.")
doc.close()
return
print(f"Extracting images from {len(pages_to_process)} specified pages...")
image_xrefs = set() # Track processed images by xref to avoid duplicates
extracted_count = 0
skipped_size_count = 0
skipped_duplicate_count = 0
# Use tqdm for a progress bar
# Wrap the actual iteration over pages_to_process
for page_idx in tqdm(pages_to_process, desc="Processing pages", unit="page"):
try: # Inner try/except for page-level errors
page = doc.load_page(page_idx)
image_list = page.get_images(full=True) # full=True gets more info including xref
for img_info in image_list:
xref = img_info[0]
# Skip if already processed (unique image)
if xref in image_xrefs:
skipped_duplicate_count += 1
continue
try: # Innermost try/except for individual image extraction errors
img_dict = doc.extract_image(xref)
if not img_dict:
# print(f"\nWarning: Could not extract image with xref {xref} on page {page_idx+1}.") # Too verbose?
continue
# Apply size filter if requested
img_width = img_dict.get("width", 0) # Use .get for safety
img_height = img_dict.get("height", 0)
if (min_width > 0 and img_width < min_width) or \
(min_height > 0 and img_height < min_height):
skipped_size_count += 1
continue
img_bytes = img_dict.get("image")
img_ext = img_dict.get("ext")
if not img_bytes or not img_ext:
# print(f"\nWarning: Incomplete image data for xref {xref} on page {page_idx+1}.")
continue
# Generate a more informative filename
# Use page number where the image reference was found
img_filename = f"page{page_idx+1}_xref{xref}.{img_ext}"
output_path = os.path.join(output_dir, img_filename)
# Check if file already exists (optional, useful for multiple runs)
# if os.path.exists(output_path):
# print(f"\nWarning: Output file exists, overwriting: {output_path}")
with open(output_path, "wb") as img_file:
img_file.write(img_bytes)
image_xrefs.add(xref) # Add xref to set *after* successful processing/saving
extracted_count += 1
except Exception as img_e:
# Print errors related to individual images without breaking the loop
# This print inside tqdm loop might still mess up the bar slightly
tqdm.write(f"\nError extracting image xref {xref} on page {page_idx+1}: {img_e}")
continue # Continue to the next image on the page
except Exception as page_e:
# Print errors related to processing a whole page
tqdm.write(f"\nError processing page {page_idx+1}: {page_e}")
continue # Continue to the next page
doc.close() # Ensure doc is closed
print(f"\nExtraction complete.")
print(f"Total images processed (including duplicates): {len(image_xrefs) + skipped_duplicate_count}")
print(f"Unique images found: {len(image_xrefs)}")
print(f"Unique images extracted: {extracted_count}")
if skipped_size_count > 0:
print(f"Images skipped due to size filter ({min_width}x{min_height}): {skipped_size_count}")
if skipped_duplicate_count > 0:
print(f"Image references skipped (duplicate unique images): {skipped_duplicate_count}")
print(f"Images saved to: {output_dir}")
except Exception as e:
# Catch errors opening the document or major process failures
print(f"\nError during extraction process: {e}")
# Ensure doc is closed even if an error occurs after opening
if 'doc' in locals() and doc and not doc.is_closed:
doc.close()
def _cmd_show_pdf_info(self):
"""Display detailed information about the current PDF."""
if not self.current_pdf:
print("Error: No PDF currently loaded. Use 'open' command first.")
return
try:
doc = fitz.open(self.current_pdf)
print("\nPDF INFORMATION:")
print(f" Filename: {os.path.basename(self.current_pdf)}")
print(f" Pages: {doc.page_count}")
print(f" PDF Version: {doc.pdf_version}")
print(f" Encrypted: {doc.is_encrypted}")
# Get metadata
metadata = doc.metadata
if metadata:
print("\nMETADATA:")
# Filter out None or empty values for cleaner output
for key, value in metadata.items():
if value:
print(f" {key}: {value}")
else:
print("\nMETADATA: None found.")
# Count images
total_img_refs = 0
unique_images = set() # Store xrefs
print("\nIMAGE ANALYSIS:")
try:
# Use tqdm for image counting as it can be slow on large PDFs
for page_num in tqdm(range(len(doc)), desc="Analyzing images"):
page = doc.load_page(page_num)
img_list = page.get_images(full=True)
total_img_refs += len(img_list)
for img in img_list:
unique_images.add(img[0]) # Add xref to set
except Exception as img_analyze_e:
tqdm.write(f"\nWarning during image analysis: {img_analyze_e}")
print(f" Total image references found: {total_img_refs}")
print(f" Unique images found (by internal reference): {len(unique_images)}")
# Note: fitz.extract_image also gives width/height, could analyze sizes here too,
# but might make info command slow. Keeping it simple is fine.
doc.close() # Ensure doc is closed
except Exception as e:
# Removed the duplicate except block
print(f"Error getting PDF info: {e}")
if 'doc' in locals() and doc and not doc.is_closed:
doc.close()
def _cmd_setup_output(self):
"""Configure the output directory."""
current = self.output_dir or "Not set (will use PDF directory)"
new_dir = input(f"Current output directory: {current}\nEnter new directory path (or press Enter to cancel): ").strip()
if not new_dir:
print("Output directory unchanged.")
return
new_dir = os.path.expanduser(new_dir)
# Check if directory exists, offer to create
if not os.path.exists(new_dir):
# Use pathlib for cleaner path representation in prompt? Or just keep os.path
create = input(f"Directory '{new_dir}' doesn't exist. Create it? (y/n): ").lower()
if create.startswith('y'):
try:
# Use exist_ok=True here too for consistency
os.makedirs(new_dir, exist_ok=True)
print(f"Created directory: {new_dir}")
except Exception as e:
print(f"Error creating directory: {e}")
return
else:
print("Operation cancelled. Output directory not set.")
return # Don't set if not created
# Check if the path is actually a directory
if not os.path.isdir(new_dir):
print(f"Error: Path exists but is not a directory: {new_dir}")
return
self.output_dir = new_dir
print(f"Output directory set to: {new_dir}")
def main():
"""Main function to parse arguments and run the toolkit."""
# Parse command-line arguments for non-interactive mode options
parser = argparse.ArgumentParser(
description="PDF Toolkit - Unrestrict and extract images from PDFs",
formatter_class=argparse.RawTextHelpFormatter # Allows for better formatting in help text
)
parser.add_argument("-i", "--interactive", action="store_true",
help="Run in interactive mode (default if no other command is given)")
parser.add_argument("-p", "--pdf", metavar="PATH",
help="Path to PDF file for non-interactive mode.")
parser.add_argument("--password", metavar="PASSWORD",
help="PDF password for unrestriction (--unrestrict) or decryption.")
parser.add_argument("-u", "--unrestrict", action="store_true",
help="Remove PDF restrictions (requires --pdf and --password). "
"Saves a new unrestricted PDF.")
parser.add_argument("--unrestrict-output", metavar="PATH",
help="Output path for the unrestricted PDF when using --unrestrict.")
parser.add_argument("-e", "--extract", action="store_true",
help="Extract images from PDF (requires --pdf).")
parser.add_argument("-o", "--output", metavar="DIRECTORY",
help="Output directory for extracted images when using --extract. "
"Defaults to a subdirectory near the PDF.")
parser.add_argument("--pages", metavar="RANGE",
help="Page range for image extraction (e.g., '1-5,8,10-12'). "
"Applies to --extract. 1-based indexing.")
parser.add_argument("--min-size", metavar="WxH",
help="Minimum image size filter (e.g., '100x100'). "
"Applies to --extract. Images smaller than W or H are skipped.")
args = parser.parse_args()
# Create the toolkit instance
toolkit = PDFToolkit()
# Determine if interactive mode should run
# Interactive mode runs if no arguments are given, or if -i is explicitly set.
# If other operational arguments (-u, -e) are given, we assume non-interactive unless -i is also present.
# Let's simplify: If *any* operational argument is present (-u, -e), run non-interactive.
# If only -p, -o, --password, etc. are present without -u or -e, *and* no -i, it's an error or interactive?
# The original logic: `len(sys.argv) == 1 or args.interactive` runs interactive. This means
# `python script.py -p input.pdf` runs interactive, which is likely not desired.
# Let's adjust: Interactive unless -u or -e is specified.
is_interactive = args.interactive or not (args.unrestrict or args.extract or args.pdf)
if is_interactive:
# Check if any other command args were given with -i (could be confusing)
# For simplicity, if -i is present, ignore other command args (-u, -e)
if (args.unrestrict or args.extract) and args.interactive:
print("Warning: Command arguments (--unrestrict, --extract) are ignored when --interactive is used.")
toolkit.interactive_mode()
return
# Non-interactive mode with command-line arguments
print("Running in non-interactive mode...")
if not args.pdf:
print("Error: In non-interactive mode, a PDF file must be specified with --pdf.")
sys.exit(1) # Use exit codes
pdf_path = os.path.expanduser(args.pdf)
if not os.path.exists(pdf_path):
print(f"Error: PDF file not found: {pdf_path}")
sys.exit(1)
toolkit.current_pdf = pdf_path # Set the PDF for potential subsequent operations
# Handle unrestriction if requested
if args.unrestrict:
if not args.password:
print("Error: Password required for unrestriction (--password).")
sys.exit(1)
try:
# Use specified output filename or create default
if args.unrestrict_output:
output_filename = os.path.expanduser(args.unrestrict_output)
else:
base_path = os.path.splitext(pdf_path)[0]
output_filename = f"{base_path}_unrestricted.pdf"
print(f"Attempting to remove restrictions from: {os.path.basename(pdf_path)}")
# Open with password
# Note: If the file is already unrestricted but has an open password,
# pikepdf.open(password=...) might still work if the password is correct.
# If it's *not* encrypted but has restrictions set without a password,
# pikepdf might not need a password but saving it removes restrictions.
pdf = pikepdf.open(pdf_path, password=args.password) # password=None is handled by open if not needed? Or need to pass only if provided? Passing None is fine.
# Saving removes restrictions if the password allowed it
pdf.save(output_filename)
pdf.close()
print(f"Successfully saved unrestricted PDF to: {output_filename}")
# Use the unrestricted version for subsequent non-interactive operations (like extraction)
toolkit.current_pdf = output_filename
toolkit.is_unrestricted = True # Assume success means unrestricted
except pikepdf.PasswordError:
print("Error: Incorrect password provided for unrestriction.")
sys.exit(1)
except FileExistsError: # Add specific error for file existing if not using overwrite logic
print(f"Error: Output file already exists: {output_filename}. Use --unrestrict-output to specify a different name or remove the existing file.")
sys.exit(1)
except Exception as e:
print(f"Error removing restrictions: {e}")
sys.exit(1)
# Handle extraction if requested
if args.extract:
if not toolkit.current_pdf:
# Should not happen if --pdf was used, but safety check
print("Internal Error: PDF not loaded for extraction.")
sys.exit(1)
# Determine output directory for extraction
if args.output:
output_dir = os.path.expanduser(args.output)
else:
# Use the PDF's directory (could be the original or the new unrestricted one)
pdf_dir = os.path.dirname(toolkit.current_pdf)
pdf_name = os.path.splitext(os.path.basename(toolkit.current_pdf))[0]
output_dir = os.path.join(pdf_dir, f"{pdf_name}_images")
try:
os.makedirs(output_dir, exist_ok=True)
except Exception as e:
print(f"Error creating output directory {output_dir} for extraction: {e}")
sys.exit(1)
# Parse page range from arguments (similar logic to interactive)
pages_to_extract = None
if args.pages:
try:
parsed_pages = []
doc_temp = fitz.open(toolkit.current_pdf) # Open temporarily for page count
max_page = doc_temp.page_count
doc_temp.close()
for part in args.pages.split(','):
part = part.strip()
if not part: continue
if '-' in part:
start_str, end_str = part.split('-', 1)
start = int(start_str.strip())
end = int(end_str.strip())
if 1 <= start <= end <= max_page:
parsed_pages.extend(range(start-1, end))
else:
print(f"Warning: Invalid page range '{part}' in --pages. Skipping.")
else:
page_num = int(part.strip())
if 1 <= page_num <= max_page:
parsed_pages.append(page_num-1)
else:
print(f"Warning: Invalid page number '{part}' in --pages. Skipping.")
pages_to_extract = sorted(list(set(parsed_pages)))
if not pages_to_extract:
print("No valid pages specified in --pages. Extracting all pages instead.")
pages_to_extract = None # Fallback to None
except Exception as e:
print(f"Error parsing --pages argument '{args.pages}': {e}")
print("Extracting all pages instead.")
pages_to_extract = None # Fallback to None
# Parse min-size from arguments (similar logic to interactive)
min_width, min_height = 0, 0
if args.min_size:
try:
parts = args.min_size.lower().split('x')
if len(parts) == 2:
min_width = int(parts[0])
min_height = int(parts[1])
if min_width < 0 or min_height < 0: raise ValueError # Validate non-negative
else:
raise ValueError # Invalid format
except (ValueError, IndexError):
print(f"Error: Invalid --min-size format '{args.min_size}'. Expected WxH (e.g., 100x100). No size filter applied.")
min_width, min_height = 0, 0
else:
print(f"Applying minimum size filter: {min_width}x{min_height}")
# Perform extraction
# Pass None if pages_to_extract was empty or parsing failed
toolkit._extract_images(toolkit.current_pdf, output_dir, pages=pages_to_extract, min_width=min_width, min_height=min_height)
# If neither -u nor -e was specified in non-interactive mode
if not args.unrestrict and not args.extract and args.pdf and not args.interactive:
print("Error: No action specified for non-interactive mode (--unrestrict or --extract required).")
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()