Skip to content

Commit be49913

Browse files
committed
first commit
0 parents  commit be49913

File tree

2 files changed

+275
-0
lines changed

2 files changed

+275
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/env

main.py

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import os
2+
import tkinter as tk
3+
from tkinter import filedialog, messagebox, ttk
4+
from pathlib import Path
5+
try:
6+
from PyPDF2 import PdfReader, PdfWriter
7+
except ImportError:
8+
print("PyPDF2 library is required. Installing...")
9+
import subprocess
10+
import sys
11+
subprocess.check_call([sys.executable, "-m", "pip", "install", "PyPDF2"])
12+
from PyPDF2 import PdfReader, PdfWriter
13+
14+
15+
def parse_page_ranges(page_input):
16+
"""
17+
Parse page ranges like '1-3,5,6-9,11' into a list of page numbers.
18+
Returns a sorted list of unique page numbers (1-indexed).
19+
"""
20+
pages = set()
21+
parts = page_input.replace(" ", "").split(",")
22+
23+
for part in parts:
24+
if not part: # Skip empty parts
25+
continue
26+
if "-" in part:
27+
# Handle range like "1-3"
28+
start, end = part.split("-", 1)
29+
try:
30+
start_page = int(start)
31+
end_page = int(end)
32+
if start_page > end_page:
33+
raise ValueError(f"Invalid range {part} (start > end)")
34+
pages.update(range(start_page, end_page + 1))
35+
except ValueError as e:
36+
raise ValueError(f"Invalid range format '{part}': {e}")
37+
else:
38+
# Handle single page number
39+
try:
40+
pages.add(int(part))
41+
except ValueError:
42+
raise ValueError(f"Invalid page number '{part}'")
43+
44+
return sorted(pages)
45+
46+
47+
def trim_pdf(input_path, page_numbers, output_path):
48+
"""
49+
Create a new PDF with only the specified pages.
50+
51+
Args:
52+
input_path: Path to the input PDF file
53+
page_numbers: List of page numbers to include (1-indexed)
54+
output_path: Path where the output PDF will be saved
55+
56+
Returns:
57+
tuple: (success: bool, message: str, valid_pages: list)
58+
"""
59+
try:
60+
reader = PdfReader(input_path)
61+
writer = PdfWriter()
62+
63+
total_pages = len(reader.pages)
64+
65+
# Validate and add pages
66+
valid_pages = []
67+
invalid_pages = []
68+
for page_num in page_numbers:
69+
if 1 <= page_num <= total_pages:
70+
# Convert to 0-indexed for PyPDF2
71+
writer.add_page(reader.pages[page_num - 1])
72+
valid_pages.append(page_num)
73+
else:
74+
invalid_pages.append(page_num)
75+
76+
if not valid_pages:
77+
return False, "No valid pages to include in the output PDF.", []
78+
79+
# Write the output PDF
80+
with open(output_path, "wb") as output_file:
81+
writer.write(output_file)
82+
83+
message = f"Successfully created PDF with {len(valid_pages)} pages"
84+
if invalid_pages:
85+
message += f"\n\nSkipped invalid pages: {invalid_pages} (PDF has {total_pages} pages)"
86+
87+
return True, message, valid_pages
88+
89+
except Exception as e:
90+
return False, f"Error processing PDF: {str(e)}", []
91+
92+
93+
class PDFPageSelectorApp:
94+
def __init__(self, root):
95+
self.root = root
96+
self.root.title("PDF Page Selector")
97+
self.root.geometry("600x400")
98+
self.root.resizable(True, True)
99+
100+
self.input_path = None
101+
self.total_pages = 0
102+
103+
self.create_widgets()
104+
105+
def create_widgets(self):
106+
# Main frame with padding
107+
main_frame = ttk.Frame(self.root, padding="20")
108+
main_frame.grid(row=0, column=0, sticky="nsew")
109+
110+
self.root.columnconfigure(0, weight=1)
111+
self.root.rowconfigure(0, weight=1)
112+
main_frame.columnconfigure(1, weight=1)
113+
114+
# Title
115+
title_label = ttk.Label(main_frame, text="PDF Page Selector",
116+
font=("Arial", 16, "bold"))
117+
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
118+
119+
# Input PDF section
120+
ttk.Label(main_frame, text="Input PDF:").grid(row=1, column=0, sticky=tk.W, pady=5)
121+
122+
self.input_label = ttk.Label(main_frame, text="No file selected",
123+
foreground="gray", relief="sunken", padding=5)
124+
self.input_label.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
125+
126+
ttk.Button(main_frame, text="Browse...",
127+
command=self.browse_input).grid(row=1, column=2, pady=5)
128+
129+
# Page info label
130+
self.page_info_label = ttk.Label(main_frame, text="", foreground="blue")
131+
self.page_info_label.grid(row=2, column=1, sticky=tk.W, padx=5)
132+
133+
# Page ranges section
134+
ttk.Label(main_frame, text="Page Ranges:").grid(row=3, column=0, sticky=tk.W, pady=5)
135+
136+
self.page_entry = ttk.Entry(main_frame, width=40)
137+
self.page_entry.grid(row=3, column=1, sticky="ew", padx=5, pady=5)
138+
139+
# Help text
140+
help_frame = ttk.Frame(main_frame)
141+
help_frame.grid(row=4, column=1, sticky="ew", padx=5, pady=5)
142+
143+
help_text = ttk.Label(help_frame,
144+
text="Examples: 1-3,5,6-9,11 or 1,3,5 or 10-20",
145+
foreground="gray", font=("Arial", 9))
146+
help_text.pack(anchor=tk.W)
147+
148+
# Output section
149+
ttk.Label(main_frame, text="Output PDF:").grid(row=5, column=0, sticky=tk.W, pady=5)
150+
151+
self.output_label = ttk.Label(main_frame, text="No output location selected",
152+
foreground="gray", relief="sunken", padding=5)
153+
self.output_label.grid(row=5, column=1, sticky="ew", padx=5, pady=5)
154+
155+
ttk.Button(main_frame, text="Browse...",
156+
command=self.browse_output).grid(row=5, column=2, pady=5)
157+
158+
# Process button
159+
self.process_button = ttk.Button(main_frame, text="Create Trimmed PDF",
160+
command=self.process_pdf, state=tk.DISABLED)
161+
self.process_button.grid(row=6, column=0, columnspan=3, pady=20)
162+
163+
# Status label
164+
self.status_label = ttk.Label(main_frame, text="", wraplength=550)
165+
self.status_label.grid(row=7, column=0, columnspan=3, pady=10)
166+
167+
def browse_input(self):
168+
filename = filedialog.askopenfilename(
169+
title="Select Input PDF",
170+
filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")]
171+
)
172+
173+
if filename:
174+
self.input_path = filename
175+
# Truncate long paths for display
176+
display_name = filename if len(filename) < 60 else "..." + filename[-57:]
177+
self.input_label.config(text=display_name, foreground="black")
178+
179+
# Get page count
180+
try:
181+
reader = PdfReader(filename)
182+
self.total_pages = len(reader.pages)
183+
self.page_info_label.config(text=f"(Total pages: {self.total_pages})")
184+
185+
# Suggest default output
186+
input_file = Path(filename)
187+
default_output = input_file.parent / f"{input_file.stem}_trimmed{input_file.suffix}"
188+
self.output_path = str(default_output)
189+
display_output = str(default_output) if len(str(default_output)) < 60 else "..." + str(default_output)[-57:]
190+
self.output_label.config(text=display_output, foreground="black")
191+
192+
self.update_process_button()
193+
except Exception as e:
194+
messagebox.showerror("Error", f"Could not read PDF: {str(e)}")
195+
self.input_path = None
196+
self.total_pages = 0
197+
self.input_label.config(text="No file selected", foreground="gray")
198+
self.page_info_label.config(text="")
199+
200+
def browse_output(self):
201+
if self.input_path:
202+
input_file = Path(self.input_path)
203+
initial_file = f"{input_file.stem}_trimmed{input_file.suffix}"
204+
initial_dir = input_file.parent
205+
else:
206+
initial_file = "output_trimmed.pdf"
207+
initial_dir = Path.home()
208+
209+
filename = filedialog.asksaveasfilename(
210+
title="Save Trimmed PDF As",
211+
initialfile=initial_file,
212+
initialdir=initial_dir,
213+
defaultextension=".pdf",
214+
filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")]
215+
)
216+
217+
if filename:
218+
self.output_path = filename
219+
display_name = filename if len(filename) < 60 else "..." + filename[-57:]
220+
self.output_label.config(text=display_name, foreground="black")
221+
self.update_process_button()
222+
223+
def update_process_button(self):
224+
if self.input_path and hasattr(self, 'output_path'):
225+
self.process_button.config(state=tk.NORMAL)
226+
else:
227+
self.process_button.config(state=tk.DISABLED)
228+
229+
def process_pdf(self):
230+
# Validate page input
231+
page_input = self.page_entry.get().strip()
232+
if not page_input:
233+
messagebox.showerror("Error", "Please enter page ranges.")
234+
return
235+
236+
try:
237+
page_numbers = parse_page_ranges(page_input)
238+
except ValueError as e:
239+
messagebox.showerror("Error", f"Invalid page format:\n{str(e)}")
240+
return
241+
242+
if not page_numbers:
243+
messagebox.showerror("Error", "No valid page numbers found.")
244+
return
245+
246+
# Check if output file exists
247+
if os.path.exists(self.output_path):
248+
if not messagebox.askyesno("Confirm Overwrite",
249+
f"File already exists:\n{self.output_path}\n\nOverwrite?"):
250+
return
251+
252+
# Process the PDF
253+
self.status_label.config(text="Processing...", foreground="blue")
254+
self.root.update()
255+
256+
success, message, valid_pages = trim_pdf(self.input_path, page_numbers, self.output_path)
257+
258+
if success:
259+
self.status_label.config(text=f"Success! Saved to:\n{self.output_path}",
260+
foreground="green")
261+
messagebox.showinfo("Success", message)
262+
else:
263+
self.status_label.config(text="Failed", foreground="red")
264+
messagebox.showerror("Error", message)
265+
266+
267+
def main():
268+
root = tk.Tk()
269+
app = PDFPageSelectorApp(root)
270+
root.mainloop()
271+
272+
273+
if __name__ == "__main__":
274+
main()

0 commit comments

Comments
 (0)