diff --git a/app/app.py b/app/app.py index 48d2119..8246250 100755 --- a/app/app.py +++ b/app/app.py @@ -69,8 +69,12 @@ db_path = os.path.join(instance_dir, 'dashboard.db') app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -# Set maximum content length to 1GB -app.config['MAX_CONTENT_LENGTH'] = 2048 * 2048 * 2048 # 2GB, adjust as needed +# Set maximum content length to 2GB +app.config['MAX_CONTENT_LENGTH'] = 2048 * 1024 * 1024 # 2GB, adjust as needed + +# Set longer timeouts for file processing +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 300 # 5 minutes for static files +app.config['PERMANENT_SESSION_LIFETIME'] = 1800 # 30 minutes for sessions # Ensure the instance folder exists os.makedirs(app.instance_path, exist_ok=True) @@ -95,6 +99,22 @@ login_manager.login_view = 'login' migrate = Migrate(app, db) +# Add error handlers for better user experience +@app.errorhandler(413) +def request_entity_too_large(error): + flash('File too large. Please upload files smaller than 2GB.', 'danger') + return redirect(url_for('dashboard')) + +@app.errorhandler(408) +def request_timeout(error): + flash('Request timed out. Please try uploading smaller files or try again later.', 'danger') + return redirect(url_for('dashboard')) + +@app.errorhandler(500) +def internal_server_error(error): + flash('An internal server error occurred. Please try again or contact support.', 'danger') + return redirect(url_for('dashboard')) + @login_manager.user_loader def load_user(user_id): return db.session.get(User, int(user_id)) @@ -213,8 +233,23 @@ def upload_content(): flash('Please select a target type and target ID.', 'danger') return redirect(url_for('upload_content')) - # Process uploaded files and get results - results = process_uploaded_files(app, files, media_type, duration, target_type, target_id) + try: + # Process uploaded files and get results + results = process_uploaded_files(app, files, media_type, duration, target_type, target_id) + + # Check for any failed uploads + failed_files = [r for r in results if not r.get('success', True)] + if failed_files: + for failed in failed_files: + flash(f"Error uploading {failed.get('filename', 'unknown file')}: {failed.get('message', 'Unknown error')}", 'warning') + else: + flash('All files uploaded and processed successfully!', 'success') + + except Exception as e: + print(f"Error in upload_content: {e}") + import traceback + traceback.print_exc() + flash(f'Upload failed: {str(e)}', 'danger') return redirect(return_url) diff --git a/app/static/uploads/call-of-duty-black-3840x2160-23674.jpg b/app/static/uploads/call-of-duty-black-3840x2160-23674.jpg new file mode 100644 index 0000000..62f7d98 Binary files /dev/null and b/app/static/uploads/call-of-duty-black-3840x2160-23674.jpg differ diff --git a/app/templates/upload_content.html b/app/templates/upload_content.html index 8f30906..99e141b 100644 --- a/app/templates/upload_content.html +++ b/app/templates/upload_content.html @@ -243,7 +243,7 @@ statusMessage.textContent = 'Converting PDF to 4K images. This may take a while...'; break; case 'ppt': - statusMessage.textContent = 'Converting PowerPoint to 4K images. This may take a while...'; + statusMessage.textContent = 'Converting PowerPoint to images (PPTX → PDF → Images). This may take 2-5 minutes...'; break; default: statusMessage.textContent = 'Uploading and processing your files. Please wait...'; diff --git a/app/utils/pptx_converter.py b/app/utils/pptx_converter.py index e00e31c..5fec17f 100644 --- a/app/utils/pptx_converter.py +++ b/app/utils/pptx_converter.py @@ -9,12 +9,23 @@ The converted PDF is then processed by the main upload workflow for 4K image gen import os import subprocess import logging +import signal +import time # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +def cleanup_libreoffice_processes(): + """Clean up any hanging LibreOffice processes""" + try: + subprocess.run(['pkill', '-f', 'soffice'], capture_output=True, timeout=10) + time.sleep(1) # Give processes time to terminate + except Exception as e: + logger.warning(f"Failed to cleanup LibreOffice processes: {e}") + + def pptx_to_pdf_libreoffice(pptx_path, output_dir): """ Convert PPTX to PDF using LibreOffice for highest quality. @@ -30,6 +41,9 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir): str: Path to the generated PDF file, or None if conversion failed """ try: + # Clean up any existing LibreOffice processes + cleanup_libreoffice_processes() + # Ensure output directory exists os.makedirs(output_dir, exist_ok=True) @@ -39,14 +53,19 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir): '--headless', '--convert-to', 'pdf', '--outdir', output_dir, + '--invisible', # Run without any UI + '--nodefault', # Don't start with default template pptx_path ] logger.info(f"Converting PPTX to PDF using LibreOffice: {pptx_path}") - result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + # Increase timeout to 300 seconds (5 minutes) for large presentations + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) if result.returncode != 0: logger.error(f"LibreOffice conversion failed: {result.stderr}") + logger.error(f"LibreOffice stdout: {result.stdout}") + cleanup_libreoffice_processes() # Clean up on failure return None # Find the generated PDF file @@ -55,16 +74,22 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir): if os.path.exists(pdf_path): logger.info(f"PDF conversion successful: {pdf_path}") + cleanup_libreoffice_processes() # Clean up after success return pdf_path else: logger.error(f"PDF file not found after conversion: {pdf_path}") + cleanup_libreoffice_processes() # Clean up on failure return None except subprocess.TimeoutExpired: - logger.error("LibreOffice conversion timed out (120s)") + logger.error("LibreOffice conversion timed out (300s)") + cleanup_libreoffice_processes() # Clean up on timeout return None except Exception as e: logger.error(f"Error in PPTX to PDF conversion: {e}") + import traceback + logger.error(f"Traceback: {traceback.format_exc()}") + cleanup_libreoffice_processes() # Clean up on error return None diff --git a/app/utils/uploads.py b/app/utils/uploads.py index 94100a1..0bcdf52 100644 --- a/app/utils/uploads.py +++ b/app/utils/uploads.py @@ -290,21 +290,33 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id): try: # Step 1: Convert PPTX to PDF using LibreOffice for vector quality + print("Step 1: Converting PPTX to PDF...") from utils.pptx_converter import pptx_to_pdf_libreoffice pdf_file = pptx_to_pdf_libreoffice(input_file, output_folder) if not pdf_file: print("Error: Failed to convert PPTX to PDF") + print("This could be due to:") + print("- LibreOffice not properly installed") + print("- Corrupted PPTX file") + print("- Insufficient memory") + print("- File permission issues") return False print(f"PPTX successfully converted to PDF: {pdf_file}") # Step 2: Use the same PDF to images workflow as direct PDF uploads + print("Step 2: Converting PDF to JPG images...") # Convert PDF to JPG images (300 DPI, same as PDF workflow) image_filenames = convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300) if not image_filenames: print("Error: Failed to convert PDF to images") + print("This could be due to:") + print("- poppler-utils not properly installed") + print("- PDF corruption during conversion") + print("- Insufficient disk space") + print("- Memory issues during image processing") return False print(f"Generated {len(image_filenames)} JPG images from PPTX → PDF") @@ -313,11 +325,14 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id): if os.path.exists(input_file): os.remove(input_file) print(f"Original PPTX file deleted: {input_file}") - + # Step 4: Update playlist with generated images in sequential order + print("Step 3: Adding images to playlist...") success = update_playlist_with_files(image_filenames, duration, target_type, target_id) if success: print(f"Successfully processed PPTX: {len(image_filenames)} images added to playlist") + else: + print("Error: Failed to add images to playlist database") return success except Exception as e: