import os import subprocess from flask import Flask from werkzeug.utils import secure_filename from pdf2image import convert_from_path from extensions import db from models import Content, Player, Group from utils.logger import log_content_added, log_upload, log_process # Function to add image to playlist def add_image_to_playlist(app, file, filename, duration, target_type, target_id): """ Save the image file and add it to the playlist database. """ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) # Only save if file does not already exist if not os.path.exists(file_path): file.save(file_path) print(f"Adding image to playlist: {filename}, Target Type: {target_type}, Target ID: {target_id}") if target_type == 'group': group = Group.query.get_or_404(target_id) for player in group.players: new_content = Content(file_name=filename, duration=duration, player_id=player.id) db.session.add(new_content) log_content_added(filename, target_type, group.name) elif target_type == 'player': player = Player.query.get_or_404(target_id) new_content = Content(file_name=filename, duration=duration, player_id=target_id) db.session.add(new_content) log_content_added(filename, target_type, player.username) db.session.commit() log_upload('image', filename, target_type, target_id) return True # Video conversion functions def convert_video(input_file, output_folder): """ Converts a video file to MP4 format with H.264 codec. """ if not os.path.exists(output_folder): os.makedirs(output_folder) # Generate the output file path base_name = os.path.splitext(os.path.basename(input_file))[0] output_file = os.path.join(output_folder, f"{base_name}.mp4") # FFmpeg command to convert the video command = [ "ffmpeg", "-i", input_file, # Input file "-c:v", "libx264", # Video codec: H.264 "-preset", "fast", # Encoding speed/quality tradeoff "-crf", "23", # Constant Rate Factor (quality, lower is better) "-vf", "scale=-1:1080", # Scale video to 1080p (preserve aspect ratio) "-r", "30", # Frame rate: 30 FPS "-c:a", "aac", # Audio codec: AAC "-b:a", "128k", # Audio bitrate output_file # Output file ] try: # Run the FFmpeg command subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) print(f"Video converted successfully: {output_file}") return output_file except subprocess.CalledProcessError as e: print(f"Error converting video: {e.stderr.decode()}") return None def convert_video_and_update_playlist(app, file_path, original_filename, target_type, target_id, duration): """ Converts a video and updates the playlist database. """ print(f"Starting video conversion for: {file_path}") converted_file = convert_video(file_path, app.config['UPLOAD_FOLDER']) if converted_file: converted_filename = os.path.basename(converted_file) print(f"Video converted successfully: {converted_filename}") # Use the application context to interact with the database with app.app_context(): # Update the database with the converted filename if target_type == 'group': group = Group.query.get_or_404(target_id) for player in group.players: content = Content.query.filter_by(player_id=player.id, file_name=original_filename).first() if content: content.file_name = converted_filename elif target_type == 'player': content = Content.query.filter_by(player_id=target_id, file_name=original_filename).first() if content: content.file_name = converted_filename db.session.commit() print(f"Database updated with converted video: {converted_filename}") # Delete the original file only if it exists if os.path.exists(file_path): os.remove(file_path) print(f"Original file deleted: {file_path}") else: print(f"Video conversion failed for: {file_path}") # PDF conversion functions def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=600): """ Convert a PDF file to images in sequential order at high resolution (4K). """ print(f"Converting PDF to images: {pdf_file} at {dpi} DPI") try: # Convert PDF to images images = convert_from_path(pdf_file, dpi=dpi) base_name = os.path.splitext(os.path.basename(pdf_file))[0] image_filenames = [] # Save each page as an image with zero-padded page numbers for proper sorting for i, image in enumerate(images): # Use consistent naming with zero-padded page numbers (e.g., page_001.jpg) page_num = str(i + 1).zfill(3) # e.g., 001, 002, etc. image_filename = f"{base_name}_page_{page_num}.jpg" image_path = os.path.join(output_folder, image_filename) image.save(image_path, 'JPEG') image_filenames.append(image_filename) print(f"Saved page {i + 1} as image: {image_path}") # Verify all pages were saved print(f"PDF conversion complete. {len(image_filenames)} pages saved.") print(f"Images in order: {image_filenames}") # Delete the PDF file if requested if delete_pdf and os.path.exists(pdf_file): os.remove(pdf_file) print(f"PDF file deleted: {pdf_file}") return image_filenames except Exception as e: print(f"Error converting PDF to images: {e}") return [] def update_playlist_with_files(image_filenames, duration, target_type, target_id): """ Add files to a player or group playlist and update version numbers. Args: image_filenames (list): List of filenames to add to playlist duration (int): Duration in seconds for each file target_type (str): 'player' or 'group' target_id (int): ID of the player or group Returns: bool: True if successful, False otherwise """ try: if target_type == 'group': group = Group.query.get_or_404(target_id) for player in group.players: for image_filename in image_filenames: new_content = Content(file_name=image_filename, duration=duration, player_id=player.id) db.session.add(new_content) player.playlist_version += 1 group.playlist_version += 1 elif target_type == 'player': player = Player.query.get_or_404(target_id) for image_filename in image_filenames: new_content = Content(file_name=image_filename, duration=duration, player_id=target_id) db.session.add(new_content) player.playlist_version += 1 else: print(f"Invalid target type: {target_type}") return False db.session.commit() print(f"Added {len(image_filenames)} files to playlist") return True except Exception as e: db.session.rollback() print(f"Error updating playlist: {e}") return False def process_pdf(input_file, output_folder, duration, target_type, target_id): """ Process a PDF file: convert to images and update playlist. Args: input_file (str): Path to the PDF file output_folder (str): Path to save the images duration (int): Duration in seconds for each image target_type (str): 'player' or 'group' target_id (int): ID of the player or group Returns: bool: True if successful, False otherwise """ # Ensure output folder exists if not os.path.exists(output_folder): os.makedirs(output_folder) # Convert PDF to images image_filenames = convert_pdf_to_images(input_file, output_folder) # Update playlist with generated images if image_filenames: return update_playlist_with_files(image_filenames, duration, target_type, target_id) return False def process_pptx(input_file, output_folder, duration, target_type, target_id): """ Process a PPTX file: convert to PDF, then to images, and update playlist in sequential order. Args: input_file (str): Path to the PPTX file output_folder (str): Path to save the images duration (int): Duration in seconds for each image target_type (str): 'player' or 'group' target_id (int): ID of the player or group Returns: bool: True if successful, False otherwise """ # Ensure output folder exists if not os.path.exists(output_folder): os.makedirs(output_folder) # Step 1: Convert PPTX to PDF using LibreOffice pdf_file = os.path.join(output_folder, os.path.splitext(os.path.basename(input_file))[0] + ".pdf") command = [ 'libreoffice', '--headless', '--convert-to', 'pdf', '--outdir', output_folder, '--printer-resolution', '600', input_file ] print(f"Running LibreOffice command: {' '.join(command)}") try: result = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) print(f"PPTX file converted to PDF: {pdf_file}") print(f"LibreOffice output: {result.stdout.decode()}") print(f"LibreOffice errors (if any): {result.stderr.decode()}") # Step 2: Convert PDF to images and update playlist image_filenames = convert_pdf_to_images(pdf_file, output_folder, True, dpi=600) # Verify we got images if not image_filenames: print("Error: No images were generated from the PDF") return False print(f"Generated {len(image_filenames)} images for PPTX") # Step 3: Delete the original PPTX file 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 return update_playlist_with_files(image_filenames, duration, target_type, target_id) except subprocess.CalledProcessError as e: print(f"Error converting PPTX to PDF: {e.stderr.decode() if hasattr(e, 'stderr') else str(e)}") return False except Exception as e: print(f"Error processing PPTX file: {e}") return False def process_uploaded_files(app, files, media_type, duration, target_type, target_id): """ Process uploaded files based on media type and add them to playlists. Returns: list: List of result dictionaries with success status and messages """ results = [] # Get target name for logging target_name = "" if target_type == 'group': group = Group.query.get_or_404(target_id) target_name = group.name elif target_type == 'player': player = Player.query.get_or_404(target_id) target_name = player.username for file in files: try: # Generate a secure filename and save the file filename = secure_filename(file.filename) file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(file_path) print(f"Processing file: {filename}, Media Type: {media_type}") result = {'filename': filename, 'success': True, 'message': ''} if media_type == 'image': add_image_to_playlist(app, file, filename, duration, target_type, target_id) result['message'] = f"Image {filename} added to playlist" log_upload('image', filename, target_type, target_id) elif media_type == 'video': # For videos, add to playlist then start conversion in background if target_type == 'group': group = Group.query.get_or_404(target_id) for player in group.players: new_content = Content(file_name=filename, duration=duration, player_id=player.id) db.session.add(new_content) player.playlist_version += 1 group.playlist_version += 1 elif target_type == 'player': player = Player.query.get_or_404(target_id) new_content = Content(file_name=filename, duration=duration, player_id=target_id) db.session.add(new_content) player.playlist_version += 1 db.session.commit() # Start background conversion import threading threading.Thread(target=convert_video_and_update_playlist, args=(app, file_path, filename, target_type, target_id, duration)).start() result['message'] = f"Video {filename} added to playlist and being processed" log_upload('video', filename, target_type, target_id) elif media_type == 'pdf': # For PDFs, convert to images and update playlist success = process_pdf(file_path, app.config['UPLOAD_FOLDER'], duration, target_type, target_id) if success: result['message'] = f"PDF {filename} processed successfully" log_process('pdf', filename, target_type, target_id) else: result['success'] = False result['message'] = f"Error processing PDF file: {filename}" elif media_type == 'ppt': # For PPT/PPTX, convert to PDF, then to images, and update playlist success = process_pptx(file_path, app.config['UPLOAD_FOLDER'], duration, target_type, target_id) if success: result['message'] = f"PowerPoint {filename} processed successfully" log_process('ppt', filename, target_type, target_id) else: result['success'] = False result['message'] = f"Error processing PowerPoint file: {filename}" results.append(result) except Exception as e: print(f"Error processing file {file.filename}: {e}") results.append({ 'filename': file.filename, 'success': False, 'message': f"Error processing file {file.filename}: {str(e)}" }) return results