"""Content blueprint for media upload and management.""" from flask import (Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app, send_from_directory) from flask_login import login_required from werkzeug.utils import secure_filename import os from typing import Optional, Dict import json from app.extensions import db, cache from app.models import Content, Group from app.utils.logger import log_action from app.utils.uploads import ( save_uploaded_file, process_video_file, process_pdf_file, get_upload_progress, set_upload_progress ) content_bp = Blueprint('content', __name__, url_prefix='/content') # In-memory storage for upload progress (for simple demo; use Redis in production) upload_progress = {} @content_bp.route('/') @login_required def content_list(): """Display list of all content.""" try: contents = Content.query.order_by(Content.uploaded_at.desc()).all() # Get group info for each content content_groups = {} for content in contents: content_groups[content.id] = content.groups.count() return render_template('content_list.html', contents=contents, content_groups=content_groups) except Exception as e: log_action('error', f'Error loading content list: {str(e)}') flash('Error loading content list.', 'danger') return redirect(url_for('main.dashboard')) @content_bp.route('/upload', methods=['GET', 'POST']) @login_required def upload_content(): """Upload new content.""" if request.method == 'GET': return render_template('upload_content.html') try: if 'file' not in request.files: flash('No file provided.', 'warning') return redirect(url_for('content.upload_content')) file = request.files['file'] if file.filename == '': flash('No file selected.', 'warning') return redirect(url_for('content.upload_content')) # Get optional parameters duration = request.form.get('duration', type=int) description = request.form.get('description', '').strip() # Generate unique upload ID for progress tracking upload_id = os.urandom(16).hex() # Save file with progress tracking filename = secure_filename(file.filename) upload_folder = current_app.config['UPLOAD_FOLDER'] os.makedirs(upload_folder, exist_ok=True) filepath = os.path.join(upload_folder, filename) # Save file with progress updates set_upload_progress(upload_id, 0, 'Uploading file...') file.save(filepath) set_upload_progress(upload_id, 50, 'File uploaded, processing...') # Determine content type file_ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else '' if file_ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp']: content_type = 'image' elif file_ext in ['mp4', 'avi', 'mov', 'mkv', 'webm']: content_type = 'video' # Process video (convert, optimize, extract metadata) set_upload_progress(upload_id, 60, 'Processing video...') process_video_file(filepath, upload_id) elif file_ext == 'pdf': content_type = 'pdf' # Process PDF (convert to images) set_upload_progress(upload_id, 60, 'Processing PDF...') process_pdf_file(filepath, upload_id) elif file_ext in ['ppt', 'pptx']: content_type = 'presentation' # Process presentation (convert to PDF then images) set_upload_progress(upload_id, 60, 'Processing presentation...') # This would call pptx_converter utility else: content_type = 'other' set_upload_progress(upload_id, 90, 'Creating database entry...') # Create content record new_content = Content( filename=filename, content_type=content_type, duration=duration, description=description or None, file_size=os.path.getsize(filepath) ) db.session.add(new_content) db.session.commit() set_upload_progress(upload_id, 100, 'Complete!') # Clear all playlist caches cache.clear() log_action('info', f'Content "{filename}" uploaded successfully (Type: {content_type})') flash(f'Content "{filename}" uploaded successfully.', 'success') return redirect(url_for('content.content_list')) except Exception as e: db.session.rollback() log_action('error', f'Error uploading content: {str(e)}') flash('Error uploading content. Please try again.', 'danger') return redirect(url_for('content.upload_content')) @content_bp.route('//edit', methods=['GET', 'POST']) @login_required def edit_content(content_id: int): """Edit content metadata.""" content = Content.query.get_or_404(content_id) if request.method == 'GET': return render_template('edit_content.html', content=content) try: duration = request.form.get('duration', type=int) description = request.form.get('description', '').strip() # Update content if duration is not None: content.duration = duration content.description = description or None db.session.commit() # Clear caches cache.clear() log_action('info', f'Content "{content.filename}" (ID: {content_id}) updated') flash(f'Content "{content.filename}" updated successfully.', 'success') return redirect(url_for('content.content_list')) except Exception as e: db.session.rollback() log_action('error', f'Error updating content: {str(e)}') flash('Error updating content. Please try again.', 'danger') return redirect(url_for('content.edit_content', content_id=content_id)) @content_bp.route('//delete', methods=['POST']) @login_required def delete_content(content_id: int): """Delete content and associated file.""" try: content = Content.query.get_or_404(content_id) filename = content.filename # Delete file from disk filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) if os.path.exists(filepath): os.remove(filepath) # Delete from database db.session.delete(content) db.session.commit() # Clear caches cache.clear() log_action('info', f'Content "{filename}" (ID: {content_id}) deleted') flash(f'Content "{filename}" deleted successfully.', 'success') except Exception as e: db.session.rollback() log_action('error', f'Error deleting content: {str(e)}') flash('Error deleting content. Please try again.', 'danger') return redirect(url_for('content.content_list')) @content_bp.route('/bulk/delete', methods=['POST']) @login_required def bulk_delete_content(): """Delete multiple content items at once.""" try: content_ids = request.json.get('content_ids', []) if not content_ids: return jsonify({'success': False, 'error': 'No content selected'}), 400 # Delete content deleted_count = 0 for content_id in content_ids: content = Content.query.get(content_id) if content: # Delete file filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], content.filename) if os.path.exists(filepath): os.remove(filepath) db.session.delete(content) deleted_count += 1 db.session.commit() # Clear caches cache.clear() log_action('info', f'Bulk deleted {deleted_count} content items') return jsonify({'success': True, 'deleted': deleted_count}) except Exception as e: db.session.rollback() log_action('error', f'Error bulk deleting content: {str(e)}') return jsonify({'success': False, 'error': str(e)}), 500 @content_bp.route('/upload-progress/') @login_required def upload_progress_status(upload_id: str): """Get upload progress for a specific upload.""" progress = get_upload_progress(upload_id) return jsonify(progress) @content_bp.route('/preview/') @login_required def preview_content(content_id: int): """Preview content in browser.""" try: content = Content.query.get_or_404(content_id) # Serve file from uploads folder return send_from_directory( current_app.config['UPLOAD_FOLDER'], content.filename, as_attachment=False ) except Exception as e: log_action('error', f'Error previewing content: {str(e)}') return "Error loading content", 500 @content_bp.route('//download') @login_required def download_content(content_id: int): """Download content file.""" try: content = Content.query.get_or_404(content_id) log_action('info', f'Content "{content.filename}" downloaded') return send_from_directory( current_app.config['UPLOAD_FOLDER'], content.filename, as_attachment=True ) except Exception as e: log_action('error', f'Error downloading content: {str(e)}') return "Error downloading content", 500 @content_bp.route('/statistics') @login_required def content_statistics(): """Get content statistics.""" try: total_content = Content.query.count() # Count by type type_counts = {} for content_type in ['image', 'video', 'pdf', 'presentation', 'other']: count = Content.query.filter_by(content_type=content_type).count() type_counts[content_type] = count # Calculate total storage upload_folder = current_app.config['UPLOAD_FOLDER'] total_size = 0 if os.path.exists(upload_folder): for dirpath, dirnames, filenames in os.walk(upload_folder): for filename in filenames: filepath = os.path.join(dirpath, filename) if os.path.exists(filepath): total_size += os.path.getsize(filepath) return jsonify({ 'total': total_content, 'by_type': type_counts, 'total_size_mb': round(total_size / (1024 * 1024), 2) }) except Exception as e: log_action('error', f'Error getting content statistics: {str(e)}') return jsonify({'error': str(e)}), 500 @content_bp.route('/check-duplicates') @login_required def check_duplicates(): """Check for duplicate filenames.""" try: # Get all filenames all_content = Content.query.all() filename_counts = {} for content in all_content: filename_counts[content.filename] = filename_counts.get(content.filename, 0) + 1 # Find duplicates duplicates = {fname: count for fname, count in filename_counts.items() if count > 1} return jsonify({ 'has_duplicates': len(duplicates) > 0, 'duplicates': duplicates }) except Exception as e: log_action('error', f'Error checking duplicates: {str(e)}') return jsonify({'error': str(e)}), 500 @content_bp.route('//groups') @login_required def content_groups_info(content_id: int): """Get groups that contain this content.""" try: content = Content.query.get_or_404(content_id) groups_data = [] for group in content.groups: groups_data.append({ 'id': group.id, 'name': group.name, 'description': group.description, 'player_count': group.players.count() }) return jsonify({ 'content_id': content_id, 'filename': content.filename, 'groups': groups_data }) except Exception as e: log_action('error', f'Error getting content groups: {str(e)}') return jsonify({'error': str(e)}), 500