Files
digiserver/app/utils/uploads.py

443 lines
18 KiB
Python

import os
import subprocess
from flask import Flask
from werkzeug.utils import secure_filename
from pdf2image import convert_from_path
from extensions import db
from models import Content, Player, Group
from utils.logger import log_content_added, log_upload, log_process
# Function to add image to playlist
def add_image_to_playlist(app, file, filename, duration, target_type, target_id):
"""
Save the image file and add it to the playlist database.
"""
# Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER']
# In container, working directory is /app, so static/uploads resolves correctly
print(f"Upload folder config: {upload_folder}")
# Ensure upload folder exists
if not os.path.exists(upload_folder):
os.makedirs(upload_folder, exist_ok=True)
print(f"Created upload folder: {upload_folder}")
file_path = os.path.join(upload_folder, filename)
print(f"Saving image to: {file_path}")
# Only save if file does not already exist
if not os.path.exists(file_path):
file.save(file_path)
print(f"Image saved successfully: {file_path}")
else:
print(f"File already exists: {file_path}")
print(f"Adding image to playlist: {filename}, Target Type: {target_type}, Target ID: {target_id}")
if target_type == 'group':
group = Group.query.get_or_404(target_id)
for player in group.players:
new_content = Content(file_name=filename, duration=duration, player_id=player.id)
db.session.add(new_content)
log_content_added(filename, target_type, group.name)
elif target_type == 'player':
player = Player.query.get_or_404(target_id)
new_content = Content(file_name=filename, duration=duration, player_id=target_id)
db.session.add(new_content)
log_content_added(filename, target_type, player.username)
db.session.commit()
log_upload('image', filename, target_type, target_id)
return True
# Video conversion functions
def convert_video(input_file, output_folder):
"""
Converts a video file to MP4 format with H.264 codec.
"""
# Use simple path resolution for containerized environment
if not os.path.isabs(output_folder):
# In container, relative paths work from /app directory
print(f"Using relative path: {output_folder}")
else:
print(f"Using absolute path: {output_folder}")
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
print(f"Created output folder: {output_folder}")
# Generate the output file path
base_name = os.path.splitext(os.path.basename(input_file))[0]
output_file = os.path.join(output_folder, f"{base_name}.mp4")
print(f"Converting video: {input_file} -> {output_file}")
# FFmpeg command to convert the video
command = [
"ffmpeg",
"-i", input_file, # Input file
"-c:v", "libx264", # Video codec: H.264
"-preset", "fast", # Encoding speed/quality tradeoff
"-crf", "23", # Constant Rate Factor (quality, lower is better)
"-vf", "scale=-1:1080", # Scale video to 1080p (preserve aspect ratio)
"-r", "30", # Frame rate: 30 FPS
"-c:a", "aac", # Audio codec: AAC
"-b:a", "128k", # Audio bitrate
output_file # Output file
]
try:
# Run the FFmpeg command
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(f"Video converted successfully: {output_file}")
return output_file
except subprocess.CalledProcessError as e:
print(f"Error converting video: {e.stderr.decode()}")
return None
def convert_video_and_update_playlist(app, file_path, original_filename, target_type, target_id, duration):
"""
Converts a video and updates the playlist database.
"""
print(f"Starting video conversion for: {file_path}")
# Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER']
print(f"Upload folder: {upload_folder}")
converted_file = convert_video(file_path, upload_folder)
if converted_file:
converted_filename = os.path.basename(converted_file)
print(f"Video converted successfully: {converted_filename}")
# Use the application context to interact with the database
with app.app_context():
# Update the database with the converted filename
if target_type == 'group':
group = Group.query.get_or_404(target_id)
for player in group.players:
content = Content.query.filter_by(player_id=player.id, file_name=original_filename).first()
if content:
content.file_name = converted_filename
elif target_type == 'player':
content = Content.query.filter_by(player_id=target_id, file_name=original_filename).first()
if content:
content.file_name = converted_filename
db.session.commit()
print(f"Database updated with converted video: {converted_filename}")
# Delete the original file only if it exists
if os.path.exists(file_path):
os.remove(file_path)
print(f"Original file deleted: {file_path}")
else:
print(f"Video conversion failed for: {file_path}")
# PDF conversion functions
def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300):
"""
Convert a PDF file to high-quality JPG images in sequential order.
Uses standard 300 DPI for reliable conversion.
"""
print(f"Converting PDF to JPG images: {pdf_file} at {dpi} DPI")
print(f"Output folder: {output_folder}")
try:
# Ensure output folder exists
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
print(f"Created output folder: {output_folder}")
# Convert PDF to images using pdf2image
print("Starting PDF conversion...")
images = convert_from_path(pdf_file, dpi=dpi)
print(f"PDF converted to {len(images)} page(s)")
if not images:
print("ERROR: No images generated from PDF")
return []
base_name = os.path.splitext(os.path.basename(pdf_file))[0]
image_filenames = []
# Save each page as JPG image
for i, image in enumerate(images):
# Convert to RGB if necessary
if image.mode != 'RGB':
image = image.convert('RGB')
# Simple naming with page numbers
page_num = str(i + 1).zfill(3) # e.g., 001, 002, etc.
image_filename = f"{base_name}_page_{page_num}.jpg"
image_path = os.path.join(output_folder, image_filename)
# Save as JPG
image.save(image_path, 'JPEG', quality=85, optimize=True)
image_filenames.append(image_filename)
print(f"Saved page {i + 1} to: {image_path}")
print(f"PDF conversion complete. {len(image_filenames)} JPG images saved to {output_folder}")
# Delete the PDF file if requested and conversion was successful
if delete_pdf and os.path.exists(pdf_file) and image_filenames:
os.remove(pdf_file)
print(f"PDF file deleted: {pdf_file}")
return image_filenames
except Exception as e:
print(f"Error converting PDF to JPG images: {e}")
import traceback
traceback.print_exc()
return []
def update_playlist_with_files(image_filenames, duration, target_type, target_id):
"""
Add files to a player or group playlist and update version numbers.
Args:
image_filenames (list): List of filenames to add to playlist
duration (int): Duration in seconds for each file
target_type (str): 'player' or 'group'
target_id (int): ID of the player or group
Returns:
bool: True if successful, False otherwise
"""
try:
if target_type == 'group':
group = Group.query.get_or_404(target_id)
for player in group.players:
for image_filename in image_filenames:
new_content = Content(file_name=image_filename, duration=duration, player_id=player.id)
db.session.add(new_content)
player.playlist_version += 1
group.playlist_version += 1
elif target_type == 'player':
player = Player.query.get_or_404(target_id)
for image_filename in image_filenames:
new_content = Content(file_name=image_filename, duration=duration, player_id=target_id)
db.session.add(new_content)
player.playlist_version += 1
else:
print(f"Invalid target type: {target_type}")
return False
db.session.commit()
print(f"Added {len(image_filenames)} files to playlist")
return True
except Exception as e:
db.session.rollback()
print(f"Error updating playlist: {e}")
return False
def process_pdf(input_file, output_folder, duration, target_type, target_id):
"""
Process a PDF file: convert to images and update playlist.
Args:
input_file (str): Path to the PDF file
output_folder (str): Path to save the images
duration (int): Duration in seconds for each image
target_type (str): 'player' or 'group'
target_id (int): ID of the player or group
Returns:
bool: True if successful, False otherwise
"""
print(f"Processing PDF file: {input_file}")
print(f"Output folder: {output_folder}")
# Ensure output folder exists
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
print(f"Created output folder: {output_folder}")
# Convert PDF to images using standard quality (delete PDF after successful conversion)
image_filenames = convert_pdf_to_images(input_file, output_folder, delete_pdf=True, dpi=300)
# Update playlist with generated images
if image_filenames:
success = update_playlist_with_files(image_filenames, duration, target_type, target_id)
if success:
print(f"Successfully processed PDF: {len(image_filenames)} images added to playlist")
return success
else:
print("Failed to convert PDF to images")
return False
def process_pptx(input_file, output_folder, duration, target_type, target_id):
"""
Process a PPTX file: convert to PDF first, then to JPG images (same workflow as PDF).
Args:
input_file (str): Path to the PPTX file
output_folder (str): Path to save the images
duration (int): Duration in seconds for each image
target_type (str): 'player' or 'group'
target_id (int): ID of the player or group
Returns:
bool: True if successful, False otherwise
"""
print(f"Processing PPTX file using PDF workflow: {input_file}")
print(f"Output folder: {output_folder}")
# Ensure output folder exists
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
print(f"Created output folder: {output_folder}")
try:
# Step 1: Convert PPTX to PDF using LibreOffice for vector quality
print("Step 1: Converting PPTX to PDF...")
from utils.pptx_converter import pptx_to_pdf_libreoffice
pdf_file = pptx_to_pdf_libreoffice(input_file, output_folder)
if not pdf_file:
print("Error: Failed to convert PPTX to PDF")
print("This could be due to:")
print("- LibreOffice not properly installed")
print("- Corrupted PPTX file")
print("- Insufficient memory")
print("- File permission issues")
return False
print(f"PPTX successfully converted to PDF: {pdf_file}")
# Step 2: Use the same PDF to images workflow as direct PDF uploads
print("Step 2: Converting PDF to JPG images...")
# Convert PDF to JPG images (300 DPI, same as PDF workflow)
image_filenames = convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300)
if not image_filenames:
print("Error: Failed to convert PDF to images")
print("This could be due to:")
print("- poppler-utils not properly installed")
print("- PDF corruption during conversion")
print("- Insufficient disk space")
print("- Memory issues during image processing")
return False
print(f"Generated {len(image_filenames)} JPG images from PPTX → PDF")
# Step 3: Delete the original PPTX file after successful conversion
if os.path.exists(input_file):
os.remove(input_file)
print(f"Original PPTX file deleted: {input_file}")
# Step 4: Update playlist with generated images in sequential order
print("Step 3: Adding images to playlist...")
success = update_playlist_with_files(image_filenames, duration, target_type, target_id)
if success:
print(f"Successfully processed PPTX: {len(image_filenames)} images added to playlist")
else:
print("Error: Failed to add images to playlist database")
return success
except Exception as e:
print(f"Error processing PPTX file: {e}")
import traceback
traceback.print_exc()
return False
def process_uploaded_files(app, files, media_type, duration, target_type, target_id):
"""
Process uploaded files based on media type and add them to playlists.
Returns:
list: List of result dictionaries with success status and messages
"""
results = []
# Get target name for logging
target_name = ""
if target_type == 'group':
group = Group.query.get_or_404(target_id)
target_name = group.name
elif target_type == 'player':
player = Player.query.get_or_404(target_id)
target_name = player.username
for file in files:
try:
# Generate a secure filename and save the file
filename = secure_filename(file.filename)
# Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER']
print(f"Upload folder: {upload_folder}")
# Ensure upload folder exists
if not os.path.exists(upload_folder):
os.makedirs(upload_folder, exist_ok=True)
print(f"Created upload folder: {upload_folder}")
file_path = os.path.join(upload_folder, filename)
file.save(file_path)
print(f"File saved to: {file_path}")
print(f"Processing file: {filename}, Media Type: {media_type}")
result = {'filename': filename, 'success': True, 'message': ''}
if media_type == 'image':
add_image_to_playlist(app, file, filename, duration, target_type, target_id)
result['message'] = f"Image {filename} added to playlist"
log_upload('image', filename, target_type, target_id)
elif media_type == 'video':
# For videos, add to playlist then start conversion in background
if target_type == 'group':
group = Group.query.get_or_404(target_id)
for player in group.players:
new_content = Content(file_name=filename, duration=duration, player_id=player.id)
db.session.add(new_content)
player.playlist_version += 1
group.playlist_version += 1
elif target_type == 'player':
player = Player.query.get_or_404(target_id)
new_content = Content(file_name=filename, duration=duration, player_id=target_id)
db.session.add(new_content)
player.playlist_version += 1
db.session.commit()
# Start background conversion using absolute path
import threading
threading.Thread(target=convert_video_and_update_playlist,
args=(app, file_path, filename, target_type, target_id, duration)).start()
result['message'] = f"Video {filename} added to playlist and being processed"
log_upload('video', filename, target_type, target_id)
elif media_type == 'pdf':
# For PDFs, convert to images and update playlist using absolute path
success = process_pdf(file_path, upload_folder,
duration, target_type, target_id)
if success:
result['message'] = f"PDF {filename} processed successfully"
log_process('pdf', filename, target_type, target_id)
else:
result['success'] = False
result['message'] = f"Error processing PDF file: {filename}"
elif media_type == 'ppt':
# For PPT/PPTX, convert to PDF, then to images, and update playlist using absolute path
success = process_pptx(file_path, upload_folder,
duration, target_type, target_id)
if success:
result['message'] = f"PowerPoint {filename} processed successfully"
log_process('ppt', filename, target_type, target_id)
else:
result['success'] = False
result['message'] = f"Error processing PowerPoint file: {filename}"
results.append(result)
except Exception as e:
print(f"Error processing file {file.filename}: {e}")
results.append({
'filename': file.filename,
'success': False,
'message': f"Error processing file {file.filename}: {str(e)}"
})
return results