Compare commits

...

5 Commits

Author SHA1 Message Date
DigiServer Developer
7b24245ddb updated the upload functionality to handle large files and added a new image file 2025-08-21 16:27:16 +03:00
DigiServer Developer
58694ff3f4 Update all changes before rebase and push 2025-08-21 16:26:53 +03:00
7f5991f60d fix: use correct endpoint for group media delete in manage_group.html 2025-08-20 15:11:22 -04:00
DigiServer Developer
5e4950563c Fix admin authentication and update port mapping
- Fix environment variable mismatch in create_default_user.py
- Now correctly uses ADMIN_USER and ADMIN_PASSWORD from docker-compose
- Maintains backward compatibility with DEFAULT_USER and DEFAULT_PASSWORD
- Change port mapping from 8880 to 80 for easier access
- Resolves login issues with admin user credentials
2025-08-11 17:01:58 +03:00
091e985ff2 fix: Simplified Docker deployment and fixed upload path resolution
🐳 Docker Configuration Improvements:
- Simplified docker-compose.yml to use single app folder bind mount
- Removed complex data folder mapping that caused path confusion
- Updated environment variables to match entrypoint script expectations
- Streamlined deployment for better reliability

🔧 Upload System Fixes:
- Fixed path resolution issues in uploads.py for containerized deployment
- Simplified upload folder path handling to work correctly in containers
- Removed complex absolute path conversion logic that caused file placement issues
- Ensured all file operations use consistent /app/static/uploads path

📁 File Processing Improvements:
- Fixed PPTX to JPG conversion workflow path handling
- Corrected PDF processing to save files in correct container location
- Improved video conversion path resolution
- Enhanced error handling and logging for upload operations

🚀 Production Benefits:
- Eliminates 404 errors for uploaded media files
- Ensures files are saved in correct locations within container
- Simplifies development and debugging with direct app folder mounting
- Maintains data consistency across container restarts

 This resolves the upload workflow issues where PPTX files were not
being correctly processed and saved to the expected locations.
2025-08-05 19:16:08 -04:00
11 changed files with 122 additions and 68 deletions

View File

@@ -69,8 +69,12 @@ db_path = os.path.join(instance_dir, 'dashboard.db')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Set maximum content length to 1GB
app.config['MAX_CONTENT_LENGTH'] = 2048 * 2048 * 2048 # 2GB, adjust as needed
# Set maximum content length to 2GB
app.config['MAX_CONTENT_LENGTH'] = 2048 * 1024 * 1024 # 2GB, adjust as needed
# Set longer timeouts for file processing
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 300 # 5 minutes for static files
app.config['PERMANENT_SESSION_LIFETIME'] = 1800 # 30 minutes for sessions
# Ensure the instance folder exists
os.makedirs(app.instance_path, exist_ok=True)
@@ -95,6 +99,22 @@ login_manager.login_view = 'login'
migrate = Migrate(app, db)
# Add error handlers for better user experience
@app.errorhandler(413)
def request_entity_too_large(error):
flash('File too large. Please upload files smaller than 2GB.', 'danger')
return redirect(url_for('dashboard'))
@app.errorhandler(408)
def request_timeout(error):
flash('Request timed out. Please try uploading smaller files or try again later.', 'danger')
return redirect(url_for('dashboard'))
@app.errorhandler(500)
def internal_server_error(error):
flash('An internal server error occurred. Please try again or contact support.', 'danger')
return redirect(url_for('dashboard'))
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))
@@ -213,8 +233,23 @@ def upload_content():
flash('Please select a target type and target ID.', 'danger')
return redirect(url_for('upload_content'))
# Process uploaded files and get results
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id)
try:
# Process uploaded files and get results
results = process_uploaded_files(app, files, media_type, duration, target_type, target_id)
# Check for any failed uploads
failed_files = [r for r in results if not r.get('success', True)]
if failed_files:
for failed in failed_files:
flash(f"Error uploading {failed.get('filename', 'unknown file')}: {failed.get('message', 'Unknown error')}", 'warning')
else:
flash('All files uploaded and processed successfully!', 'success')
except Exception as e:
print(f"Error in upload_content: {e}")
import traceback
traceback.print_exc()
flash(f'Upload failed: {str(e)}', 'danger')
return redirect(return_url)

View File

@@ -2,8 +2,9 @@
import os
def create_default_user(db, User, bcrypt):
username = os.getenv('DEFAULT_USER', 'admin')
password = os.getenv('DEFAULT_PASSWORD', '1234')
# Use ADMIN_USER and ADMIN_PASSWORD to match docker-compose environment variables
username = os.getenv('ADMIN_USER', os.getenv('DEFAULT_USER', 'admin'))
password = os.getenv('ADMIN_PASSWORD', os.getenv('DEFAULT_PASSWORD', '1234'))
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
existing_user = User.query.filter_by(username=username).first()
if not existing_user:

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -110,35 +110,44 @@
<ul class="list-group sortable-list" id="groupMediaList">
{% for media in content %}
<li class="list-group-item d-flex align-items-center {{ 'dark-mode' if theme == 'dark' else '' }}"
<li class="list-group-item d-flex align-items-center {{ 'dark-mode' if theme == 'dark' else '' }}"
draggable="true"
data-id="{{ media.id }}"
data-position="{{ loop.index0 }}">
<!-- Checkbox for bulk selection -->
<div class="me-2">
<input class="form-check-input media-checkbox"
type="checkbox"
name="selected_content"
<input class="form-check-input media-checkbox"
type="checkbox"
name="selected_content"
value="{{ media.id }}">
</div>
<!-- Drag handle -->
<div class="drag-handle me-2" title="Drag to reorder">
<i class="bi bi-grip-vertical"></i>
&#9776;
</div>
<div class="flex-grow-1">
<!-- Media Thumbnail and Name -->
<div class="flex-grow-1 mb-2 mb-md-0 d-flex align-items-center">
<img src="{{ url_for('static', filename='uploads/' ~ media.file_name) }}"
alt="thumbnail"
style="width: 48px; height: 48px; object-fit: cover; margin-right: 10px; border-radius: 4px;"
onerror="this.style.display='none';">
<p class="mb-0"><strong>Media Name:</strong> {{ media.file_name }}</p>
</div>
<form action="{{ url_for('edit_group_media', group_id=group.id, content_id=media.id) }}" method="post" class="d-flex align-items-center">
<<<<<<< HEAD
=======
>>>>>>> 2255cc2 (Show media thumbnails in manage group page, matching player page style)
<form action="{{ url_for('edit_group_media_route', group_id=group.id, content_id=media.id) }}" method="post" class="d-flex align-items-center">
<div class="input-group me-2">
<span class="input-group-text">seconds</span>
<input type="number" class="form-control {{ 'dark-mode' if theme == 'dark' else '' }}" name="duration" value="{{ media.duration }}" required>
</div>
<button type="submit" class="btn btn-warning me-2">Edit</button>
</form>
<form action="{{ url_for('delete_group_media', group_id=group.id, content_id=media.id) }}" method="post" style="display:inline;">
<form action="{{ url_for('delete_group_media_route', group_id=group.id, content_id=media.id) }}" method="post" style="display:inline;">
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this media?');">Delete</button>
</form>
</li>

View File

@@ -243,7 +243,7 @@
statusMessage.textContent = 'Converting PDF to 4K images. This may take a while...';
break;
case 'ppt':
statusMessage.textContent = 'Converting PowerPoint to 4K images. This may take a while...';
statusMessage.textContent = 'Converting PowerPoint to images (PPTX → PDF → Images). This may take 2-5 minutes...';
break;
default:
statusMessage.textContent = 'Uploading and processing your files. Please wait...';

View File

@@ -9,12 +9,23 @@ The converted PDF is then processed by the main upload workflow for 4K image gen
import os
import subprocess
import logging
import signal
import time
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def cleanup_libreoffice_processes():
"""Clean up any hanging LibreOffice processes"""
try:
subprocess.run(['pkill', '-f', 'soffice'], capture_output=True, timeout=10)
time.sleep(1) # Give processes time to terminate
except Exception as e:
logger.warning(f"Failed to cleanup LibreOffice processes: {e}")
def pptx_to_pdf_libreoffice(pptx_path, output_dir):
"""
Convert PPTX to PDF using LibreOffice for highest quality.
@@ -30,6 +41,9 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir):
str: Path to the generated PDF file, or None if conversion failed
"""
try:
# Clean up any existing LibreOffice processes
cleanup_libreoffice_processes()
# Ensure output directory exists
os.makedirs(output_dir, exist_ok=True)
@@ -39,14 +53,19 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir):
'--headless',
'--convert-to', 'pdf',
'--outdir', output_dir,
'--invisible', # Run without any UI
'--nodefault', # Don't start with default template
pptx_path
]
logger.info(f"Converting PPTX to PDF using LibreOffice: {pptx_path}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
# Increase timeout to 300 seconds (5 minutes) for large presentations
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode != 0:
logger.error(f"LibreOffice conversion failed: {result.stderr}")
logger.error(f"LibreOffice stdout: {result.stdout}")
cleanup_libreoffice_processes() # Clean up on failure
return None
# Find the generated PDF file
@@ -55,16 +74,22 @@ def pptx_to_pdf_libreoffice(pptx_path, output_dir):
if os.path.exists(pdf_path):
logger.info(f"PDF conversion successful: {pdf_path}")
cleanup_libreoffice_processes() # Clean up after success
return pdf_path
else:
logger.error(f"PDF file not found after conversion: {pdf_path}")
cleanup_libreoffice_processes() # Clean up on failure
return None
except subprocess.TimeoutExpired:
logger.error("LibreOffice conversion timed out (120s)")
logger.error("LibreOffice conversion timed out (300s)")
cleanup_libreoffice_processes() # Clean up on timeout
return None
except Exception as e:
logger.error(f"Error in PPTX to PDF conversion: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
cleanup_libreoffice_processes() # Clean up on error
return None

View File

@@ -12,10 +12,10 @@ def add_image_to_playlist(app, file, filename, duration, target_type, target_id)
"""
Save the image file and add it to the playlist database.
"""
# Ensure we use absolute path for upload folder
# Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER']
if not os.path.isabs(upload_folder):
upload_folder = os.path.abspath(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):
@@ -55,10 +55,12 @@ def convert_video(input_file, output_folder):
"""
Converts a video file to MP4 format with H.264 codec.
"""
# Ensure we use absolute path for output folder
# Use simple path resolution for containerized environment
if not os.path.isabs(output_folder):
output_folder = os.path.abspath(output_folder)
print(f"Converted output folder to absolute path: {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)
@@ -98,11 +100,9 @@ def convert_video_and_update_playlist(app, file_path, original_filename, target_
"""
print(f"Starting video conversion for: {file_path}")
# Ensure we use absolute path for upload folder
# Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER']
if not os.path.isabs(upload_folder):
upload_folder = os.path.abspath(upload_folder)
print(f"Converted upload folder to absolute path: {upload_folder}")
print(f"Upload folder: {upload_folder}")
converted_file = convert_video(file_path, upload_folder)
if converted_file:
@@ -140,25 +140,7 @@ def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=300):
Uses standard 300 DPI for reliable conversion.
"""
print(f"Converting PDF to JPG images: {pdf_file} at {dpi} DPI")
print(f"Original output folder: {output_folder}")
# Force absolute path resolution to ensure we use the app directory
if not os.path.isabs(output_folder):
# If relative path, resolve from the current working directory
output_folder = os.path.abspath(output_folder)
print(f"Converted relative path to absolute: {output_folder}")
else:
print(f"Using provided absolute path: {output_folder}")
# Ensure we're using the app static folder, not workspace root
if output_folder.endswith('static/uploads'):
# Check if we're accidentally using workspace root instead of app folder
expected_app_path = '/opt/digiserver/app/static/uploads'
if output_folder != expected_app_path:
print(f"WARNING: Correcting path from {output_folder} to {expected_app_path}")
output_folder = expected_app_path
print(f"Final output folder: {output_folder}")
print(f"Output folder: {output_folder}")
try:
# Ensure output folder exists
@@ -266,11 +248,6 @@ def process_pdf(input_file, output_folder, duration, target_type, target_id):
print(f"Processing PDF file: {input_file}")
print(f"Output folder: {output_folder}")
# Ensure we have absolute path for output folder
if not os.path.isabs(output_folder):
output_folder = os.path.abspath(output_folder)
print(f"Converted output folder to absolute path: {output_folder}")
# Ensure output folder exists
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
@@ -306,11 +283,6 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
print(f"Processing PPTX file using PDF workflow: {input_file}")
print(f"Output folder: {output_folder}")
# Ensure we have absolute path for output folder
if not os.path.isabs(output_folder):
output_folder = os.path.abspath(output_folder)
print(f"Converted output folder to absolute path: {output_folder}")
# Ensure output folder exists
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
@@ -318,21 +290,33 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
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")
@@ -341,11 +325,14 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
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:
@@ -377,10 +364,9 @@ def process_uploaded_files(app, files, media_type, duration, target_type, target
# Generate a secure filename and save the file
filename = secure_filename(file.filename)
# Ensure we use absolute path for upload folder
# Use simple path resolution for containerized environment
upload_folder = app.config['UPLOAD_FOLDER']
if not os.path.isabs(upload_folder):
upload_folder = os.path.abspath(upload_folder)
print(f"Upload folder: {upload_folder}")
# Ensure upload folder exists
if not os.path.exists(upload_folder):

View File

@@ -8,18 +8,16 @@ services:
image: digiserver:latest
container_name: digiserver
ports:
- "8880:5000"
- "80:5000"
environment:
- FLASK_APP=app.py
- FLASK_RUN_HOST=0.0.0.0
- DEFAULT_USER=admin
- DEFAULT_PASSWORD=Initial01!
- ADMIN_USER=admin
- ADMIN_PASSWORD=Initial01!
- SECRET_KEY=Ma_Duc_Dupa_Merele_Lui_Ana
volumes:
# Persistent data volumes
- ./data/instance:/app/instance
- ./data/uploads:/app/static/uploads
- ./data/resurse:/app/static/resurse
# Bind mount the app folder for easier development and debugging
- ./app:/app
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/"]