checked for updates

This commit is contained in:
2025-07-16 11:19:31 +03:00
parent c36ba9dc64
commit 94fad22d85
12 changed files with 1303 additions and 354 deletions

View File

@@ -1,382 +1,229 @@
"""
File upload processing utilities
File upload and processing utilities
"""
import os
import subprocess
import shutil
from flask import current_app
from werkzeug.utils import secure_filename
from pdf2image import convert_from_path
from PIL import Image
from app.extensions import db
from app.models.content import Content
from app.models.player import Player
from app.models.group import Group
from app.utils.logger import log_upload, log_process, log_content_added
def allowed_file(filename, file_type='all'):
"""
Check if file extension is allowed
Args:
filename (str): Name of the file
file_type (str): Type of file to check ('images', 'videos', 'documents', 'all')
Returns:
bool: True if file is allowed
"""
from flask import current_app
if '.' not in filename:
return False
ext = filename.rsplit('.', 1)[1].lower()
allowed_extensions = current_app.config['ALLOWED_EXTENSIONS']
if file_type == 'all':
all_extensions = set()
for extensions in allowed_extensions.values():
all_extensions.update(extensions)
return ext in all_extensions
return ext in allowed_extensions.get(file_type, set())
def get_file_type(filename):
"""
Determine file type based on extension
Args:
filename (str): Name of the file
Returns:
str: File type ('image', 'video', 'document')
"""
from flask import current_app
if '.' not in filename:
return 'unknown'
ext = filename.rsplit('.', 1)[1].lower()
allowed_extensions = current_app.config['ALLOWED_EXTENSIONS']
for file_type, extensions in allowed_extensions.items():
if ext in extensions:
return file_type.rstrip('s') # Remove 's' from 'images', 'videos', etc.
return 'unknown'
def save_uploaded_file(file, upload_folder):
"""
Save uploaded file to disk
Args:
file: FileStorage object from request
upload_folder (str): Path to upload folder
Returns:
tuple: (success, filename, error_message)
"""
try:
if not file or file.filename == '':
return False, None, "No file selected"
if not allowed_file(file.filename):
return False, None, f"File type not allowed: {file.filename}"
# Generate secure filename
original_filename = file.filename
filename = secure_filename(original_filename)
# Handle duplicate filenames
base_name, ext = os.path.splitext(filename)
counter = 1
while os.path.exists(os.path.join(upload_folder, filename)):
filename = f"{base_name}_{counter}{ext}"
counter += 1
# Save file
file_path = os.path.join(upload_folder, filename)
file.save(file_path)
return True, filename, None
except Exception as e:
return False, None, str(e)
def process_image(file_path, max_width=1920, max_height=1080):
"""
Process and optimize image file
Args:
file_path (str): Path to image file
max_width (int): Maximum width for resizing
max_height (int): Maximum height for resizing
Returns:
tuple: (width, height) of processed image
"""
try:
with Image.open(file_path) as img:
# Get original dimensions
original_width, original_height = img.size
# Calculate new dimensions while maintaining aspect ratio
if original_width > max_width or original_height > max_height:
img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
img.save(file_path, optimize=True, quality=85)
return img.size
except Exception as e:
print(f"Error processing image {file_path}: {e}")
return None, None
def process_video(file_path, output_path=None):
"""
Process video file (convert to web-compatible format)
Args:
file_path (str): Path to input video file
output_path (str): Path for output file (optional)
Returns:
tuple: (success, output_filename, error_message)
"""
try:
if output_path is None:
base_name = os.path.splitext(file_path)[0]
output_path = f"{base_name}_converted.mp4"
# Use FFmpeg to convert video
cmd = [
'ffmpeg', '-i', file_path,
'-c:v', 'libx264',
'-preset', 'medium',
'-crf', '23',
'-c:a', 'aac',
'-b:a', '128k',
'-movflags', '+faststart',
'-y', # Overwrite output file
output_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
# Remove original file if conversion successful
if os.path.exists(output_path) and file_path != output_path:
os.remove(file_path)
return True, os.path.basename(output_path), None
else:
return False, None, result.stderr
except Exception as e:
return False, None, str(e)
def process_pdf(file_path, output_folder):
"""
Convert PDF to images
Args:
file_path (str): Path to PDF file
output_folder (str): Folder to save converted images
Returns:
list: List of generated image filenames
"""
try:
images = convert_from_path(file_path, dpi=150)
base_name = os.path.splitext(os.path.basename(file_path))[0]
image_files = []
for i, image in enumerate(images):
image_filename = f"{base_name}_page_{i+1}.png"
image_path = os.path.join(output_folder, image_filename)
image.save(image_path, 'PNG')
image_files.append(image_filename)
# Remove original PDF
os.remove(file_path)
return image_files
except Exception as e:
print(f"Error processing PDF {file_path}: {e}")
return []
def process_pptx(file_path, output_folder):
"""
Convert PowerPoint to images using LibreOffice
Args:
file_path (str): Path to PPTX file
output_folder (str): Folder to save converted images
Returns:
list: List of generated image filenames
"""
try:
# Use LibreOffice to convert PPTX to PDF first
temp_dir = os.path.join(output_folder, 'temp')
os.makedirs(temp_dir, exist_ok=True)
cmd = [
'libreoffice', '--headless',
'--convert-to', 'pdf',
'--outdir', temp_dir,
file_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
# Find the generated PDF
base_name = os.path.splitext(os.path.basename(file_path))[0]
pdf_path = os.path.join(temp_dir, f"{base_name}.pdf")
if os.path.exists(pdf_path):
# Convert PDF to images
image_files = process_pdf(pdf_path, output_folder)
# Clean up
shutil.rmtree(temp_dir)
os.remove(file_path)
return image_files
return []
except Exception as e:
print(f"Error processing PPTX {file_path}: {e}")
return []
def process_uploaded_files(app, files, duration, target_type, target_id):
"""
Process uploaded files and add them to the database
Process uploaded files and add them to playlists
Args:
app: Flask application instance
files: List of uploaded files
duration (int): Duration for each file in seconds
target_type (str): 'player' or 'group'
target_id (int): ID of the target player or group
duration: Display duration in seconds
target_type: 'player' or 'group'
target_id: Target ID
Returns:
dict: Results of processing
dict: Results with success and error lists
"""
results = {'success': [], 'errors': []}
upload_folder = os.path.join(app.static_folder, 'uploads')
results = {
'success': [],
'errors': [],
'processed': 0
}
os.makedirs(upload_folder, exist_ok=True)
for file in files:
if not file or file.filename == '':
continue
try:
# Save the file
success, filename, error = save_uploaded_file(file, upload_folder)
if not success:
results['errors'].append(f"{file.filename}: {error}")
continue
file_path = os.path.join(upload_folder, filename)
file_type = get_file_type(filename)
# Process based on file type
processed_files = []
if file_type == 'image':
width, height = process_image(file_path)
processed_files = [filename]
elif file_type == 'video':
success, converted_filename, error = process_video(file_path)
if success:
processed_files = [converted_filename]
else:
results['errors'].append(f"{filename}: Video conversion failed - {error}")
continue
elif file_type == 'document':
if filename.lower().endswith('.pdf'):
processed_files = process_pdf(file_path, upload_folder)
elif filename.lower().endswith(('.pptx', '.ppt')):
processed_files = process_pptx(file_path, upload_folder)
if not processed_files:
results['errors'].append(f"{filename}: Document conversion failed")
continue
# Add processed files to database
from app.models.player import Player
if target_type == 'player':
player = Player.query.get(target_id)
if not player:
results['errors'].append(f"Player {target_id} not found")
if file and file.filename:
try:
# Secure the filename
filename = secure_filename(file.filename)
if not filename:
results['errors'].append(f"Invalid filename: {file.filename}")
continue
# Get max position for ordering
max_position = db.session.query(db.func.max(Content.position)).filter_by(player_id=target_id).scalar() or 0
# Get file extension and determine content type
file_ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
content_type = get_content_type(file_ext)
for processed_file in processed_files:
content = Content(
file_name=processed_file,
if not content_type:
results['errors'].append(f"Unsupported file type: {file_ext}")
continue
# Save file
file_path = os.path.join(upload_folder, filename)
file.save(file_path)
# Get file size
file_size = os.path.getsize(file_path)
# Process based on target type
if target_type == 'player':
success = add_content_to_player(
player_id=target_id,
filename=filename,
original_name=file.filename,
duration=duration,
player_id=target_id,
content_type=file_type,
position=max_position + 1
content_type=content_type,
file_size=file_size
)
db.session.add(content)
max_position += 1
# Update playlist version
player.increment_playlist_version()
log_content_added(file.filename, 'player', player.username)
elif target_type == 'group':
from app.models.group import Group
group = Group.query.get(target_id)
if not group:
results['errors'].append(f"Group {target_id} not found")
elif target_type == 'group':
success = add_content_to_group(
group_id=target_id,
filename=filename,
original_name=file.filename,
duration=duration,
content_type=content_type,
file_size=file_size
)
else:
results['errors'].append(f"Invalid target type: {target_type}")
continue
# Add content to all players in the group
for player in group.players:
max_position = db.session.query(db.func.max(Content.position)).filter_by(player_id=player.id).scalar() or 0
if success:
results['success'].append(filename)
log_upload(content_type, filename, target_type, str(target_id))
else:
results['errors'].append(f"Failed to add {filename} to {target_type}")
for processed_file in processed_files:
content = Content(
file_name=processed_file,
original_name=file.filename,
duration=duration,
player_id=player.id,
content_type=file_type,
position=max_position + 1
)
db.session.add(content)
max_position += 1
player.increment_playlist_version()
log_content_added(file.filename, 'group', group.name)
results['success'].append(file.filename)
results['processed'] += len(processed_files)
# Log the upload
log_upload(file_type, file.filename, target_type, target_id)
except Exception as e:
results['errors'].append(f"{file.filename}: {str(e)}")
# Commit all changes
try:
db.session.commit()
except Exception as e:
db.session.rollback()
results['errors'].append(f"Database error: {str(e)}")
except Exception as e:
results['errors'].append(f"Error processing {file.filename}: {str(e)}")
return results
def get_content_type(file_ext):
"""Determine content type from file extension"""
image_extensions = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'}
video_extensions = {'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'}
document_extensions = {'pdf', 'pptx', 'ppt'}
if file_ext in image_extensions:
return 'image'
elif file_ext in video_extensions:
return 'video'
elif file_ext in document_extensions:
return 'document'
else:
return None
def add_content_to_player(player_id, filename, original_name, duration, content_type, file_size):
"""Add content to a specific player"""
try:
player = Player.query.get(player_id)
if not player:
return False
# Get next position
max_position = db.session.query(db.func.max(Content.position)).filter_by(player_id=player_id).scalar() or 0
# Create content entry
content = Content(
file_name=filename,
original_name=original_name,
duration=duration,
position=max_position + 1,
player_id=player_id,
content_type=content_type,
file_size=file_size
)
db.session.add(content)
player.increment_playlist_version()
db.session.commit()
log_content_added(filename, 'player', player.username)
return True
except Exception as e:
db.session.rollback()
print(f"Error adding content to player: {e}")
return False
def add_content_to_group(group_id, filename, original_name, duration, content_type, file_size):
"""Add content to all players in a group"""
try:
group = Group.query.get(group_id)
if not group:
return False
# Add content to all players in the group
for player in group.players:
# Get next position for this player
max_position = db.session.query(db.func.max(Content.position)).filter_by(player_id=player.id).scalar() or 0
# Create content entry
content = Content(
file_name=filename,
original_name=original_name,
duration=duration,
position=max_position + 1,
player_id=player.id,
content_type=content_type,
file_size=file_size
)
db.session.add(content)
# Update playlist version for group
group.increment_playlist_version()
db.session.commit()
log_content_added(filename, 'group', group.name)
return True
except Exception as e:
db.session.rollback()
print(f"Error adding content to group: {e}")
return False
def allowed_file(filename, allowed_extensions=None):
"""Check if file has an allowed extension"""
if allowed_extensions is None:
allowed_extensions = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp',
'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv',
'pdf', 'pptx', 'ppt'}
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in allowed_extensions
def get_file_info(file_path):
"""Get basic file information"""
try:
stat = os.stat(file_path)
return {
'size': stat.st_size,
'modified': stat.st_mtime,
'exists': True
}
except OSError:
return {'exists': False}
def cleanup_orphaned_files(upload_folder):
"""Remove files that are not referenced in the database"""
try:
# Get all filenames from database
db_files = {content.file_name for content in Content.query.all()}
# Get all files in upload folder
if os.path.exists(upload_folder):
disk_files = set(os.listdir(upload_folder))
# Find orphaned files
orphaned = disk_files - db_files
# Remove orphaned files
removed_count = 0
for filename in orphaned:
file_path = os.path.join(upload_folder, filename)
if os.path.isfile(file_path):
try:
os.remove(file_path)
removed_count += 1
except OSError as e:
print(f"Error removing {file_path}: {e}")
return removed_count
return 0
except Exception as e:
print(f"Error during cleanup: {e}")
return 0