Last active
August 14, 2026 08:25
-
-
Save pswalia2u/3ae86e24a9ba59bdced011eaa26e2f2b to your computer and use it in GitHub Desktop.
instant pdf merger app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| echo "[*] Setting up Python virtual environment..." | |
| python3 -m venv venv | |
| source venv/bin/activate | |
| echo "[*] Installing dependencies..." | |
| pip install --quiet fastapi uvicorn pypdf python-multipart | |
| echo "[*] Generating main.py..." | |
| cat << 'EOF' > main.py | |
| from fastapi import FastAPI, File, UploadFile, HTTPException | |
| from fastapi.responses import Response, HTMLResponse | |
| from pypdf import PdfWriter, PdfReader | |
| import io | |
| app = FastAPI(title="PDF Merge API with GUI") | |
| @app.get("/") | |
| async def serve_gui(): | |
| html_content = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>PDF Merger Tool</title> | |
| <style> | |
| body { font-family: system-ui, sans-serif; background: #f4f4f9; padding: 2rem; } | |
| .container { max-width: 600px; margin: 0 auto; background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } | |
| h2 { margin-top: 0; } | |
| .file-input { margin-bottom: 1rem; } | |
| button { background: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 1rem; } | |
| button:hover { background: #0056b3; } | |
| #status { margin-top: 1rem; font-weight: bold; color: #333; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h2>Merge PDF Files</h2> | |
| <form id="uploadForm"> | |
| <input type="file" id="filePicker" class="file-input" name="files" multiple accept="application/pdf" required> | |
| <br> | |
| <button type="submit">Merge and Download</button> | |
| </form> | |
| <div id="status"></div> | |
| </div> | |
| <script> | |
| document.getElementById('uploadForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const statusDiv = document.getElementById('status'); | |
| const filePicker = document.getElementById('filePicker'); | |
| if (filePicker.files.length < 2) { | |
| statusDiv.style.color = "red"; | |
| statusDiv.innerText = "Error: Please select at least two PDF files."; | |
| return; | |
| } | |
| statusDiv.style.color = "blue"; | |
| statusDiv.innerText = "Processing files..."; | |
| const formData = new FormData(); | |
| for (const file of filePicker.files) { | |
| formData.append('files', file); | |
| } | |
| try { | |
| const response = await fetch('/merge', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (response.ok) { | |
| const blob = await response.blob(); | |
| const downloadUrl = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = downloadUrl; | |
| a.download = 'merged.pdf'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| window.URL.revokeObjectURL(downloadUrl); | |
| statusDiv.style.color = "green"; | |
| statusDiv.innerText = "Merge successful! Download starting..."; | |
| } else { | |
| const errText = await response.text(); | |
| statusDiv.style.color = "red"; | |
| statusDiv.innerText = "Server Error: " + errText; | |
| } | |
| } catch (error) { | |
| statusDiv.style.color = "red"; | |
| statusDiv.innerText = "Network Error: Could not connect to the API."; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| return HTMLResponse(content=html_content) | |
| @app.post("/merge") | |
| async def merge_pdfs(files: list[UploadFile] = File(...)): | |
| if len(files) < 2: | |
| raise HTTPException(status_code=400, detail="At least two PDF files are required to merge.") | |
| writer = PdfWriter() | |
| try: | |
| for file in files: | |
| if not file.filename.lower().endswith('.pdf'): | |
| raise HTTPException(status_code=400, detail=f"File {file.filename} is not a PDF.") | |
| content = await file.read() | |
| reader = PdfReader(io.BytesIO(content)) | |
| for page in reader.pages: | |
| writer.add_page(page) | |
| output_buffer = io.BytesIO() | |
| writer.write(output_buffer) | |
| output_buffer.seek(0) | |
| return Response( | |
| content=output_buffer.getvalue(), | |
| media_type="application/pdf", | |
| headers={"Content-Disposition": "attachment; filename=merged.pdf"} | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Error processing PDFs: {str(e)}") | |
| EOF | |
| echo "[*] Scheduling browser to launch in 2 seconds..." | |
| (sleep 2 && python3 -m webbrowser "http://127.0.0.1:8000") & | |
| echo "[*] Starting the API and GUI on http://127.0.0.1:8000" | |
| uvicorn main:app --host 127.0.0.1 --port 8000 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To run:

Curl https://gist.github.com/pswalia2u/3ae86e24a9ba59bdced011eaa26e2f2b/raw/4f5915f65d7eba540cf23e4a1d80cd199814fe4e/pdf-merger.sh | bash