File size: 4,890 Bytes
e68fc4b
 
 
 
611be2f
4c85f97
 
 
21bc43d
e68fc4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4c85f97
679df68
72673b1
4c85f97
 
72673b1
4c85f97
 
39ae163
 
4c85f97
 
 
39ae163
 
e68fc4b
 
 
 
21bc43d
4c85f97
 
 
611be2f
52f175a
679df68
52f175a
 
611be2f
21bc43d
87d950a
 
21bc43d
87d950a
 
e68fc4b
21bc43d
 
4c85f97
 
679df68
e68fc4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import dash
from dash import dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate
import pikepdf
import requests
import io
import tempfile
import os
import base64
import threading

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = dbc.Container([
    html.H1("PDF Compressor", className="my-4"),
    dbc.Card([
        dbc.CardBody([
            dcc.Upload(
                id='upload-pdf',
                children=html.Div([
                    'Drag and Drop or ',
                    html.A('Select PDF File')
                ]),
                style={
                    'width': '100%',
                    'height': '60px',
                    'lineHeight': '60px',
                    'borderWidth': '1px',
                    'borderStyle': 'dashed',
                    'borderRadius': '5px',
                    'textAlign': 'center',
                    'margin': '10px'
                },
                multiple=False
            ),
            dbc.Input(id="url-input", placeholder="Or enter PDF URL", type="text", className="mt-3"),
            dbc.Button("Compress", id="compress-btn", color="primary", className="mt-3"),
            dbc.Spinner(html.Div(id="compression-status"), color="primary", type="grow", className="mt-3"),
            dcc.Download(id="download-pdf"),
            dbc.Button("Download Compressed PDF", id="download-btn", color="success", className="mt-3", disabled=True),
        ])
    ]),
])

def compress_pdf(input_file, url):
    if input_file is None and (url is None or url.strip() == ""):
        return None, "Please provide either a file or a URL."
    
    if input_file is not None and url and url.strip() != "":
        return None, "Please provide either a file or a URL, not both."
    
    try:
        if url and url.strip() != "":
            response = requests.get(url)
            response.raise_for_status()
            pdf_content = io.BytesIO(response.content)
            initial_size = len(response.content)
        else:
            content_type, content_string = input_file.split(',')
            decoded = base64.b64decode(content_string)
            pdf_content = io.BytesIO(decoded)
            initial_size = len(decoded)

        with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
            temp_file_path = temp_file.name

        pdf = pikepdf.Pdf.open(pdf_content)
        
        compression_params = dict(compress_streams=True, object_stream_mode=pikepdf.ObjectStreamMode.generate)

        pdf.save(temp_file_path, **compression_params)

        compressed_size = os.path.getsize(temp_file_path)
        compression_ratio = compressed_size / initial_size
        compression_percentage = (1 - compression_ratio) * 100

        if compression_ratio >= 1 or compression_percentage < 5:
            os.remove(temp_file_path)
            return None, f"Unable to compress the PDF effectively. Original file returned. (Attempted compression: {compression_percentage:.2f}%)"

        return temp_file_path, f"PDF compressed successfully! Compression achieved: {compression_percentage:.2f}%"
    except Exception as e:
        return None, f"Error compressing PDF: {str(e)}"

@callback(
    Output("compression-status", "children"),
    Output("download-btn", "disabled"),
    Output("download-pdf", "data"),
    Input("compress-btn", "n_clicks"),
    Input("download-btn", "n_clicks"),
    State("upload-pdf", "contents"),
    State("url-input", "value"),
    prevent_initial_call=True
)
def process_and_compress(compress_clicks, download_clicks, file_content, url):
    ctx = dash.callback_context
    if not ctx.triggered:
        raise PreventUpdate

    triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]

    if triggered_id == "compress-btn":
        if file_content is None and (url is None or url.strip() == ""):
            return "Please provide either a file or a URL.", True, None

        def compression_thread():
            nonlocal file_content, url
            output_file, message = compress_pdf(file_content, url)
            if output_file:
                with open(output_file, "rb") as file:
                    compressed_content = file.read()
                os.remove(output_file)
                return message, False, dcc.send_bytes(compressed_content, "compressed.pdf")
            else:
                return message, True, None

        thread = threading.Thread(target=compression_thread)
        thread.start()
        thread.join()
        return compression_thread()

    elif triggered_id == "download-btn":
        raise PreventUpdate

    raise PreventUpdate

if __name__ == '__main__':
    print("Starting the Dash application...")
    app.run(debug=True, host='0.0.0.0', port=7860)
    print("Dash application has finished running.")