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