File size: 5,251 Bytes
ae4f5c7 98e158d ae4f5c7 30b1981 c51c2d8 30b1981 98e158d ae4f5c7 98e158d ae4f5c7 98e158d 30b1981 c51c2d8 30b1981 98e158d ae4f5c7 98e158d ae4f5c7 98e158d 30b1981 ae4f5c7 223ca22 efaec81 223ca22 efaec81 223ca22 efaec81 223ca22 efaec81 223ca22 efaec81 223ca22 30b1981 efaec81 ae4f5c7 30b1981 efaec81 223ca22 ae4f5c7 c51c2d8 ae4f5c7 30b1981 c51c2d8 30b1981 ae4f5c7 98e158d 223ca22 30b1981 efaec81 30b1981 223ca22 30b1981 223ca22 c51c2d8 30b1981 98e158d ae4f5c7 223ca22 c51c2d8 223ca22 c51c2d8 223ca22 ae4f5c7 98e158d 223ca22 efaec81 30b1981 efaec81 30b1981 223ca22 efaec81 223ca22 30b1981 223ca22 30b1981 223ca22 30b1981 ae4f5c7 98e158d efaec81 98e158d efaec81 98e158d ae4f5c7 98e158d 223ca22 ae4f5c7 c51c2d8 | 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 | from fastapi import APIRouter, File, UploadFile, HTTPException, Query, BackgroundTasks
from fastapi.responses import FileResponse
import os
import tempfile
import subprocess
import shutil
import logging
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
router = APIRouter()
TEMP_DIR = "/tmp/conversions"
os.makedirs(TEMP_DIR, exist_ok=True)
def cleanup_files(*paths):
for path in paths:
try:
if os.path.exists(path):
os.remove(path)
except Exception:
pass
@router.post("/compress")
async def compress_pdf(
background_tasks: BackgroundTasks,
file: UploadFile = File(...),
quality: str = Query("medium", enum=["low", "medium", "high"])
):
if not shutil.which("gs"):
logger.error("Ghostscript not found! Please update Dockerfile.")
raise HTTPException(status_code=500, detail="Server Configuration Error: Ghostscript is not installed.")
if not file.filename.endswith('.pdf'):
raise HTTPException(status_code=400, detail="Only PDF files are allowed")
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf', dir=TEMP_DIR) as tmp_in:
input_path = tmp_in.name
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf', dir=TEMP_DIR) as tmp_out:
output_path = tmp_out.name
try:
content = await file.read()
with open(input_path, 'wb') as f:
f.write(content)
# === FIXED SETTINGS ===
# We removed the manual 'Resolution' flags because they conflict
# with PDFSETTINGS in Ghostscript v10+, causing the crash.
settings = {
"low": {
"pdf_settings": "/screen", # Forces 72 DPI
"extra_flags": [
"-dDownsampleColorImages=true",
"-dDownsampleGrayImages=true",
"-dDownsampleMonoImages=true",
# We remove the explicit resolution numbers to avoid 'rangecheck' errors
]
},
"medium": {
"pdf_settings": "/ebook", # Forces 150 DPI
"extra_flags": [
"-dDownsampleColorImages=true",
"-dDownsampleGrayImages=true",
"-dDownsampleMonoImages=true",
]
},
"high": {
"pdf_settings": "/printer", # Forces 300 DPI
"extra_flags": []
}
}
selected = settings.get(quality)
# Construct Command
cmd = [
"gs",
"-dSAFER",
"-sDEVICE=pdfwrite",
"-dCompatibilityLevel=1.4",
f"-dPDFSETTINGS={selected['pdf_settings']}",
"-dNOPAUSE",
"-dBATCH",
f"-sOutputFile={output_path}"
]
# Add extra flags
cmd.extend(selected["extra_flags"])
cmd.append(input_path)
logger.info(f"Running command: {' '.join(cmd)}")
# Run Ghostscript
process = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # Merge output to catch errors
text=True
)
if process.returncode != 0:
logger.error(f"Ghostscript Error Output:\n{process.stdout}")
raise Exception("Ghostscript processing failed. Check logs.")
# Check Output
if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
raise Exception("Compression resulted in an empty file.")
original_size = os.path.getsize(input_path)
compressed_size = os.path.getsize(output_path)
# === FAILSAFE ===
# If the file got bigger, return the original file instead of the bad one.
final_path = output_path
final_size = compressed_size
output_filename = f"{os.path.splitext(file.filename)[0]}_compressed.pdf"
if compressed_size >= original_size:
logger.info(f"Compression ineffective ({compressed_size} > {original_size}). Returning original.")
final_path = input_path
final_size = original_size
output_filename = f"{os.path.splitext(file.filename)[0]}_original.pdf"
background_tasks.add_task(cleanup_files, output_path) # Delete the useless compressed file
else:
background_tasks.add_task(cleanup_files, input_path) # Delete input file
# Ensure cleanup runs eventually
background_tasks.add_task(cleanup_files, input_path, output_path)
reduction = ((original_size - final_size) / original_size) * 100
return FileResponse(
final_path,
media_type="application/pdf",
filename=output_filename,
headers={
"X-Original-Size": str(original_size),
"X-Compressed-Size": str(final_size),
"X-Size-Reduction": f"{reduction:.2f}%"
}
)
except Exception as e:
logger.exception("Server Error")
cleanup_files(input_path, output_path)
raise HTTPException(status_code=500, detail=str(e))
|