Files
digiserver-v2/app/blueprints/content.py
ske087 498c03ef00 Replace emoji icons with local SVG files for consistent rendering
- Created 10 SVG icon files in app/static/icons/ (Feather Icons style)
- Updated base.html with SVG icons in navigation and dark mode toggle
- Updated dashboard.html with icons in stats cards and quick actions
- Updated content_list_new.html (playlist management) with SVG icons
- Updated upload_media.html with upload-related icons
- Updated manage_player.html with player management icons
- Icons use currentColor for automatic theme adaptation
- Removed emoji dependency for better Raspberry Pi compatibility
- Added ICON_INTEGRATION.md documentation
2025-11-13 21:00:07 +02:00

377 lines
14 KiB
Python

"""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/<int:playlist_id>/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/<int:playlist_id>/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/<int:playlist_id>/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/<int:playlist_id>/remove-content/<int:content_id>', 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/<int:playlist_id>/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/<int:player_id>/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'))