updated to 4k images from pptx
This commit is contained in:
19
app.py
19
app.py
@@ -327,7 +327,8 @@ def edit_player(player_id):
|
|||||||
hostname = request.form['hostname']
|
hostname = request.form['hostname']
|
||||||
password = request.form['password'] if request.form['password'] else None
|
password = request.form['password'] if request.form['password'] else None
|
||||||
quickconnect_password = request.form['quickconnect_password'] if request.form['quickconnect_password'] else None
|
quickconnect_password = request.form['quickconnect_password'] if request.form['quickconnect_password'] else None
|
||||||
edit_player_util(player_id, username, hostname, password, quickconnect_password)
|
orientation = request.form.get('orientation', player.orientation) # <-- Get orientation
|
||||||
|
edit_player_util(player_id, username, hostname, password, quickconnect_password, orientation) # <-- Pass orientation
|
||||||
flash(f'Player "{username}" updated successfully.', 'success')
|
flash(f'Player "{username}" updated successfully.', 'success')
|
||||||
return redirect(url_for('player_page', player_id=player.id))
|
return redirect(url_for('player_page', player_id=player.id))
|
||||||
|
|
||||||
@@ -648,14 +649,14 @@ def create_admin(username, password):
|
|||||||
|
|
||||||
from models.create_default_user import create_default_user
|
from models.create_default_user import create_default_user
|
||||||
|
|
||||||
with app.app_context():
|
if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||||
try:
|
with app.app_context():
|
||||||
db.session.execute(db.select(User).limit(1))
|
try:
|
||||||
except Exception as e:
|
db.session.execute(db.select(User).limit(1))
|
||||||
print("Database not initialized or missing tables. Re-initializing...")
|
except Exception as e:
|
||||||
db.create_all()
|
print("Database not initialized or missing tables. Re-initializing...")
|
||||||
# Always ensure default user exists
|
db.create_all()
|
||||||
create_default_user(db, User, bcrypt)
|
create_default_user(db, User, bcrypt)
|
||||||
|
|
||||||
# Add this at the end of app.py
|
# Add this at the end of app.py
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Binary file not shown.
@@ -49,11 +49,23 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="orientation" class="form-label">Group Orientation</label>
|
||||||
|
<select class="form-control {{ 'dark-mode' if theme == 'dark' else '' }}" id="orientation" name="orientation" required>
|
||||||
|
<option value="Landscape" selected>Landscape</option>
|
||||||
|
<option value="Portret">Portret</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
<strong>Warning:</strong> Adding players to a group will delete their individual playlists.
|
<strong>Warning:</strong> Adding players to a group will delete their individual playlists.
|
||||||
All players in a group will share the same content.
|
All players in a group will share the same content.
|
||||||
</div>
|
</div>
|
||||||
|
<div id="orientation-warning" class="alert alert-danger d-none" role="alert">
|
||||||
|
No players with the selected orientation are available.
|
||||||
|
</div>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button type="submit" class="btn btn-primary">Create Group</button>
|
<button type="submit" class="btn btn-primary">Create Group</button>
|
||||||
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary mt-3">Back to Dashboard</a>
|
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary mt-3">Back to Dashboard</a>
|
||||||
@@ -61,5 +73,38 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// Get all players and their orientations from the backend
|
||||||
|
const players = [
|
||||||
|
{% for player in players %}
|
||||||
|
{id: {{ player.id }}, username: "{{ player.username }}", orientation: "{{ player.orientation }}"},
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
|
||||||
|
const orientationSelect = document.getElementById('orientation');
|
||||||
|
const playersSelect = document.getElementById('players');
|
||||||
|
const orientationWarning = document.getElementById('orientation-warning');
|
||||||
|
|
||||||
|
function filterPlayers() {
|
||||||
|
const selectedOrientation = orientationSelect.value;
|
||||||
|
playersSelect.innerHTML = '';
|
||||||
|
let compatibleCount = 0;
|
||||||
|
players.forEach(player => {
|
||||||
|
if (player.orientation === selectedOrientation) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = player.id;
|
||||||
|
option.textContent = player.username;
|
||||||
|
playersSelect.appendChild(option);
|
||||||
|
compatibleCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById('orientation-warning').classList.toggle('d-none', compatibleCount > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
orientationSelect.addEventListener('change', filterPlayers);
|
||||||
|
|
||||||
|
// Initial filter on page load
|
||||||
|
filterPlayers();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -60,6 +60,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="orientation" class="form-label">Orientation</label>
|
||||||
|
<select class="form-control {{ 'dark-mode' if theme == 'dark' else '' }}" id="orientation" name="orientation" required>
|
||||||
|
<option value="Landscape" {% if player.orientation == 'Landscape' %}selected{% endif %}>Landscape</option>
|
||||||
|
<option value="Portret" {% if player.orientation == 'Portret' %}selected{% endif %}>Portret</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button type="submit" class="btn btn-primary">Update Player</button>
|
<button type="submit" class="btn btn-primary">Update Player</button>
|
||||||
<a href="{{ return_url }}" class="btn btn-secondary mt-3">Back to Player Page</a>
|
<a href="{{ return_url }}" class="btn btn-secondary mt-3">Back to Player Page</a>
|
||||||
|
|||||||
@@ -106,8 +106,12 @@
|
|||||||
☰
|
☰
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Media Name -->
|
<!-- Media Thumbnail and Name -->
|
||||||
<div class="flex-grow-1 mb-2 mb-md-0">
|
<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>
|
<p class="mb-0"><strong>Media Name:</strong> {{ media.file_name }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -130,7 +130,7 @@ def add_player(username, hostname, password, quickconnect_password, orientation=
|
|||||||
log_player_created(username, hostname)
|
log_player_created(username, hostname)
|
||||||
return new_player
|
return new_player
|
||||||
|
|
||||||
def edit_player(player_id, username, hostname, password=None, quickconnect_password=None):
|
def edit_player(player_id, username, hostname, password=None, quickconnect_password=None, orientation=None):
|
||||||
"""
|
"""
|
||||||
Edit an existing player's details.
|
Edit an existing player's details.
|
||||||
"""
|
"""
|
||||||
@@ -147,6 +147,9 @@ def edit_player(player_id, username, hostname, password=None, quickconnect_passw
|
|||||||
if quickconnect_password:
|
if quickconnect_password:
|
||||||
player.quickconnect_password = bcrypt.generate_password_hash(quickconnect_password).decode('utf-8')
|
player.quickconnect_password = bcrypt.generate_password_hash(quickconnect_password).decode('utf-8')
|
||||||
|
|
||||||
|
if orientation:
|
||||||
|
player.orientation = orientation
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
log_player_edited(username)
|
log_player_edited(username)
|
||||||
return player
|
return player
|
||||||
|
|||||||
@@ -105,23 +105,14 @@ def convert_video_and_update_playlist(app, file_path, original_filename, target_
|
|||||||
print(f"Video conversion failed for: {file_path}")
|
print(f"Video conversion failed for: {file_path}")
|
||||||
|
|
||||||
# PDF conversion functions
|
# PDF conversion functions
|
||||||
def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True):
|
def convert_pdf_to_images(pdf_file, output_folder, delete_pdf=True, dpi=600):
|
||||||
"""
|
"""
|
||||||
Convert a PDF file to images in sequential order.
|
Convert a PDF file to images in sequential order at high resolution (4K).
|
||||||
|
|
||||||
Args:
|
|
||||||
pdf_file (str): Path to the PDF file
|
|
||||||
output_folder (str): Path to save the images
|
|
||||||
delete_pdf (bool): Whether to delete the PDF file after processing
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: List of generated image filenames in page order, or empty list if conversion failed
|
|
||||||
"""
|
"""
|
||||||
print(f"Converting PDF to images: {pdf_file}")
|
print(f"Converting PDF to images: {pdf_file} at {dpi} DPI")
|
||||||
try:
|
try:
|
||||||
# Convert PDF to images
|
# Convert PDF to images
|
||||||
images = convert_from_path(pdf_file, dpi=300)
|
images = convert_from_path(pdf_file, dpi=dpi)
|
||||||
print(f"Number of pages in PDF: {len(images)}")
|
|
||||||
base_name = os.path.splitext(os.path.basename(pdf_file))[0]
|
base_name = os.path.splitext(os.path.basename(pdf_file))[0]
|
||||||
image_filenames = []
|
image_filenames = []
|
||||||
|
|
||||||
@@ -240,6 +231,7 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
|
|||||||
'--headless',
|
'--headless',
|
||||||
'--convert-to', 'pdf',
|
'--convert-to', 'pdf',
|
||||||
'--outdir', output_folder,
|
'--outdir', output_folder,
|
||||||
|
'--printer-resolution', '600',
|
||||||
input_file
|
input_file
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -251,7 +243,7 @@ def process_pptx(input_file, output_folder, duration, target_type, target_id):
|
|||||||
print(f"LibreOffice errors (if any): {result.stderr.decode()}")
|
print(f"LibreOffice errors (if any): {result.stderr.decode()}")
|
||||||
|
|
||||||
# Step 2: Convert PDF to images and update playlist
|
# Step 2: Convert PDF to images and update playlist
|
||||||
image_filenames = convert_pdf_to_images(pdf_file, output_folder, True)
|
image_filenames = convert_pdf_to_images(pdf_file, output_folder, True, dpi=600)
|
||||||
|
|
||||||
# Verify we got images
|
# Verify we got images
|
||||||
if not image_filenames:
|
if not image_filenames:
|
||||||
@@ -306,7 +298,7 @@ def process_uploaded_files(app, files, media_type, duration, target_type, target
|
|||||||
if media_type == 'image':
|
if media_type == 'image':
|
||||||
add_image_to_playlist(app, file, filename, duration, target_type, target_id)
|
add_image_to_playlist(app, file, filename, duration, target_type, target_id)
|
||||||
result['message'] = f"Image {filename} added to playlist"
|
result['message'] = f"Image {filename} added to playlist"
|
||||||
log_upload('image', filename, target_type, target_name)
|
log_upload('image', filename, target_type, target_id)
|
||||||
|
|
||||||
elif media_type == 'video':
|
elif media_type == 'video':
|
||||||
# For videos, add to playlist then start conversion in background
|
# For videos, add to playlist then start conversion in background
|
||||||
@@ -329,7 +321,7 @@ def process_uploaded_files(app, files, media_type, duration, target_type, target
|
|||||||
threading.Thread(target=convert_video_and_update_playlist,
|
threading.Thread(target=convert_video_and_update_playlist,
|
||||||
args=(app, file_path, filename, target_type, target_id, duration)).start()
|
args=(app, file_path, filename, target_type, target_id, duration)).start()
|
||||||
result['message'] = f"Video {filename} added to playlist and being processed"
|
result['message'] = f"Video {filename} added to playlist and being processed"
|
||||||
log_upload('video', filename, target_type, target_name)
|
log_upload('video', filename, target_type, target_id)
|
||||||
|
|
||||||
elif media_type == 'pdf':
|
elif media_type == 'pdf':
|
||||||
# For PDFs, convert to images and update playlist
|
# For PDFs, convert to images and update playlist
|
||||||
@@ -337,7 +329,7 @@ def process_uploaded_files(app, files, media_type, duration, target_type, target
|
|||||||
duration, target_type, target_id)
|
duration, target_type, target_id)
|
||||||
if success:
|
if success:
|
||||||
result['message'] = f"PDF {filename} processed successfully"
|
result['message'] = f"PDF {filename} processed successfully"
|
||||||
log_process('pdf', filename, target_type, target_name)
|
log_process('pdf', filename, target_type, target_id)
|
||||||
else:
|
else:
|
||||||
result['success'] = False
|
result['success'] = False
|
||||||
result['message'] = f"Error processing PDF file: {filename}"
|
result['message'] = f"Error processing PDF file: {filename}"
|
||||||
@@ -348,7 +340,7 @@ def process_uploaded_files(app, files, media_type, duration, target_type, target
|
|||||||
duration, target_type, target_id)
|
duration, target_type, target_id)
|
||||||
if success:
|
if success:
|
||||||
result['message'] = f"PowerPoint {filename} processed successfully"
|
result['message'] = f"PowerPoint {filename} processed successfully"
|
||||||
log_process('ppt', filename, target_type, target_name)
|
log_process('ppt', filename, target_type, target_id)
|
||||||
else:
|
else:
|
||||||
result['success'] = False
|
result['success'] = False
|
||||||
result['message'] = f"Error processing PowerPoint file: {filename}"
|
result['message'] = f"Error processing PowerPoint file: {filename}"
|
||||||
|
|||||||
Reference in New Issue
Block a user