Perf: Optimize playback and simplify playlist management

- Performance improvements:
  * Throttle drawing updates to 60fps (16ms intervals)
  * Optimize file I/O: use single os.stat() instead of exists+getsize
  * Reduce logger overhead: convert hot-path info logs to debug
  * Preload next media asynchronously for smoother transitions
  * Smart cache invalidation for edited images

- Simplify playlist management:
  * Remove versioning: single server_playlist.json file
  * Create nested directories for edited_media downloads
  * Recursively delete unused media and empty folders
  * Cleaner version tracking without file proliferation

- UI improvements:
  * Smoother intro-to-playlist transition
  * Fix edited media directory creation for nested paths
This commit is contained in:
Kiwy Signage Player
2025-12-14 16:57:47 +02:00
parent b2d380511a
commit 1cc0eae542
4 changed files with 156 additions and 164 deletions
+67 -93
View File
@@ -188,10 +188,9 @@ def fetch_server_playlist(config):
return {'playlist': [], 'version': 0}
def save_playlist_with_version(playlist_data, playlist_dir):
"""Save playlist to file with version number."""
version = playlist_data.get('version', 0)
playlist_file = os.path.join(playlist_dir, f'server_playlist_v{version}.json')
def save_playlist(playlist_data, playlist_dir):
"""Save playlist to a single file (no versioning)."""
playlist_file = os.path.join(playlist_dir, 'server_playlist.json')
# Ensure directory exists
os.makedirs(playlist_dir, exist_ok=True)
@@ -222,6 +221,9 @@ def download_media_files(playlist, media_dir):
logger.info(f"✓ File {file_name} already exists. Skipping download.")
else:
try:
# Create parent directories if they don't exist (for nested paths like edited_media/5/)
os.makedirs(os.path.dirname(local_path), exist_ok=True)
response = requests.get(file_url, timeout=30)
if response.status_code == 200:
with open(local_path, 'wb') as file:
@@ -245,82 +247,59 @@ def download_media_files(playlist, media_dir):
return updated_playlist
def delete_old_playlists_and_media(current_version, playlist_dir, media_dir, keep_versions=1):
"""Delete old playlist files and media files not referenced by the latest playlist version."""
def delete_unused_media(playlist_data, media_dir):
"""Delete media files not referenced in the current playlist."""
try:
# Find all playlist files
playlist_files = [f for f in os.listdir(playlist_dir)
if f.startswith('server_playlist_v') and f.endswith('.json')]
# Extract versions and sort
versions = []
for f in playlist_files:
try:
version = int(f.replace('server_playlist_v', '').replace('.json', ''))
versions.append((version, f))
except ValueError:
continue
versions.sort(reverse=True)
# Keep only the latest N versions
files_to_delete = [f for v, f in versions[keep_versions:]]
for f in files_to_delete:
filepath = os.path.join(playlist_dir, f)
os.remove(filepath)
logger.info(f"🗑️ Deleted old playlist: {f}")
# Clean up unused media files
logger.info("🔍 Checking for unused media files...")
# Get list of media files referenced in current playlist
current_playlist_file = os.path.join(playlist_dir, f'server_playlist_v{current_version}.json')
referenced_files = set()
for media in playlist_data.get('playlist', []):
file_name = media.get('file_name', '')
if file_name:
referenced_files.add(file_name)
if os.path.exists(current_playlist_file):
try:
with open(current_playlist_file, 'r') as f:
playlist_data = json.load(f)
for item in playlist_data.get('playlist', []):
file_name = item.get('file_name', '')
if file_name:
referenced_files.add(file_name)
logger.info(f"📋 Current playlist references {len(referenced_files)} media files")
# Get all files in media directory (excluding edited_media subfolder)
if os.path.exists(media_dir):
media_files = [f for f in os.listdir(media_dir)
if os.path.isfile(os.path.join(media_dir, f))]
deleted_count = 0
for media_file in media_files:
# Skip if file is in current playlist
if media_file in referenced_files:
continue
# Delete unreferenced file
media_path = os.path.join(media_dir, media_file)
try:
os.remove(media_path)
logger.info(f"🗑️ Deleted unused media: {media_file}")
deleted_count += 1
except Exception as e:
logger.warning(f"⚠️ Could not delete {media_file}: {e}")
if deleted_count > 0:
logger.info(f"✅ Deleted {deleted_count} unused media files")
else:
logger.info("✅ No unused media files to delete")
except Exception as e:
logger.error(f"❌ Error reading playlist for media cleanup: {e}")
logger.info(f"📋 Current playlist references {len(referenced_files)} files")
logger.info(f"✅ Cleanup complete (kept {keep_versions} latest playlist versions)")
if os.path.exists(media_dir):
# Recursively get all media files
deleted_count = 0
for root, dirs, files in os.walk(media_dir):
for media_file in files:
# Get relative path from media_dir
full_path = os.path.join(root, media_file)
rel_path = os.path.relpath(full_path, media_dir)
# Skip if file is in current playlist
if rel_path in referenced_files:
continue
# Delete unreferenced file
try:
os.remove(full_path)
logger.info(f"🗑️ Deleted unused media: {rel_path}")
deleted_count += 1
except Exception as e:
logger.warning(f"⚠️ Could not delete {rel_path}: {e}")
# Clean up empty directories
for root, dirs, files in os.walk(media_dir, topdown=False):
for dir_name in dirs:
dir_path = os.path.join(root, dir_name)
try:
if not os.listdir(dir_path): # If directory is empty
os.rmdir(dir_path)
logger.debug(f"🗑️ Removed empty directory: {os.path.relpath(dir_path, media_dir)}")
except Exception:
pass
if deleted_count > 0:
logger.info(f"✅ Deleted {deleted_count} unused media files")
else:
logger.info("✅ No unused media files to delete")
except Exception as e:
logger.error(f"❌ Error during cleanup: {e}")
logger.error(f"❌ Error during media cleanup: {e}")
def update_playlist_if_needed(config, playlist_dir, media_dir):
@@ -334,22 +313,17 @@ def update_playlist_if_needed(config, playlist_dir, media_dir):
logger.warning("⚠️ No valid playlist received from server")
return None
# Check local version
# Check local version from single playlist file
local_version = 0
local_playlist_file = None
playlist_file = os.path.join(playlist_dir, 'server_playlist.json')
if os.path.exists(playlist_dir):
playlist_files = [f for f in os.listdir(playlist_dir)
if f.startswith('server_playlist_v') and f.endswith('.json')]
for f in playlist_files:
try:
version = int(f.replace('server_playlist_v', '').replace('.json', ''))
if version > local_version:
local_version = version
local_playlist_file = os.path.join(playlist_dir, f)
except ValueError:
continue
if os.path.exists(playlist_file):
try:
with open(playlist_file, 'r') as f:
local_data = json.load(f)
local_version = local_data.get('version', 0)
except Exception as e:
logger.warning(f"⚠️ Could not read local playlist: {e}")
logger.info(f"📊 Playlist versions - Server: v{server_version}, Local: v{local_version}")
@@ -361,17 +335,17 @@ def update_playlist_if_needed(config, playlist_dir, media_dir):
updated_playlist = download_media_files(server_data['playlist'], media_dir)
server_data['playlist'] = updated_playlist
# Save new playlist
playlist_file = save_playlist_with_version(server_data, playlist_dir)
# Save new playlist (single file, no versioning)
playlist_file = save_playlist(server_data, playlist_dir)
# Clean up old versions
delete_old_playlists_and_media(server_version, playlist_dir, media_dir)
# Delete unused media files
delete_unused_media(server_data, media_dir)
logger.info(f"✅ Playlist updated successfully to v{server_version}")
return playlist_file
else:
logger.info("✓ Playlist is up to date")
return local_playlist_file
return playlist_file
except Exception as e:
logger.error(f"❌ Error updating playlist: {e}")