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))