updated to correctly play the playlist and reload images after edited
This commit is contained in:
1
.player_stop_requested
Normal file
1
.player_stop_requested
Normal file
@@ -0,0 +1 @@
|
||||
User requested exit via password
|
||||
@@ -271,8 +271,53 @@ def delete_old_playlists_and_media(current_version, playlist_dir, media_dir, kee
|
||||
os.remove(filepath)
|
||||
logger.info(f"🗑️ Deleted old playlist: {f}")
|
||||
|
||||
# TODO: Clean up unused media files
|
||||
logger.info(f"✅ Cleanup complete (kept {keep_versions} latest versions)")
|
||||
# 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()
|
||||
|
||||
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"✅ Cleanup complete (kept {keep_versions} latest playlist versions)")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error during cleanup: {e}")
|
||||
|
||||
162
src/main.py
162
src/main.py
@@ -382,13 +382,26 @@ class EditPopup(Popup):
|
||||
self.user_card_data = user_card_data # Store card data to send to server on save
|
||||
self.drawing_layer = None
|
||||
|
||||
# Pause playback
|
||||
# Pause playback (without auto-resume timer)
|
||||
self.was_paused = self.player.is_paused
|
||||
if not self.was_paused:
|
||||
self.player.is_paused = True
|
||||
Clock.unschedule(self.player.next_media)
|
||||
|
||||
# Cancel auto-resume timer if one exists (don't want auto-resume during editing)
|
||||
if self.player.auto_resume_event:
|
||||
Clock.unschedule(self.player.auto_resume_event)
|
||||
self.player.auto_resume_event = None
|
||||
Logger.info("EditPopup: Cancelled auto-resume timer")
|
||||
|
||||
# Update button icon to play (to show it's paused)
|
||||
self.player.ids.play_pause_btn.background_normal = self.player.resources_path + '/play.png'
|
||||
self.player.ids.play_pause_btn.background_down = self.player.resources_path + '/play.png'
|
||||
|
||||
if self.player.current_widget and isinstance(self.player.current_widget, Video):
|
||||
self.player.current_widget.state = 'pause'
|
||||
|
||||
Logger.info("EditPopup: ⏸ Paused playback (no auto-resume) for editing")
|
||||
|
||||
# Show cursor
|
||||
try:
|
||||
@@ -703,6 +716,32 @@ class EditPopup(Popup):
|
||||
# Export only the visible content (image + drawings, no toolbars)
|
||||
self.content.export_to_png(output_path)
|
||||
|
||||
Logger.info(f"EditPopup: Saved edited image to {output_path}")
|
||||
|
||||
# ALSO overwrite the original image with edited content
|
||||
Logger.info(f"EditPopup: Overwriting original image at {self.image_path}")
|
||||
import shutil
|
||||
import time
|
||||
|
||||
# Get original file info before overwrite
|
||||
orig_size = os.path.getsize(self.image_path)
|
||||
orig_mtime = os.path.getmtime(self.image_path)
|
||||
|
||||
# Overwrite the file
|
||||
shutil.copy2(output_path, self.image_path)
|
||||
|
||||
# Force file system sync to ensure data is written to disk
|
||||
os.sync()
|
||||
|
||||
# Verify the overwrite
|
||||
new_size = os.path.getsize(self.image_path)
|
||||
new_mtime = os.path.getmtime(self.image_path)
|
||||
|
||||
Logger.info(f"EditPopup: ✓ File overwritten:")
|
||||
Logger.info(f" - Size: {orig_size} -> {new_size} bytes (changed: {new_size != orig_size})")
|
||||
Logger.info(f" - Modified time: {orig_mtime} -> {new_mtime} (changed: {new_mtime > orig_mtime})")
|
||||
Logger.info(f"EditPopup: ✓ File synced to disk")
|
||||
|
||||
# Restore toolbars
|
||||
self.top_toolbar.opacity = 1
|
||||
self.right_sidebar.opacity = 1
|
||||
@@ -711,9 +750,7 @@ class EditPopup(Popup):
|
||||
json_filename = self._save_metadata(edited_dir, new_name, base_name,
|
||||
new_version if version_match else 1, output_filename)
|
||||
|
||||
Logger.info(f"EditPopup: Saved edited image to {output_path}")
|
||||
|
||||
# Upload to server asynchronously (non-blocking)
|
||||
# Upload to server in background (continues after popup closes)
|
||||
import threading
|
||||
upload_thread = threading.Thread(
|
||||
target=self._upload_to_server,
|
||||
@@ -721,10 +758,42 @@ class EditPopup(Popup):
|
||||
daemon=True
|
||||
)
|
||||
upload_thread.start()
|
||||
Logger.info(f"EditPopup: Background upload thread started")
|
||||
|
||||
# Show confirmation
|
||||
self.title = f'Saved as {output_filename}'
|
||||
Clock.schedule_once(lambda dt: self.dismiss(), 1)
|
||||
# NOW show saving popup AFTER everything is done
|
||||
def show_saving_and_dismiss(dt):
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.label import Label
|
||||
|
||||
# Create label with background
|
||||
save_label = Label(
|
||||
text='✓ Saved! Reloading player...',
|
||||
font_size='36sp',
|
||||
color=(1, 1, 1, 1),
|
||||
bold=True
|
||||
)
|
||||
|
||||
saving_popup = Popup(
|
||||
title='',
|
||||
content=save_label,
|
||||
size_hint=(0.8, 0.3),
|
||||
auto_dismiss=False,
|
||||
separator_height=0,
|
||||
background_color=(0.2, 0.7, 0.2, 0.95) # Green background
|
||||
)
|
||||
saving_popup.open()
|
||||
Logger.info("EditPopup: Saving confirmation popup opened")
|
||||
|
||||
# Dismiss both popups after 2 seconds
|
||||
def dismiss_all(dt):
|
||||
saving_popup.dismiss()
|
||||
Logger.info(f"EditPopup: Dismissing to resume playback...")
|
||||
self.dismiss()
|
||||
|
||||
Clock.schedule_once(dismiss_all, 2.0)
|
||||
|
||||
# Small delay to ensure UI is ready, then show popup
|
||||
Clock.schedule_once(show_saving_and_dismiss, 0.1)
|
||||
|
||||
except Exception as e:
|
||||
Logger.error(f"EditPopup: Error in export: {e}")
|
||||
@@ -734,6 +803,8 @@ class EditPopup(Popup):
|
||||
# Restore toolbars
|
||||
self.top_toolbar.opacity = 1
|
||||
self.right_sidebar.opacity = 1
|
||||
# Still dismiss on error after brief delay
|
||||
Clock.schedule_once(lambda dt: self.dismiss(), 1)
|
||||
|
||||
Clock.schedule_once(do_export, 0.1)
|
||||
return
|
||||
@@ -851,16 +922,35 @@ class EditPopup(Popup):
|
||||
self.dismiss()
|
||||
|
||||
def on_popup_dismiss(self, *args):
|
||||
"""Resume playback when popup closes"""
|
||||
# Resume playback if it wasn't paused before
|
||||
"""Resume playback when popup closes - reload current image and continue"""
|
||||
from kivy.clock import Clock
|
||||
|
||||
# Force remove current widget immediately
|
||||
if self.player.current_widget:
|
||||
Logger.info("EditPopup: Removing current widget to force reload")
|
||||
self.player.ids.content_area.remove_widget(self.player.current_widget)
|
||||
self.player.current_widget = None
|
||||
Logger.info("EditPopup: ✓ Widget removed, ready for fresh load")
|
||||
|
||||
# Resume playback if it wasn't paused before editing
|
||||
if not self.was_paused:
|
||||
self.player.is_paused = False
|
||||
self.player.play_current_media()
|
||||
|
||||
# Update button icon to pause (to show it's playing)
|
||||
self.player.ids.play_pause_btn.background_normal = self.player.resources_path + '/pause.png'
|
||||
self.player.ids.play_pause_btn.background_down = self.player.resources_path + '/pause.png'
|
||||
|
||||
# Add delay to ensure file write is complete and synced
|
||||
def reload_media(dt):
|
||||
Logger.info("EditPopup: ▶ Resuming playback and reloading edited image (force_reload=True)")
|
||||
self.player.play_current_media(force_reload=True)
|
||||
|
||||
Clock.schedule_once(reload_media, 0.5)
|
||||
else:
|
||||
Logger.info("EditPopup: Dismissed, keeping paused state")
|
||||
|
||||
# Restart control hide timer
|
||||
self.player.schedule_hide_controls()
|
||||
|
||||
Logger.info("EditPopup: Dismissed, playback resumed")
|
||||
|
||||
# Custom keyboard container with close button
|
||||
class KeyboardContainer(BoxLayout):
|
||||
@@ -1656,8 +1746,12 @@ class SignagePlayer(Widget):
|
||||
self.current_index = 0
|
||||
self.play_current_media()
|
||||
|
||||
def play_current_media(self):
|
||||
"""Play the current media item"""
|
||||
def play_current_media(self, force_reload=False):
|
||||
"""Play the current media item
|
||||
|
||||
Args:
|
||||
force_reload: If True, clears image cache before loading (for edited images)
|
||||
"""
|
||||
# Don't play if paused (unless we're explicitly resuming)
|
||||
if self.is_paused:
|
||||
Logger.debug(f"SignagePlayer: Skipping play_current_media - player is paused")
|
||||
@@ -1719,7 +1813,7 @@ class SignagePlayer(Widget):
|
||||
elif file_extension in ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp']:
|
||||
# Image file
|
||||
Logger.info(f"SignagePlayer: Media type: IMAGE")
|
||||
self.play_image(media_path, duration)
|
||||
self.play_image(media_path, duration, force_reload=force_reload)
|
||||
else:
|
||||
Logger.warning(f"SignagePlayer: ❌ Unsupported media type: {file_extension}")
|
||||
Logger.warning(f"SignagePlayer: Supported: .mp4/.avi/.mkv/.mov/.webm/.jpg/.jpeg/.png/.bmp/.gif/.webp")
|
||||
@@ -1819,17 +1913,37 @@ class SignagePlayer(Widget):
|
||||
except Exception as e:
|
||||
Logger.warning(f"SignagePlayer: Could not log video info: {e}")
|
||||
|
||||
def play_image(self, image_path, duration):
|
||||
def play_image(self, image_path, duration, force_reload=False):
|
||||
"""Play an image file"""
|
||||
try:
|
||||
Logger.info(f"SignagePlayer: Creating AsyncImage widget...")
|
||||
self.current_widget = AsyncImage(
|
||||
source=image_path,
|
||||
allow_stretch=True,
|
||||
keep_ratio=True, # Maintain aspect ratio
|
||||
size_hint=(1, 1),
|
||||
pos_hint={'center_x': 0.5, 'center_y': 0.5}
|
||||
)
|
||||
# Log file info before loading
|
||||
file_size = os.path.getsize(image_path)
|
||||
file_mtime = os.path.getmtime(image_path)
|
||||
Logger.info(f"SignagePlayer: Loading image: {os.path.basename(image_path)}")
|
||||
Logger.info(f" - Size: {file_size:,} bytes, Modified: {file_mtime}")
|
||||
|
||||
if force_reload:
|
||||
Logger.info(f"SignagePlayer: Force reload - using Image widget (no async cache)")
|
||||
# Use regular Image widget instead of AsyncImage to bypass all caching
|
||||
from kivy.uix.image import Image
|
||||
self.current_widget = Image(
|
||||
source=image_path,
|
||||
allow_stretch=True,
|
||||
keep_ratio=True,
|
||||
size_hint=(1, 1),
|
||||
pos_hint={'center_x': 0.5, 'center_y': 0.5}
|
||||
)
|
||||
# Force reload the texture
|
||||
self.current_widget.reload()
|
||||
else:
|
||||
Logger.info(f"SignagePlayer: Creating AsyncImage widget...")
|
||||
self.current_widget = AsyncImage(
|
||||
source=image_path,
|
||||
allow_stretch=True,
|
||||
keep_ratio=True, # Maintain aspect ratio
|
||||
size_hint=(1, 1),
|
||||
pos_hint={'center_x': 0.5, 'center_y': 0.5}
|
||||
)
|
||||
Logger.info(f"SignagePlayer: Adding image widget to content area...")
|
||||
self.ids.content_area.add_widget(self.current_widget)
|
||||
# Schedule next media after duration (unschedule first to prevent overlaps)
|
||||
|
||||
Reference in New Issue
Block a user