383 lines
12 KiB
Python
383 lines
12 KiB
Python
"""
|
|
File upload processing utilities
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import shutil
|
|
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.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
|
|
|
|
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
|
|
|
|
Returns:
|
|
dict: Results of processing
|
|
"""
|
|
upload_folder = os.path.join(app.static_folder, 'uploads')
|
|
results = {
|
|
'success': [],
|
|
'errors': [],
|
|
'processed': 0
|
|
}
|
|
|
|
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")
|
|
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
|
|
|
|
for processed_file in processed_files:
|
|
content = Content(
|
|
file_name=processed_file,
|
|
original_name=file.filename,
|
|
duration=duration,
|
|
player_id=target_id,
|
|
content_type=file_type,
|
|
position=max_position + 1
|
|
)
|
|
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")
|
|
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
|
|
|
|
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)}")
|
|
|
|
return results
|