File size: 6,727 Bytes
0ac70f9
 
04b04af
 
0ac70f9
 
 
742f5dc
04b04af
 
 
 
 
742f5dc
5e3f9ac
 
0ac70f9
04b04af
0ac70f9
04b04af
 
0ac70f9
04b04af
 
 
2700cfa
 
 
04b04af
 
 
 
2700cfa
0ac70f9
04b04af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ac70f9
04b04af
2700cfa
 
 
 
04b04af
 
2700cfa
 
 
04b04af
 
d167a56
2700cfa
 
 
 
 
 
04b04af
 
2700cfa
 
 
 
 
 
d167a56
2700cfa
 
04b04af
 
 
2700cfa
04b04af
5e3f9ac
2700cfa
04b04af
2700cfa
04b04af
 
2700cfa
04b04af
 
 
 
 
 
 
 
 
 
 
 
 
2700cfa
 
04b04af
 
2700cfa
 
04b04af
2700cfa
 
 
 
04b04af
2700cfa
04b04af
2700cfa
04b04af
2700cfa
04b04af
 
 
 
2700cfa
 
04b04af
2700cfa
 
 
 
04b04af
2700cfa
 
 
 
 
 
 
0ac70f9
 
2700cfa
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
import os
import tempfile
import math
from typing import List, Tuple
import gradio as gr
from PyPDF2 import PdfReader, PdfWriter

# Load custom CSS
try:
    with open(os.path.join(os.path.dirname(__file__), "fancy.css")) as f:
        custom_css = f.read()
except FileNotFoundError:
    custom_css = ""

# Maximum upload size (1 GB)
MAX_SIZE_BYTES = 1 * 1024 * 1024 * 1024

def split_pdf_into_chunks(file, num_chunks: int) -> Tuple[List[str], str]:
    """
    Teilt eine PDF-Datei in X möglichst gleich große Teile auf.
    Gibt eine Liste der temporären Dateipfade und eine Fehlermeldung (falls vorhanden) zurück.
    """
    if file is None:
        return [], "Bitte lade zuerst eine PDF-Datei hoch."
        
    # Validate file size
    file_size = os.path.getsize(file.name)
    if file_size > MAX_SIZE_BYTES:
        return [], f"Datei überschreitet das 1 GB Limit ({file_size / (1024*1024*1024):.2f} GB)."

    if num_chunks < 1:
        return [], "Die Anzahl der Teile muss mindestens 1 betragen."

    try:
        reader = PdfReader(file.name)
        total_pages = len(reader.pages)
        
        if total_pages == 0:
            return [], "Die PDF-Datei enthält keine Seiten."
            
        # Wenn mehr Teile gefordert sind als Seiten da sind, begrenzen wir es auf die Seitenzahl
        chunks = min(num_chunks, total_pages)
        
        # Berechne die Basis-Seitenanzahl pro Teil und den Rest
        pages_per_chunk = total_pages // chunks
        remainder = total_pages % chunks
        
        output_files = []
        current_page = 0
        
        for i in range(chunks):
            writer = PdfWriter()
            
            # Verteile den Rest auf die ersten Chunks
            actual_pages = pages_per_chunk + (1 if i < remainder else 0)
            
            for _ in range(actual_pages):
                writer.add_page(reader.pages[current_page])
                current_page += 1
                
            # Temporäre Datei für diesen Teil erstellen
            # Verwende ein Muster im Namen, damit man die Reihenfolge erkennt
            out_path = tempfile.NamedTemporaryFile(delete=False, suffix=f"_teil_{i+1}.pdf").name
            with open(out_path, "wb") as f_out:
                writer.write(f_out)
            output_files.append(out_path)
            
        return output_files, ""
        
    except Exception as e:
        return [], f"Fehler beim Aufteilen der PDF: {str(e)}"

def merge_pdfs(files):
    # Merge multiple PDFs in upload order
    if not files:
        return None, "Bitte lade mindestens zwei PDF-Dateien zum Zusammenführen hoch."
    
    # Validate total size
    total_size = sum(os.path.getsize(f.name) for f in files)
    if total_size > MAX_SIZE_BYTES:
        return None, f"Gesamtgröße überschreitet das 1 GB Limit ({total_size / (1024*1024*1024):.2f} GB)."
        
    writer = PdfWriter()
    try:
        for f in files:
            reader = PdfReader(f.name)
            for page in reader.pages:
                writer.add_page(page)
    except Exception as e:
        return None, f"Fehler beim Lesen der PDFs: {e}"
        
    out_path = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf").name
    with open(out_path, "wb") as f_out:
        writer.write(f_out)
    return out_path, None

# Build Gradio interface
with gr.Blocks(css=custom_css) as demo:
    gr.HTML("<h1 id='header'>PDF Splitter & Merger</h1>")
    gr.Markdown(
        "**Schnelle & Lokale Lösung**: Verarbeite deine PDFs direkt im Speicher ohne dauerhafte Speicherung.\n"
        "Nutze den **Split**-Tab, um eine PDF in gleich große Teile zu zerlegen, oder den **Merge**-Tab, um mehrere PDFs zu vereinen.\n\n"
        "Maximale Dateigröße: **1 GB**."
    )
    
    with gr.Tabs():
        # Split Tab
        with gr.TabItem("PDF Aufteilen"):
            with gr.Row(elem_classes="input-row"):
                pdf_input = gr.File(label="PDF zum Aufteilen auswählen", file_types=['.pdf'])
                chunks_input = gr.Number(label="In wie viele Teile aufteilen?", value=4, precision=0)
            with gr.Row(elem_classes="button-row"):
                split_button = gr.Button("PDF aufteilen", variant="primary")
            
            # Wichtig: file_count="multiple" erlaubt die Anzeige von mehreren Download-Links
            output_split = gr.File(label="Herunterladbare PDF-Teile", file_count="multiple")
            error_split = gr.Textbox(label="Fehlermeldung", interactive=False, visible=False)
            
            def run_split(file, chunks):
                if file is None:
                    return None, "Bitte lade eine PDF-Datei hoch.", True
                if chunks is None or chunks < 1:
                    return None, "Bitte gib eine gültige Anzahl an Teilen ein (mindestens 1).", True
                    
                out_paths, error = split_pdf_into_chunks(file, int(chunks))
                if error:
                    return None, error, True
                return out_paths, "", False
                
            split_button.click(
                fn=run_split,
                inputs=[pdf_input, chunks_input],
                outputs=[output_split, error_split, error_split],
                api_name="split_pdf"
            )
            error_split.change(lambda msg: msg != "", inputs=error_split, outputs=error_split)
            
        # Merge Tab
        with gr.TabItem("PDF Zusammenführen"):
            with gr.Row(elem_classes="input-row"):
                merge_inputs = gr.Files(label="PDF-Dateien zum Zusammenführen auswählen", file_types=['.pdf'])
            with gr.Row(elem_classes="button-row"):
                merge_button = gr.Button("PDFs zusammenführen", variant="primary")
            output_merge = gr.File(label="Zusammengeführte PDF herunterladen")
            error_merge = gr.Textbox(label="Fehlermeldung", interactive=False, visible=False)
            
            def run_merge(files):
                if not files or len(files) < 2:
                    return None, "Bitte lade mindestens zwei PDF-Dateien hoch.", True
                out_path, error = merge_pdfs(files)
                if error:
                    return None, error, True
                return out_path, "", False
                
            merge_button.click(
                fn=run_merge,
                inputs=[merge_inputs],
                outputs=[output_merge, error_merge, error_merge],
                api_name="merge_pdfs"
            )
            error_merge.change(lambda msg: msg != "", inputs=error_merge, outputs=error_merge)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))