"""Content blueprint - New playlist-centric workflow.""" from flask import (Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app) from flask_login import login_required from werkzeug.utils import secure_filename import os from app.extensions import db, cache from app.models import Content, Playlist, Player from app.models.playlist import playlist_content from app.utils.logger import log_action from app.utils.uploads import process_video_file, set_upload_progress content_bp = Blueprint('content', __name__, url_prefix='/content') @content_bp.route('/') @login_required def content_list(): """Main playlist management page.""" playlists = Playlist.query.order_by(Playlist.created_at.desc()).all() media_files = Content.query.order_by(Content.uploaded_at.desc()).all() players = Player.query.order_by(Player.name).all() return render_template('content/content_list_new.html', playlists=playlists, media_files=media_files, players=players) @content_bp.route('/playlist/create', methods=['POST']) @login_required def create_playlist(): """Create a new playlist.""" try: name = request.form.get('name', '').strip() description = request.form.get('description', '').strip() orientation = request.form.get('orientation', 'Landscape') if not name: flash('Playlist name is required.', 'warning') return redirect(url_for('content.content_list')) # Check if playlist name exists existing = Playlist.query.filter_by(name=name).first() if existing: flash(f'Playlist "{name}" already exists.', 'warning') return redirect(url_for('content.content_list')) playlist = Playlist( name=name, description=description or None, orientation=orientation ) db.session.add(playlist) db.session.commit() log_action('info', f'Created playlist: {name}') flash(f'Playlist "{name}" created successfully!', 'success') except Exception as e: db.session.rollback() log_action('error', f'Error creating playlist: {str(e)}') flash('Error creating playlist.', 'danger') return redirect(url_for('content.content_list')) @content_bp.route('/playlist//delete', methods=['POST']) @login_required def delete_playlist(playlist_id: int): """Delete a playlist.""" playlist = Playlist.query.get_or_404(playlist_id) try: name = playlist.name # Unassign all players from this playlist Player.query.filter_by(playlist_id=playlist_id).update({'playlist_id': None}) db.session.delete(playlist) db.session.commit() cache.clear() log_action('info', f'Deleted playlist: {name}') flash(f'Playlist "{name}" deleted successfully.', 'success') except Exception as e: db.session.rollback() log_action('error', f'Error deleting playlist: {str(e)}') flash('Error deleting playlist.', 'danger') return redirect(url_for('content.content_list')) @content_bp.route('/playlist//manage') @login_required def manage_playlist_content(playlist_id: int): """Manage content in a specific playlist.""" playlist = Playlist.query.get_or_404(playlist_id) # Get content in playlist (ordered) playlist_content = playlist.get_content_ordered() # Get all available content not in this playlist all_content = Content.query.all() playlist_content_ids = {c.id for c in playlist_content} available_content = [c for c in all_content if c.id not in playlist_content_ids] return render_template('content/manage_playlist_content.html', playlist=playlist, playlist_content=playlist_content, available_content=available_content) @content_bp.route('/playlist//add-content', methods=['POST']) @login_required def add_content_to_playlist(playlist_id: int): """Add content to playlist.""" playlist = Playlist.query.get_or_404(playlist_id) try: content_id = request.form.get('content_id', type=int) duration = request.form.get('duration', type=int, default=10) if not content_id: flash('Please select content to add.', 'warning') return redirect(url_for('content.manage_playlist_content', playlist_id=playlist_id)) content = Content.query.get_or_404(content_id) # Get max position from sqlalchemy import select, func from app.models.playlist import playlist_content max_pos = db.session.execute( select(func.max(playlist_content.c.position)).where( playlist_content.c.playlist_id == playlist_id ) ).scalar() or 0 # Add to playlist stmt = playlist_content.insert().values( playlist_id=playlist_id, content_id=content_id, position=max_pos + 1, duration=duration ) db.session.execute(stmt) playlist.increment_version() db.session.commit() cache.clear() log_action('info', f'Added "{content.filename}" to playlist "{playlist.name}"') flash(f'Added "{content.filename}" to playlist.', 'success') except Exception as e: db.session.rollback() log_action('error', f'Error adding content to playlist: {str(e)}') flash('Error adding content to playlist.', 'danger') return redirect(url_for('content.manage_playlist_content', playlist_id=playlist_id)) @content_bp.route('/playlist//remove-content/', methods=['POST']) @login_required def remove_content_from_playlist(playlist_id: int, content_id: int): """Remove content from playlist.""" playlist = Playlist.query.get_or_404(playlist_id) try: from app.models.playlist import playlist_content # Remove from playlist stmt = playlist_content.delete().where( (playlist_content.c.playlist_id == playlist_id) & (playlist_content.c.content_id == content_id) ) db.session.execute(stmt) playlist.increment_version() db.session.commit() cache.clear() log_action('info', f'Removed content from playlist "{playlist.name}"') flash('Content removed from playlist.', 'success') except Exception as e: db.session.rollback() log_action('error', f'Error removing content from playlist: {str(e)}') flash('Error removing content from playlist.', 'danger') return redirect(url_for('content.manage_playlist_content', playlist_id=playlist_id)) @content_bp.route('/playlist//reorder', methods=['POST']) @login_required def reorder_playlist_content(playlist_id: int): """Reorder content in playlist.""" playlist = Playlist.query.get_or_404(playlist_id) try: data = request.get_json() content_ids = data.get('content_ids', []) if not content_ids: return jsonify({'success': False, 'message': 'No content IDs provided'}), 400 from app.models.playlist import playlist_content # Update positions for idx, content_id in enumerate(content_ids, start=1): stmt = playlist_content.update().where( (playlist_content.c.playlist_id == playlist_id) & (playlist_content.c.content_id == content_id) ).values(position=idx) db.session.execute(stmt) playlist.increment_version() db.session.commit() cache.clear() log_action('info', f'Reordered playlist "{playlist.name}"') return jsonify({ 'success': True, 'message': 'Playlist reordered successfully', 'version': playlist.version }) except Exception as e: db.session.rollback() log_action('error', f'Error reordering playlist: {str(e)}') return jsonify({'success': False, 'message': str(e)}), 500 @content_bp.route('/upload-media-page') @login_required def upload_media_page(): """Display upload media page.""" playlists = Playlist.query.order_by(Playlist.name).all() return render_template('content/upload_media.html', playlists=playlists) @content_bp.route('/upload-media', methods=['POST']) @login_required def upload_media(): """Upload media files to library.""" try: files = request.files.getlist('files') content_type = request.form.get('content_type', 'image') duration = request.form.get('duration', type=int, default=10) playlist_id = request.form.get('playlist_id', type=int) if not files or files[0].filename == '': flash('No files provided.', 'warning') return redirect(url_for('content.upload_media_page')) upload_folder = current_app.config['UPLOAD_FOLDER'] os.makedirs(upload_folder, exist_ok=True) uploaded_count = 0 for file in files: if file.filename == '': continue filename = secure_filename(file.filename) filepath = os.path.join(upload_folder, filename) # Check if file already exists existing = Content.query.filter_by(filename=filename).first() if existing: log_action('warning', f'File {filename} already exists, skipping') continue # Save file file.save(filepath) # Determine content type from extension file_ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else '' if file_ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp']: detected_type = 'image' elif file_ext in ['mp4', 'avi', 'mov', 'mkv', 'webm']: detected_type = 'video' # Process video for Raspberry Pi success, message = process_video_file(filepath, os.urandom(8).hex()) if not success: log_action('error', f'Video processing failed: {message}') elif file_ext == 'pdf': detected_type = 'pdf' else: detected_type = 'other' # Create content record content = Content( filename=filename, content_type=detected_type, duration=duration, file_size=os.path.getsize(filepath) ) db.session.add(content) db.session.flush() # Get content ID # Add to playlist if specified if playlist_id: playlist = Playlist.query.get(playlist_id) if playlist: # Get max position max_position = db.session.query(db.func.max(playlist_content.c.position))\ .filter(playlist_content.c.playlist_id == playlist_id)\ .scalar() or 0 # Add to playlist stmt = playlist_content.insert().values( playlist_id=playlist_id, content_id=content.id, position=max_position + 1, duration=duration ) db.session.execute(stmt) # Increment playlist version playlist.version += 1 uploaded_count += 1 db.session.commit() cache.clear() log_action('info', f'Uploaded {uploaded_count} media files') if playlist_id: playlist = Playlist.query.get(playlist_id) flash(f'Successfully uploaded {uploaded_count} file(s) to playlist "{playlist.name}"!', 'success') else: flash(f'Successfully uploaded {uploaded_count} file(s) to media library!', 'success') except Exception as e: db.session.rollback() log_action('error', f'Error uploading media: {str(e)}') flash('Error uploading media files.', 'danger') return redirect(url_for('content.upload_media_page')) @content_bp.route('/player//assign-playlist', methods=['POST']) @login_required def assign_player_to_playlist(player_id: int): """Assign a player to a playlist.""" player = Player.query.get_or_404(player_id) try: playlist_id = request.form.get('playlist_id', type=int) if playlist_id: playlist = Playlist.query.get_or_404(playlist_id) player.playlist_id = playlist_id log_action('info', f'Assigned player "{player.name}" to playlist "{playlist.name}"') flash(f'Player "{player.name}" assigned to playlist "{playlist.name}".', 'success') else: player.playlist_id = None log_action('info', f'Unassigned player "{player.name}" from playlist') flash(f'Player "{player.name}" unassigned from playlist.', 'success') db.session.commit() cache.clear() except Exception as e: db.session.rollback() log_action('error', f'Error assigning player to playlist: {str(e)}') flash('Error assigning player to playlist.', 'danger') return redirect(url_for('content.content_list'))