updates to digiserver app server

This commit is contained in:
2026-09-10 21:03:16 +03:00
parent 046f5e5efd
commit 1c5186463a
129 changed files with 33014 additions and 17 deletions
+18
View File
@@ -837,6 +837,24 @@ def receive_edited_media():
with open(metadata_path, 'w') as f:
json.dump(metadata, f, indent=2)
# ── Preserve the original filename ──────────────────────────────
# `content.filename` is (re)pointed at the latest edit below so the
# player downloads the newest version. The pristine original must be
# remembered separately, otherwise the original is lost forever and
# the UI shows the edited image as the "original".
if not content.original_filename:
if not content.filename.startswith('edited_media/'):
# Normal case: current filename is still the pristine upload.
content.original_filename = content.filename
else:
# Re-process: content.filename already points at an edited
# file. Recover the pristine original from the first (v1) edit.
first_edit = PlayerEdit.query.filter_by(content_id=content.id)\
.order_by(PlayerEdit.version.asc(), PlayerEdit.created_at.asc())\
.first()
if first_edit and first_edit.original_name:
content.original_filename = first_edit.original_name
# ── Point Content.filename to the latest edit ────────────────────
# This tells the player to download the latest edited version.
old_filename = content.filename
+6
View File
@@ -829,6 +829,7 @@ def process_file_in_background(app, filepath: str, filename: str, file_ext: str,
page_filename = os.path.basename(page_file)
page_content = Content(
filename=page_filename,
original_filename=page_filename,
content_type='image',
duration=duration,
file_size=os.path.getsize(page_file)
@@ -886,6 +887,7 @@ def process_file_in_background(app, filepath: str, filename: str, file_ext: str,
slide_filename = os.path.basename(slide_file)
slide_content = Content(
filename=slide_filename,
original_filename=slide_filename,
content_type='image',
duration=duration,
file_size=os.path.getsize(slide_file)
@@ -930,6 +932,7 @@ def process_file_in_background(app, filepath: str, filename: str, file_ext: str,
if processing_success and os.path.exists(filepath):
content = Content(
filename=filename,
original_filename=filename,
content_type=detected_type,
duration=duration,
file_size=os.path.getsize(filepath)
@@ -1313,6 +1316,7 @@ def upload_media():
# Create content record for page
page_content = Content(
filename=page_filename,
original_filename=page_filename,
content_type='image',
duration=duration,
file_size=os.path.getsize(page_file)
@@ -1373,6 +1377,7 @@ def upload_media():
# Create content record for slide
slide_content = Content(
filename=slide_filename,
original_filename=slide_filename,
content_type='image',
duration=duration,
file_size=os.path.getsize(slide_file)
@@ -1419,6 +1424,7 @@ def upload_media():
if os.path.exists(filepath):
content = Content(
filename=filename,
original_filename=filename,
content_type=detected_type,
duration=duration,
file_size=os.path.getsize(filepath)
+48
View File
@@ -21,6 +21,10 @@ class Content(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False, unique=True, index=True)
# The pristine original filename as uploaded. `filename` gets overwritten
# with an edited_media/... path when a player edits the content, so this
# column preserves the original so it can always be referenced/restored.
original_filename = db.Column(db.String(255), nullable=True, index=True)
content_type = db.Column(db.String(50), nullable=False, index=True)
duration = db.Column(db.Integer, default=10, nullable=True)
file_size = db.Column(db.BigInteger, nullable=True)
@@ -52,6 +56,28 @@ class Content(db.Model):
def group_count(self) -> int:
"""Get number of groups containing this content."""
return self.groups.count()
@property
def original_display_name(self) -> str:
"""Name of the original (unedited) file for display purposes."""
return self.original_filename or self.filename
@property
def original_media_path(self) -> str:
"""Path (relative to uploads/) where the original unedited file lives.
When a player edits content the original file is archived under
edited_media/<id>/original_<name>. If never edited, the original IS the
current file.
"""
if self.original_filename and self.original_filename != self.filename:
return f"edited_media/{self.id}/original_{self.original_filename}"
return self.filename
@property
def current_media_path(self) -> str:
"""Path (relative to uploads/) of the current/latest version to display."""
return self.filename
def is_image(self) -> bool:
"""Check if content is an image."""
@@ -68,3 +94,25 @@ class Content(db.Model):
def is_weblink(self) -> bool:
"""Check if content is a web link (URL) rather than an uploaded file."""
return self.content_type == 'weblink'
@property
def has_player_edits(self) -> bool:
"""Whether this content has been edited on a player."""
return self.edits.count() > 0
@property
def original_display_name(self) -> str:
"""Display name of the original (unedited) file."""
return self.original_filename or self.filename
@property
def original_media_path(self) -> str:
"""uploads/-relative path of the pristine original file.
After a player edit the original is archived at
edited_media/<id>/original_<name>; before any edit it is simply the
current file.
"""
if self.original_filename and self.filename != self.original_filename:
return f"edited_media/{self.id}/original_{self.original_filename}"
return self.filename
+4 -4
View File
@@ -326,11 +326,11 @@
<div class="media-library" style="max-height: 350px; overflow-y: auto;">
{% if media_files %}
{% for media in media_files %}
<div class="media-item" title="{{ media.filename }}">
<div class="media-item" title="{{ media.original_display_name }}">
{% if media.content_type == 'image' %}
<div class="media-thumbnail" style="width: 100%; height: 100px; overflow: hidden; border-radius: 6px; margin-bottom: 8px; background: #f0f0f0; display: flex; align-items: center; justify-content: center;">
<img src="{{ url_for('static', filename='uploads/' + media.filename) }}"
alt="{{ media.filename }}"
<img src="{{ url_for('static', filename='uploads/' + media.current_media_path) }}"
alt="{{ media.original_display_name }}"
style="max-width: 100%; max-height: 100%; object-fit: cover;"
onerror="this.style.display='none'; this.parentElement.innerHTML='<span style=\'font-size: 48px;\'>📷</span>'">
</div>
@@ -347,7 +347,7 @@
📁
</div>
{% endif %}
<div class="media-name" style="font-size: 11px; line-height: 1.3;">{{ media.filename[:25] }}{% if media.filename|length > 25 %}...{% endif %}</div>
<div class="media-name" style="font-size: 11px; line-height: 1.3;">{{ media.original_display_name[:25] }}{% if media.original_display_name|length > 25 %}...{% endif %}</div>
<div style="font-size: 10px; color: #999; margin-top: 4px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
</div>
{% endfor %}
@@ -318,7 +318,7 @@
{% if content.content_type == 'weblink' %}
<a href="{{ content.url }}" target="_blank" rel="noopener noreferrer">{{ content.url }}</a>
{% else %}
{{ content.filename }}
{{ content.original_display_name }}
{% endif %}
</td>
<td>
@@ -434,7 +434,7 @@
{% elif content.content_type == 'video' %}🎥
{% elif content.content_type == 'pdf' %}📄
{% else %}📁{% endif %}
{{ content.filename }}
{{ content.original_display_name }}
</div>
<div style="font-size: 12px; color: #999;">
{{ content.file_size_mb }} MB
+7 -7
View File
@@ -275,11 +275,11 @@
<div class="media-card">
<button class="delete-btn" onclick="confirmDelete({{ media.id }}, '{{ media.filename }}', {{ media.playlists.count() }}, {{ media.edit_count }})" title="Delete">🗑️</button>
<div class="media-thumbnail">
<img src="{{ url_for('static', filename='uploads/' + media.filename) }}"
alt="{{ media.filename }}"
<img src="{{ url_for('static', filename='uploads/' + media.current_media_path) }}"
alt="{{ media.original_display_name }}"
onerror="this.style.display='none'; this.parentElement.innerHTML='<span class=\'media-icon\'>📷</span>'">
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info">
<span class="type-badge image">Image</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -308,7 +308,7 @@
<div class="media-thumbnail">
<span class="media-icon">🎥</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info">
<span class="type-badge video">Video</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -337,7 +337,7 @@
<div class="media-thumbnail">
<span class="media-icon">📄</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info">
<span class="type-badge pdf">PDF</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -366,7 +366,7 @@
<div class="media-thumbnail">
<span class="media-icon">📊</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info">
<span class="type-badge pptx">PPTX</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
@@ -395,7 +395,7 @@
<div class="media-thumbnail">
<span class="media-icon">📁</span>
</div>
<div class="media-filename" title="{{ media.filename }}">{{ media.filename }}</div>
<div class="media-filename" title="{{ media.original_display_name }}">{{ media.original_display_name }}</div>
<div class="media-info">
<span class="type-badge">{{ media.content_type }}</span>
<div style="margin-top: 5px;">{{ "%.1f"|format(media.file_size_mb) }} MB</div>
+4 -4
View File
@@ -333,7 +333,7 @@
<div class="card-header">
<div class="card-header-title">
<span class="card-header-icon"></span>
<span>📄 {{ original_content.filename if original_content else data.original_name }}</span>
<span>📄 {{ original_content.original_display_name if original_content else data.original_name }}</span>
<span style="font-size: 0.9rem; color: #64748b; font-weight: normal;">
({{ data.versions|length + 1 }} version{{ 's' if (data.versions|length + 1) > 1 else '' }})
</span>
@@ -420,10 +420,10 @@
{% if original_content %}
<div class="version-item"
id="version-{{ content_id }}-original"
onclick="event.stopPropagation(); selectVersion({{ content_id }}, 'original', '{{ original_content.filename }}', 'System', 'N/A', '{{ original_content.uploaded_at | localtime('%Y-%m-%d %H:%M') }}', '{{ url_for('static', filename='uploads/' ~ original_content.filename) }}')">
onclick="event.stopPropagation(); selectVersion({{ content_id }}, 'original', '{{ original_content.original_display_name }}', 'System', 'N/A', '{{ original_content.uploaded_at | localtime('%Y-%m-%d %H:%M') }}', '{{ url_for('static', filename='uploads/' ~ original_content.original_media_path) }}')">
<div class="version-thumbnail">
{% if original_content.filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %}
<img src="{{ url_for('static', filename='uploads/' ~ original_content.filename) }}"
{% if original_content.original_display_name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')) %}
<img src="{{ url_for('static', filename='uploads/' ~ original_content.original_media_path) }}"
alt="Original">
{% else %}
<div style="color: white; font-size: 2rem;">📄</div>