updates to digiserver app server
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user