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.Alert(id="upload-status", is_open=False, duration=4000, className="mt-3"), dbc.Input(id="url-input", placeholder="Or enter PDF URL", type="text", className="mt-3"), dbc.Button("Compress PDF", id="compress-btn", color="primary", className="mt-3"), dbc.Spinner( html.Div(id="compression-status", className="mt-3"), color="primary", type="border", fullscreen=True, fullscreen_style={"backgroundColor": "rgba(0, 0, 0, 0.3)"}, ), dcc.Download(id="download-pdf"), ]) ]), ]) 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("upload-status", "children"), Output("upload-status", "is_open"), Output("upload-status", "color"), Input("upload-pdf", "filename"), Input("upload-pdf", "contents"), ) def update_upload_status(filename, contents): if filename is not None and contents is not None: return f"File uploaded: {filename}", True, "success" return "", False, "primary" @callback( Output("compression-status", "children"), Output("download-pdf", "data"), Input("compress-btn", "n_clicks"), State("upload-pdf", "contents"), State("url-input", "value"), prevent_initial_call=True ) def process_compress_and_download(n_clicks, file_content, url): if file_content is None and (url is None or url.strip() == ""): return "Please provide either a file or a URL.", 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, dcc.send_bytes(compressed_content, "compressed.pdf") else: return message, None thread = threading.Thread(target=compression_thread) thread.start() thread.join() return compression_thread() 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.")