""" File upload processing utilities """ import os import subprocess import shutil from werkzeug.utils import secure_filename from pdf2image import convert_from_path from PIL import Image from app.extensions import db from app.models.content import Content from app.utils.logger import log_upload, log_process, log_content_added def allowed_file(filename, file_type='all'): """ Check if file extension is allowed Args: filename (str): Name of the file file_type (str): Type of file to check ('images', 'videos', 'documents', 'all') Returns: bool: True if file is allowed """ from flask import current_app if '.' not in filename: return False ext = filename.rsplit('.', 1)[1].lower() allowed_extensions = current_app.config['ALLOWED_EXTENSIONS'] if file_type == 'all': all_extensions = set() for extensions in allowed_extensions.values(): all_extensions.update(extensions) return ext in all_extensions return ext in allowed_extensions.get(file_type, set()) def get_file_type(filename): """ Determine file type based on extension Args: filename (str): Name of the file Returns: str: File type ('image', 'video', 'document') """ from flask import current_app if '.' not in filename: return 'unknown' ext = filename.rsplit('.', 1)[1].lower() allowed_extensions = current_app.config['ALLOWED_EXTENSIONS'] for file_type, extensions in allowed_extensions.items(): if ext in extensions: return file_type.rstrip('s') # Remove 's' from 'images', 'videos', etc. return 'unknown' def save_uploaded_file(file, upload_folder): """ Save uploaded file to disk Args: file: FileStorage object from request upload_folder (str): Path to upload folder Returns: tuple: (success, filename, error_message) """ try: if not file or file.filename == '': return False, None, "No file selected" if not allowed_file(file.filename): return False, None, f"File type not allowed: {file.filename}" # Generate secure filename original_filename = file.filename filename = secure_filename(original_filename) # Handle duplicate filenames base_name, ext = os.path.splitext(filename) counter = 1 while os.path.exists(os.path.join(upload_folder, filename)): filename = f"{base_name}_{counter}{ext}" counter += 1 # Save file file_path = os.path.join(upload_folder, filename) file.save(file_path) return True, filename, None except Exception as e: return False, None, str(e) def process_image(file_path, max_width=1920, max_height=1080): """ Process and optimize image file Args: file_path (str): Path to image file max_width (int): Maximum width for resizing max_height (int): Maximum height for resizing Returns: tuple: (width, height) of processed image """ try: with Image.open(file_path) as img: # Get original dimensions original_width, original_height = img.size # Calculate new dimensions while maintaining aspect ratio if original_width > max_width or original_height > max_height: img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS) img.save(file_path, optimize=True, quality=85) return img.size except Exception as e: print(f"Error processing image {file_path}: {e}") return None, None def process_video(file_path, output_path=None): """ Process video file (convert to web-compatible format) Args: file_path (str): Path to input video file output_path (str): Path for output file (optional) Returns: tuple: (success, output_filename, error_message) """ try: if output_path is None: base_name = os.path.splitext(file_path)[0] output_path = f"{base_name}_converted.mp4" # Use FFmpeg to convert video cmd = [ 'ffmpeg', '-i', file_path, '-c:v', 'libx264', '-preset', 'medium', '-crf', '23', '-c:a', 'aac', '-b:a', '128k', '-movflags', '+faststart', '-y', # Overwrite output file output_path ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: # Remove original file if conversion successful if os.path.exists(output_path) and file_path != output_path: os.remove(file_path) return True, os.path.basename(output_path), None else: return False, None, result.stderr except Exception as e: return False, None, str(e) def process_pdf(file_path, output_folder): """ Convert PDF to images Args: file_path (str): Path to PDF file output_folder (str): Folder to save converted images Returns: list: List of generated image filenames """ try: images = convert_from_path(file_path, dpi=150) base_name = os.path.splitext(os.path.basename(file_path))[0] image_files = [] for i, image in enumerate(images): image_filename = f"{base_name}_page_{i+1}.png" image_path = os.path.join(output_folder, image_filename) image.save(image_path, 'PNG') image_files.append(image_filename) # Remove original PDF os.remove(file_path) return image_files except Exception as e: print(f"Error processing PDF {file_path}: {e}") return [] def process_pptx(file_path, output_folder): """ Convert PowerPoint to images using LibreOffice Args: file_path (str): Path to PPTX file output_folder (str): Folder to save converted images Returns: list: List of generated image filenames """ try: # Use LibreOffice to convert PPTX to PDF first temp_dir = os.path.join(output_folder, 'temp') os.makedirs(temp_dir, exist_ok=True) cmd = [ 'libreoffice', '--headless', '--convert-to', 'pdf', '--outdir', temp_dir, file_path ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: # Find the generated PDF base_name = os.path.splitext(os.path.basename(file_path))[0] pdf_path = os.path.join(temp_dir, f"{base_name}.pdf") if os.path.exists(pdf_path): # Convert PDF to images image_files = process_pdf(pdf_path, output_folder) # Clean up shutil.rmtree(temp_dir) os.remove(file_path) return image_files return [] except Exception as e: print(f"Error processing PPTX {file_path}: {e}") return [] def process_uploaded_files(app, files, duration, target_type, target_id): """ Process uploaded files and add them to the database Args: app: Flask application instance files: List of uploaded files duration (int): Duration for each file in seconds target_type (str): 'player' or 'group' target_id (int): ID of the target player or group Returns: dict: Results of processing """ upload_folder = os.path.join(app.static_folder, 'uploads') results = { 'success': [], 'errors': [], 'processed': 0 } for file in files: if not file or file.filename == '': continue try: # Save the file success, filename, error = save_uploaded_file(file, upload_folder) if not success: results['errors'].append(f"{file.filename}: {error}") continue file_path = os.path.join(upload_folder, filename) file_type = get_file_type(filename) # Process based on file type processed_files = [] if file_type == 'image': width, height = process_image(file_path) processed_files = [filename] elif file_type == 'video': success, converted_filename, error = process_video(file_path) if success: processed_files = [converted_filename] else: results['errors'].append(f"{filename}: Video conversion failed - {error}") continue elif file_type == 'document': if filename.lower().endswith('.pdf'): processed_files = process_pdf(file_path, upload_folder) elif filename.lower().endswith(('.pptx', '.ppt')): processed_files = process_pptx(file_path, upload_folder) if not processed_files: results['errors'].append(f"{filename}: Document conversion failed") continue # Add processed files to database from app.models.player import Player if target_type == 'player': player = Player.query.get(target_id) if not player: results['errors'].append(f"Player {target_id} not found") continue # Get max position for ordering max_position = db.session.query(db.func.max(Content.position)).filter_by(player_id=target_id).scalar() or 0 for processed_file in processed_files: content = Content( file_name=processed_file, original_name=file.filename, duration=duration, player_id=target_id, content_type=file_type, position=max_position + 1 ) db.session.add(content) max_position += 1 # Update playlist version player.increment_playlist_version() log_content_added(file.filename, 'player', player.username) elif target_type == 'group': from app.models.group import Group group = Group.query.get(target_id) if not group: results['errors'].append(f"Group {target_id} not found") continue # Add content to all players in the group for player in group.players: max_position = db.session.query(db.func.max(Content.position)).filter_by(player_id=player.id).scalar() or 0 for processed_file in processed_files: content = Content( file_name=processed_file, original_name=file.filename, duration=duration, player_id=player.id, content_type=file_type, position=max_position + 1 ) db.session.add(content) max_position += 1 player.increment_playlist_version() log_content_added(file.filename, 'group', group.name) results['success'].append(file.filename) results['processed'] += len(processed_files) # Log the upload log_upload(file_type, file.filename, target_type, target_id) except Exception as e: results['errors'].append(f"{file.filename}: {str(e)}") # Commit all changes try: db.session.commit() except Exception as e: db.session.rollback() results['errors'].append(f"Database error: {str(e)}") return results