updated digiserver 2
This commit is contained in:
@@ -30,16 +30,51 @@ upload_progress = {}
|
||||
def content_list():
|
||||
"""Display list of all content."""
|
||||
try:
|
||||
contents = Content.query.order_by(Content.uploaded_at.desc()).all()
|
||||
# Get all unique content files (by filename)
|
||||
from sqlalchemy import func
|
||||
|
||||
# Get group info for each content
|
||||
content_groups = {}
|
||||
# Get content with player information
|
||||
contents = Content.query.order_by(Content.filename, Content.uploaded_at.desc()).all()
|
||||
|
||||
# Group content by filename to show which players have each file
|
||||
content_map = {}
|
||||
for content in contents:
|
||||
content_groups[content.id] = content.groups.count()
|
||||
if content.filename not in content_map:
|
||||
content_map[content.filename] = {
|
||||
'content': content,
|
||||
'players': [],
|
||||
'groups': []
|
||||
}
|
||||
|
||||
# Add player info if assigned to a player
|
||||
if content.player_id:
|
||||
from app.models import Player
|
||||
player = Player.query.get(content.player_id)
|
||||
if player:
|
||||
content_map[content.filename]['players'].append({
|
||||
'id': player.id,
|
||||
'name': player.name,
|
||||
'group': player.group.name if player.group else None
|
||||
})
|
||||
|
||||
return render_template('content_list.html',
|
||||
contents=contents,
|
||||
content_groups=content_groups)
|
||||
# Convert to list for template
|
||||
content_list = []
|
||||
for filename, data in content_map.items():
|
||||
content_list.append({
|
||||
'filename': filename,
|
||||
'content_type': data['content'].content_type,
|
||||
'duration': data['content'].duration,
|
||||
'file_size': data['content'].file_size_mb,
|
||||
'uploaded_at': data['content'].uploaded_at,
|
||||
'players': data['players'],
|
||||
'player_count': len(data['players'])
|
||||
})
|
||||
|
||||
# Sort by upload date
|
||||
content_list.sort(key=lambda x: x['uploaded_at'], reverse=True)
|
||||
|
||||
return render_template('content/content_list.html',
|
||||
content_list=content_list)
|
||||
except Exception as e:
|
||||
log_action('error', f'Error loading content list: {str(e)}')
|
||||
flash('Error loading content list.', 'danger')
|
||||
@@ -51,86 +86,166 @@ def content_list():
|
||||
def upload_content():
|
||||
"""Upload new content."""
|
||||
if request.method == 'GET':
|
||||
return render_template('upload_content.html')
|
||||
# Get parameters for return URL and pre-selection
|
||||
target_type = request.args.get('target_type')
|
||||
target_id = request.args.get('target_id', type=int)
|
||||
return_url = request.args.get('return_url', url_for('content.content_list'))
|
||||
|
||||
# Get all players and groups for selection
|
||||
from app.models import Player
|
||||
players = [{'id': p.id, 'name': p.name} for p in Player.query.order_by(Player.name).all()]
|
||||
groups = [{'id': g.id, 'name': g.name} for g in Group.query.order_by(Group.name).all()]
|
||||
|
||||
return render_template('content/upload_content.html',
|
||||
players=players,
|
||||
groups=groups,
|
||||
target_type=target_type,
|
||||
target_id=target_id,
|
||||
return_url=return_url)
|
||||
|
||||
try:
|
||||
if 'file' not in request.files:
|
||||
flash('No file provided.', 'warning')
|
||||
# Get form data
|
||||
target_type = request.form.get('target_type')
|
||||
target_id = request.form.get('target_id', type=int)
|
||||
media_type = request.form.get('media_type', 'image')
|
||||
duration = request.form.get('duration', type=int, default=10)
|
||||
session_id = request.form.get('session_id', os.urandom(8).hex())
|
||||
return_url = request.form.get('return_url', url_for('content.content_list'))
|
||||
|
||||
# Get files
|
||||
files = request.files.getlist('files')
|
||||
|
||||
if not files or files[0].filename == '':
|
||||
flash('No files provided.', 'warning')
|
||||
return redirect(url_for('content.upload_content'))
|
||||
|
||||
file = request.files['file']
|
||||
|
||||
if file.filename == '':
|
||||
flash('No file selected.', 'warning')
|
||||
if not target_type or not target_id:
|
||||
flash('Please select a target type and target ID.', 'warning')
|
||||
return redirect(url_for('content.upload_content'))
|
||||
|
||||
# Get optional parameters
|
||||
duration = request.form.get('duration', type=int)
|
||||
description = request.form.get('description', '').strip()
|
||||
# Initialize progress tracking using shared utility
|
||||
set_upload_progress(session_id, 0, 'Starting upload...', 'uploading')
|
||||
|
||||
# Generate unique upload ID for progress tracking
|
||||
upload_id = os.urandom(16).hex()
|
||||
|
||||
# Save file with progress tracking
|
||||
filename = secure_filename(file.filename)
|
||||
# Process each file
|
||||
upload_folder = current_app.config['UPLOAD_FOLDER']
|
||||
os.makedirs(upload_folder, exist_ok=True)
|
||||
|
||||
filepath = os.path.join(upload_folder, filename)
|
||||
processed_count = 0
|
||||
total_files = len(files)
|
||||
|
||||
# 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...')
|
||||
for idx, file in enumerate(files):
|
||||
if file.filename == '':
|
||||
continue
|
||||
|
||||
# Update progress
|
||||
progress_pct = int((idx / total_files) * 80) # 0-80% for file processing
|
||||
set_upload_progress(session_id, progress_pct,
|
||||
f'Processing file {idx + 1} of {total_files}...', 'processing')
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
filepath = os.path.join(upload_folder, filename)
|
||||
|
||||
# Save file
|
||||
file.save(filepath)
|
||||
|
||||
# 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 to Raspberry Pi optimized format)
|
||||
set_upload_progress(session_id, progress_pct + 5,
|
||||
f'Optimizing video {idx + 1} for Raspberry Pi (30fps, H.264)...', 'processing')
|
||||
success, message = process_video_file(filepath, session_id)
|
||||
if not success:
|
||||
log_action('error', f'Video optimization failed: {message}')
|
||||
continue # Skip this file and move to next
|
||||
elif file_ext == 'pdf':
|
||||
content_type = 'pdf'
|
||||
# Process PDF (convert to images)
|
||||
set_upload_progress(session_id, progress_pct + 5,
|
||||
f'Converting PDF {idx + 1}...', 'processing')
|
||||
# process_pdf_file(filepath, session_id)
|
||||
elif file_ext in ['ppt', 'pptx']:
|
||||
content_type = 'presentation'
|
||||
# Process presentation (convert to PDF then images)
|
||||
set_upload_progress(session_id, progress_pct + 5,
|
||||
f'Converting PowerPoint {idx + 1}...', 'processing')
|
||||
# This would call pptx_converter utility
|
||||
else:
|
||||
content_type = 'other'
|
||||
|
||||
# Create content record
|
||||
new_content = Content(
|
||||
filename=filename,
|
||||
content_type=content_type,
|
||||
duration=duration,
|
||||
file_size=os.path.getsize(filepath)
|
||||
)
|
||||
|
||||
# Link to target (player or group)
|
||||
if target_type == 'player':
|
||||
from app.models import Player
|
||||
player = Player.query.get(target_id)
|
||||
if player:
|
||||
# Add content directly to player's playlist
|
||||
new_content.player_id = target_id
|
||||
db.session.add(new_content)
|
||||
# Increment playlist version
|
||||
player.playlist_version += 1
|
||||
log_action('info', f'Content "{filename}" added to player "{player.name}" (version {player.playlist_version})')
|
||||
|
||||
elif target_type == 'group':
|
||||
group = Group.query.get(target_id)
|
||||
if group:
|
||||
# For groups, create separate content entry for EACH player in the group
|
||||
# This matches the old app behavior
|
||||
for player in group.players:
|
||||
player_content = Content(
|
||||
filename=filename,
|
||||
content_type=content_type,
|
||||
duration=duration,
|
||||
file_size=os.path.getsize(filepath),
|
||||
player_id=player.id
|
||||
)
|
||||
db.session.add(player_content)
|
||||
# Increment each player's playlist version
|
||||
player.playlist_version += 1
|
||||
|
||||
log_action('info', f'Content "{filename}" added to {len(group.players)} players in group "{group.name}"')
|
||||
# Don't add the original new_content since we created per-player entries
|
||||
new_content = None
|
||||
|
||||
if new_content:
|
||||
db.session.add(new_content)
|
||||
|
||||
processed_count += 1
|
||||
|
||||
# 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)
|
||||
# Commit all changes
|
||||
set_upload_progress(session_id, 90, 'Saving to database...', 'processing')
|
||||
db.session.commit()
|
||||
|
||||
set_upload_progress(upload_id, 100, 'Complete!')
|
||||
# Complete
|
||||
set_upload_progress(session_id, 100,
|
||||
f'Successfully uploaded {processed_count} file(s)!', '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')
|
||||
log_action('info', f'{processed_count} files uploaded successfully (Type: {media_type})')
|
||||
flash(f'{processed_count} file(s) uploaded successfully.', 'success')
|
||||
|
||||
return redirect(url_for('content.content_list'))
|
||||
return redirect(return_url)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
|
||||
# Update progress to error state
|
||||
if 'session_id' in locals():
|
||||
set_upload_progress(session_id, 0, f'Upload failed: {str(e)}', 'error')
|
||||
|
||||
log_action('error', f'Error uploading content: {str(e)}')
|
||||
flash('Error uploading content. Please try again.', 'danger')
|
||||
return redirect(url_for('content.upload_content'))
|
||||
@@ -143,7 +258,7 @@ def edit_content(content_id: int):
|
||||
content = Content.query.get_or_404(content_id)
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template('edit_content.html', content=content)
|
||||
return render_template('content/edit_content.html', content=content)
|
||||
|
||||
try:
|
||||
duration = request.form.get('duration', type=int)
|
||||
@@ -201,6 +316,54 @@ def delete_content(content_id: int):
|
||||
return redirect(url_for('content.content_list'))
|
||||
|
||||
|
||||
@content_bp.route('/delete-by-filename', methods=['POST'])
|
||||
@login_required
|
||||
def delete_by_filename():
|
||||
"""Delete all content entries with a specific filename."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
filename = data.get('filename')
|
||||
|
||||
if not filename:
|
||||
return jsonify({'success': False, 'message': 'No filename provided'}), 400
|
||||
|
||||
# Find all content entries with this filename
|
||||
contents = Content.query.filter_by(filename=filename).all()
|
||||
|
||||
if not contents:
|
||||
return jsonify({'success': False, 'message': 'Content not found'}), 404
|
||||
|
||||
deleted_count = len(contents)
|
||||
|
||||
# Delete file from disk (only once)
|
||||
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
log_action('info', f'Deleted file from disk: {filename}')
|
||||
|
||||
# Delete all database entries
|
||||
for content in contents:
|
||||
db.session.delete(content)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Clear caches
|
||||
cache.clear()
|
||||
|
||||
log_action('info', f'Content "{filename}" deleted from {deleted_count} playlist(s)')
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Content deleted from {deleted_count} playlist(s)',
|
||||
'deleted_count': deleted_count
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
log_action('error', f'Error deleting content by filename: {str(e)}')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@content_bp.route('/bulk/delete', methods=['POST'])
|
||||
@login_required
|
||||
def bulk_delete_content():
|
||||
|
||||
Reference in New Issue
Block a user