updated player to version

This commit is contained in:
2025-06-20 16:33:52 +03:00
parent b50c11d458
commit 9fe4c419d7
20 changed files with 394 additions and 131 deletions

View File

@@ -3,7 +3,7 @@
# filepath: /home/pi/Desktop/signage-player/run_app.sh
# Navigate to the application directory
cd /home/pi/signage-player/src || exit
cd /home/pi/Desktop/signage-player/src || exit
# Check for the --verbose flag
if [[ "$1" == "--verbose" ]]; then

View File

@@ -1 +1 @@
{"screen_orientation": "Landscape", "screen_name": "tv-terasa", "quickconnect_key": "8887779", "server_ip": "digi-signage.moto-adv.com", "port": "80", "screen_w": "1920", "screen_h": "1080"}
{"screen_orientation": "Landscape", "screen_name": "rpi-tv11", "quickconnect_key": "8887779", "server_ip": "192.168.1.74", "port": "5000", "screen_w": "1920", "screen_h": "1080"}

View File

@@ -1,15 +1,2 @@
2025-06-19 16:03:02 - STARTED: IMG-20250526-WA0003.jpg
2025-06-19 16:03:03 - STARTED: IMG-20250602-WA0011.jpg
2025-06-19 16:03:23 - STARTED: IMG_20250601_192845.jpg
2025-06-19 16:03:43 - STARTED: IMG_20250601_185017.jpg
2025-06-19 16:04:03 - STARTED: IMG_20250601_185019.jpg
2025-06-19 16:04:23 - STARTED: IMG_20250601_180727.jpg
2025-06-19 16:04:43 - STARTED: IMG_20250601_174724.jpg
2025-06-19 16:05:03 - STARTED: IMG-20250531-WA0070.jpg
2025-06-19 16:05:23 - STARTED: IMG-20250604-WA0006.jpg
2025-06-19 16:05:43 - STARTED: IMG-20250526-WA0003.jpg
2025-06-19 16:06:03 - STARTED: IMG-20250602-WA0011.jpg
2025-06-19 16:06:23 - STARTED: IMG_20250601_192845.jpg
2025-06-19 16:06:43 - STARTED: IMG_20250601_185017.jpg
2025-06-19 16:39:23 - STARTED: IMG-20250526-WA0003.jpg
2025-06-19 16:39:23 - STARTED: IMG-20250602-WA0011.jpg
2025-06-20 16:32:40 - STARTED: edit_pencil.png
2025-06-20 16:33:00 - STARTED: delete.png

Binary file not shown.

View File

@@ -23,7 +23,7 @@ import datetime # Import datetime for timestamping logs
import subprocess
# Import functions from python_functions.py
from python_functions import load_playlist, download_media_files, clean_unused_files
from python_functions import load_local_playlist, download_media_files, clean_unused_files, fetch_server_playlist
# Load the KV file for UI layout
Builder.load_file('kv/media_player.kv')
@@ -102,10 +102,36 @@ class MediaPlayer(Screen):
def on_enter(self):
"""Called when the screen is entered."""
self.playlist = load_playlist() # Load the playlist (local or server)
if self.playlist: # Only proceed if the playlist is not empty
download_media_files(self.playlist) # Download media files if the playlist has changed
clean_unused_files(self.playlist) # Remove unused files from the resource folder
Logger.info("MediaPlayer: Entering screen...")
# Attempt to load the local playlist
self.playlist = load_local_playlist()
Logger.info(f"MediaPlayer: Loaded local playlist: {self.playlist}")
if not self.playlist: # If no local playlist exists
Logger.warning("MediaPlayer: No local playlist found. Fetching from server...")
# Fetch the server playlist
server_playlist_data = fetch_server_playlist()
server_playlist = server_playlist_data.get('playlist', [])
server_version = server_playlist_data.get('version', 0)
if server_playlist: # If server playlist is valid
Logger.info("MediaPlayer: Server playlist fetched successfully.")
# Download media files and save the playlist locally
download_media_files(server_playlist, server_version)
self.playlist = load_local_playlist() # Reload the updated local playlist
if self.playlist:
Logger.info("MediaPlayer: Local playlist updated successfully.")
else:
Logger.error("MediaPlayer: Failed to update local playlist.")
else:
Logger.error("MediaPlayer: Failed to fetch server playlist. No media to play.")
return
if self.playlist: # If the playlist is loaded successfully
self.play_media() # Start playing media
self.show_buttons() # Ensure buttons are visible when the screen is entered
else:
@@ -159,7 +185,7 @@ class MediaPlayer(Screen):
Logger.error(f"Failed to clean up old logs: {e}")
def play_media(self):
# Play the current media in the playlist.
"""Play the current media in the playlist."""
if self.playlist:
media = self.playlist[self.current_index] # Get the current media
file_name = media.get('file_name', '') # Get the file name
@@ -214,11 +240,11 @@ class MediaPlayer(Screen):
Logger.warning("Video duration is unknown. Using default duration")
def show_image(self, file_path, duration):
# Display an image with a fade-in effect.
"""Display an image with a fade-in effect."""
Logger.info(f"Showing image: {file_path}")
if not os.path.exists(file_path):
Logger.error(f"Image file not found: {file_path}")
#return # Ensure this return is properly indented within the method
return
# Set the image source
self.image_display.source = file_path
@@ -320,14 +346,28 @@ class MediaPlayer(Screen):
self.ids.play_pause_button.background_normal = './Resurse/play.png'
def check_playlist_updates(self, dt):
#Check for updates to the playlist."""
new_playlist = load_playlist() # Load the new playlist
if new_playlist != self.playlist: # Compare the new playlist with the current one
Logger.info("Playlist updated. Changes detected.")
self.updated_playlist = new_playlist # Store the updated playlist
self.is_playlist_update_pending = True # Mark the update as pending
"""Check for updates to the playlist."""
Logger.info("Checking for playlist updates...")
# Fetch the server playlist
server_playlist_data = fetch_server_playlist() # Fetch the playlist from the server
server_playlist = server_playlist_data.get('playlist', [])
server_version = server_playlist_data.get('version', 0)
# Load the local playlist
local_playlist_data = load_local_playlist() # Load the local playlist
local_version = local_playlist_data.get('version', 0) if local_playlist_data else 0
# Compare versions
if server_version != local_version: # If versions differ
Logger.info(f"Playlist version mismatch detected. Local version: {local_version}, Server version: {server_version}")
# Update the local playlist and download new media files
download_media_files(server_playlist) # Download media files from the server
self.playlist = load_local_playlist() # Reload the updated local playlist
Logger.info("Playlist updated successfully.")
else:
Logger.info("Playlist update skipped. No changes detected.")
Logger.info("Playlist versions match. No update needed.")
class SettingsScreen(Screen):
"""Settings screen for configuring the app."""

View File

@@ -2,9 +2,12 @@ import os
import json
import requests
from kivy.logger import Logger
import bcrypt
import time
CONFIG_FILE = './Resurse/app_config.txt'
LOCAL_PLAYLIST_FILE = './static/local_playlist.json' # Path to the local playlist file
def load_config():
"""Load configuration from app_config.txt."""
if os.path.exists(CONFIG_FILE):
@@ -14,49 +17,55 @@ def load_config():
return json.load(file)
except json.JSONDecodeError as e:
Logger.error(f"python_functions: Failed to parse configuration file. Error: {e}")
return {
"screen_orientation": "Landscape",
"screen_name": "",
"quickconnect_key": "",
"server_ip": "",
"port": ""
}
return {}
else:
Logger.error(f"python_functions: Configuration file {CONFIG_FILE} not found.")
return {
"screen_orientation": "Landscape",
"screen_name": "",
"quickconnect_key": "",
"server_ip": "",
"port": ""
}
return {}
# Load configuration and initialize variables
config_data = load_config()
server = config_data.get("server_ip", "")
host = config_data.get("screen_name", "")
quick = config_data.get("quickconnect_key", "")
port = config_data.get("port", "")
# Determine the configuration status
if server and host and quick and port:
config_status = "ok"
else:
config_status = "not_ok"
port = config_data.get("port", "")
Logger.info(f"python_functions: Configuration loaded: server={server}, host={host}, quick={quick}, port={port}")
Logger.info(f"python_functions: Configuration status: {config_status}")
def load_local_playlist():
"""Load the playlist from local storage."""
if os.path.exists(LOCAL_PLAYLIST_FILE):
try:
with open(LOCAL_PLAYLIST_FILE, 'r') as local_file:
local_playlist = json.load(local_file)
Logger.info(f"python_functions: Local playlist loaded: {local_playlist}")
if isinstance(local_playlist, dict) and 'playlist' in local_playlist and 'version' in local_playlist:
return local_playlist.get('playlist', [])
else:
Logger.error("python_functions: Invalid local playlist structure.")
return []
except json.JSONDecodeError as e:
Logger.error(f"python_functions: Failed to parse local playlist file. Error: {e}")
return []
else:
Logger.warning("python_functions: Local playlist file not found.")
return []
def load_playlist():
"""Load playlist from the server or local storage."""
# Check if the local playlist exists and is valid
local_playlist = load_local_playlist()
if local_playlist:
Logger.info("python_functions: Using local playlist.")
return local_playlist # Skip server download if local playlist is valid
def save_local_playlist(playlist):
"""Save the updated playlist locally."""
if not playlist or 'playlist' not in playlist:
Logger.error("python_functions: Invalid playlist data. Cannot save local playlist.")
return
try:
Logger.info("python_functions: Attempting to load playlist from server...")
with open(LOCAL_PLAYLIST_FILE, 'w') as local_file:
json.dump(playlist, local_file)
Logger.info("python_functions: Updated local playlist with server data.")
except IOError as e:
Logger.error(f"python_functions: Failed to save local playlist: {e}")
def fetch_server_playlist():
"""Fetch the updated playlist from the server."""
try:
server_ip = f'{server}:{port}' # Construct the server IP with port
url = f'http://{server_ip}/api/playlists'
params = {
@@ -65,104 +74,83 @@ def load_playlist():
}
response = requests.get(url, params=params)
# Print the raw response content and status code for debugging
Logger.debug(f"python_functions: Status Code: {response.status_code}")
Logger.debug(f"python_functions: Response Content: {response.text}")
# Check if the request was successful
if response.status_code == 200:
try:
# Parse the JSON response
server_data = response.json()
playlist = server_data.get('playlist', [])
response_data = response.json()
playlist = response_data.get('playlist', [])
version = response_data.get('playlist_version', None)
hashed_quickconnect = response_data.get('hashed_quickconnect', None)
# Validate the playlist structure
if isinstance(playlist, list) and all(isinstance(item, dict) for item in playlist):
Logger.info("python_functions: Playlist loaded successfully.")
Logger.debug(f"python_functions: Loaded playlist: {playlist}")
# Save the playlist locally
with open(LOCAL_PLAYLIST_FILE, 'w') as local_file:
json.dump(playlist, local_file)
Logger.info("python_functions: Updated local playlist with server data.")
return playlist
if version is not None and hashed_quickconnect is not None:
if bcrypt.checkpw(quick.encode('utf-8'), hashed_quickconnect.encode('utf-8')):
Logger.info("python_functions: Fetched updated playlist from server.")
return {'playlist': playlist, 'version': version}
else:
Logger.error("python_functions: Invalid playlist structure from server.")
except json.JSONDecodeError as e:
Logger.error(f"python_functions: Failed to parse JSON response: {e}")
Logger.error("python_functions: Quickconnect code validation failed.")
else:
Logger.error("python_functions: Failed to retrieve playlist or hashed quickconnect from the response.")
else:
Logger.error(f"python_functions: Failed to retrieve playlist. Status Code: {response.status_code}")
Logger.error(f"python_functions: Failed to fetch playlist. Status Code: {response.status_code}")
except requests.exceptions.RequestException as e:
Logger.error(f"python_functions: Failed to load playlist: {e}")
Logger.error(f"python_functions: Failed to fetch playlist: {e}")
# Fallback to local playlist
return local_playlist
return {'playlist': [], 'version': 0}
def load_local_playlist():
"""Load the playlist from local storage."""
if os.path.exists(LOCAL_PLAYLIST_FILE):
try:
with open(LOCAL_PLAYLIST_FILE, 'r') as local_file:
local_playlist = json.load(local_file)
# Validate the structure of the local playlist
if isinstance(local_playlist, list) and all(isinstance(item, dict) for item in local_playlist):
Logger.info("python_functions: Loaded and validated local playlist.")
return local_playlist
else:
Logger.error("python_functions: Invalid local playlist structure.")
return []
except json.JSONDecodeError:
Logger.error("python_functions: Failed to parse local playlist file.")
return []
else:
Logger.warning("python_functions: Local playlist file not found.")
return []
def download_media_files(playlist):
"""Download media files from the playlist."""
def download_media_files(playlist, version):
"""Download media files from the server and update the local playlist."""
Logger.info("python_functions: Starting media file download...")
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') # Update this to the correct path
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') # Path to the local folder
if not os.path.exists(base_dir):
os.makedirs(base_dir)
Logger.info(f"python_functions: Created directory {base_dir} for media files.")
updated_playlist = [] # List to store updated media entries
for media in playlist:
file_name = media.get('file_name', '')
file_url = media.get('url', '')
duration = media.get('duration', 10) # Default duration if not provided
file_path = os.path.join(base_dir, file_name)
try:
response = requests.get(file_url)
if response.status_code == 200:
with open(file_path, 'wb') as file:
file.write(response.content)
Logger.info(f"python_functions: Downloaded {file_name} to {file_path}")
else:
Logger.error(f"python_functions: Failed to download {file_name}: {response.status_code}")
except requests.exceptions.RequestException as e:
Logger.error(f"python_functions: Failed to download {file_name}: {e}")
Logger.debug(f"python_functions: Preparing to download {file_name} from {file_url}...")
if os.path.exists(file_path):
Logger.info(f"python_functions: File {file_name} already exists. Skipping download.")
else:
try:
response = requests.get(file_url, timeout=10)
if response.status_code == 200:
with open(file_path, 'wb') as file:
file.write(response.content)
Logger.info(f"python_functions: Successfully downloaded {file_name} to {file_path}")
else:
Logger.error(f"python_functions: Failed to download {file_name}. Status Code: {response.status_code}")
continue
except requests.exceptions.RequestException as e:
Logger.error(f"python_functions: Error downloading {file_name}: {e}")
continue
updated_media = {
'file_name': file_name,
'url': file_path, # Update URL to local path
'duration': duration
}
updated_playlist.append(updated_media)
# Save the updated playlist locally
save_local_playlist({'playlist': updated_playlist, 'version': version})
def clean_unused_files(playlist):
"""Remove unused media files from the resource folder."""
Logger.info("python_functions: Cleaning unused media files...")
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') # Update this to the correct path
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse')
if not os.path.exists(base_dir):
Logger.debug(f"python_functions: Directory {base_dir} does not exist. No files to clean.")
return
# Get all file names from the playlist
playlist_files = {media.get('file_name', '') for media in playlist}
# Get all files in the directory
all_files = set(os.listdir(base_dir))
# Determine unused files
unused_files = all_files - playlist_files
# Delete unused files
for file_name in unused_files:
file_path = os.path.join(base_dir, file_name)
try:

248
src/python_functions2.py Normal file
View File

@@ -0,0 +1,248 @@
import os
import json
import requests
from kivy.logger import Logger
import bcrypt
import time
CONFIG_FILE = './Resurse/app_config.txt'
LOCAL_PLAYLIST_FILE = './static/local_playlist.json' # Path to the local playlist file
def load_config():
"""Load configuration from app_config.txt."""
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r') as file:
Logger.info("python_functions: Configuration file loaded successfully.")
return json.load(file)
except json.JSONDecodeError as e:
Logger.error(f"python_functions: Failed to parse configuration file. Error: {e}")
return {
"screen_orientation": "Landscape",
"screen_name": "",
"quickconnect_key": "",
"server_ip": "",
"port": ""
}
else:
Logger.error(f"python_functions: Configuration file {CONFIG_FILE} not found.")
return {
"screen_orientation": "Landscape",
"screen_name": "",
"quickconnect_key": "",
"server_ip": "",
"port": ""
}
# Load configuration and initialize variables
config_data = load_config()
server = config_data.get("server_ip", "")
host = config_data.get("screen_name", "")
quick = config_data.get("quickconnect_key", "")
port = config_data.get("port", "")
print(server, host, quick, port)
# Determine the configuration status
if server and host and quick and port:
config_status = "ok"
else:
config_status = "not_ok"
Logger.info(f"python_functions: Configuration loaded: server={server}, host={host}, quick={quick}, port={port}")
Logger.info(f"python_functions: Configuration status: {config_status}")
def load_playlist():
"""Load playlist from the server or local storage and periodically check for updates."""
local_playlist = load_local_playlist()
local_version = local_playlist.get('version', 0) if local_playlist else 0
while True:
try:
Logger.info("python_functions: Checking playlist version on the server...")
server_ip = f'{server}:{port}' # Construct the server IP with port
url = f'http://{server_ip}/api/playlist_version'
params = {
'hostname': host,
'quickconnect_code': quick
}
response = requests.get(url, params=params)
if response.status_code == 200:
response_data = response.json()
server_version = response_data.get('playlist_version', None)
hashed_quickconnect = response_data.get('hashed_quickconnect', None)
if server_version is not None and hashed_quickconnect is not None:
# Validate the quickconnect code using bcrypt
if bcrypt.checkpw(quick.encode('utf-8'), hashed_quickconnect.encode('utf-8')):
Logger.info(f"python_functions: Server playlist version: {server_version}, Local playlist version: {local_version}")
if server_version != local_version:
Logger.info("python_functions: Playlist versions differ. Updating local playlist...")
updated_playlist = fetch_server_playlist()
if updated_playlist and 'playlist' in updated_playlist:
save_local_playlist(updated_playlist) # Update local playlist
local_version = server_version # Update local version
else:
Logger.error("python_functions: Failed to update local playlist. Using existing playlist.")
else:
Logger.info("python_functions: Playlist versions match. No update needed.")
else:
Logger.error("python_functions: Quickconnect code validation failed.")
else:
Logger.error("python_functions: Failed to retrieve playlist version or hashed quickconnect from the response.")
else:
Logger.error(f"python_functions: Failed to retrieve playlist version. Status Code: {response.status_code}")
except requests.exceptions.RequestException as e:
Logger.error(f"python_functions: Failed to check playlist version: {e}")
# Wait for 5 minutes before checking again
time.sleep(300)
def load_local_playlist():
"""Load the playlist from local storage."""
if os.path.exists(LOCAL_PLAYLIST_FILE):
try:
with open(LOCAL_PLAYLIST_FILE, 'r') as local_file:
local_playlist = json.load(local_file)
# Validate the structure of the local playlist
if isinstance(local_playlist, dict) and 'playlist' in local_playlist and 'version' in local_playlist:
Logger.info("python_functions: Loaded and validated local playlist.")
return local_playlist
else:
Logger.error("python_functions: Invalid local playlist structure.")
return None
except json.JSONDecodeError:
Logger.error("python_functions: Failed to parse local playlist file.")
return None
else:
Logger.warning("python_functions: Local playlist file not found.")
return None
def download_media_files(playlist):
"""Download media files from the server and update the local playlist."""
Logger.info("python_functions: Starting media file download...")
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') # Path to the local folder
if not os.path.exists(base_dir):
os.makedirs(base_dir)
Logger.info(f"python_functions: Created directory {base_dir} for media files.")
updated_playlist = [] # List to store updated media entries
for media in playlist:
file_name = media.get('file_name', '')
file_url = media.get('url', '')
duration = media.get('duration', 10) # Default duration if not provided
file_path = os.path.join(base_dir, file_name)
Logger.debug(f"python_functions: Preparing to download {file_name} from {file_url}...")
if os.path.exists(file_path):
Logger.info(f"python_functions: File {file_name} already exists. Skipping download.")
else:
try:
response = requests.get(file_url, timeout=10) # Add timeout for better error handling
if response.status_code == 200:
with open(file_path, 'wb') as file:
file.write(response.content)
Logger.info(f"python_functions: Successfully downloaded {file_name} to {file_path}")
else:
Logger.error(f"python_functions: Failed to download {file_name}. Status Code: {response.status_code}")
continue
except requests.exceptions.RequestException as e:
Logger.error(f"python_functions: Error downloading {file_name}: {e}")
continue
# Update the media entry with the local file path and duration
updated_media = {
'file_name': file_name,
'url': file_path, # Update URL to local path
'duration': duration
}
updated_playlist.append(updated_media)
# Save the updated playlist locally
save_local_playlist({'playlist': updated_playlist, 'version': playlist.get('version', 0)})
def clean_unused_files(playlist):
"""Remove unused media files from the resource folder."""
Logger.info("python_functions: Cleaning unused media files...")
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') # Update this to the correct path
if not os.path.exists(base_dir):
Logger.debug(f"python_functions: Directory {base_dir} does not exist. No files to clean.")
return
# Get all file names from the playlist
playlist_files = {media.get('file_name', '') for media in playlist}
# Get all files in the directory
all_files = set(os.listdir(base_dir))
# Determine unused files
unused_files = all_files - playlist_files
# Delete unused files
for file_name in unused_files:
file_path = os.path.join(base_dir, file_name)
try:
os.remove(file_path)
Logger.info(f"python_functions: Deleted unused file: {file_path}")
except OSError as e:
Logger.error(f"python_functions: Failed to delete {file_path}: {e}")
def fetch_server_playlist():
"""Fetch the updated playlist from the server."""
try:
server_ip = f'{server}:{port}' # Construct the server IP with port
url = f'http://{server_ip}/api/playlists'
params = {
'hostname': host,
'quickconnect_code': quick
}
response = requests.get(url, params=params)
if response.status_code == 200:
response_data = response.json()
playlist = response_data.get('playlist', [])
version = response_data.get('playlist_version', None)
hashed_quickconnect = response_data.get('hashed_quickconnect', None)
if version is not None and hashed_quickconnect is not None:
# Validate the quickconnect code using bcrypt
if bcrypt.checkpw(quick.encode('utf-8'), hashed_quickconnect.encode('utf-8')):
Logger.info("python_functions: Fetched updated playlist from server.")
return {'playlist': playlist, 'version': version}
else:
Logger.error("python_functions: Quickconnect code validation failed.")
else:
Logger.error("python_functions: Failed to retrieve playlist or hashed quickconnect from the response.")
else:
Logger.error(f"python_functions: Failed to fetch playlist. Status Code: {response.status_code}")
except requests.exceptions.RequestException as e:
Logger.error(f"python_functions: Failed to fetch playlist: {e}")
# Return an empty playlist if fetching fails
return {'playlist': [], 'version': 0}
def save_local_playlist(playlist):
"""Save the updated playlist locally."""
if not playlist or 'playlist' not in playlist:
Logger.error("python_functions: Invalid playlist data. Cannot save local playlist.")
return
try:
with open(LOCAL_PLAYLIST_FILE, 'w') as local_file:
json.dump(playlist, local_file)
Logger.info("python_functions: Updated local playlist with server data.")
except IOError as e:
Logger.error(f"python_functions: Failed to save local playlist: {e}")
def check_playlist_updates(self, dt):
"""Check for updates to the playlist."""
new_playlist = load_playlist() # Load the new playlist
if new_playlist != self.playlist: # Compare the new playlist with the current one
Logger.info("Playlist updated. Changes detected.")
self.updated_playlist = new_playlist # Store the updated playlist
self.is_playlist_update_pending = True # Mark the update as pending
else:
Logger.info("Playlist update skipped. No changes detected.")

View File

@@ -1 +1 @@
[{"duration": 20, "file_name": "IMG-20250526-WA0003.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG-20250526-WA0003.jpg"}, {"duration": 20, "file_name": "IMG-20250602-WA0011.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG-20250602-WA0011.jpg"}, {"duration": 20, "file_name": "IMG_20250601_192845.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250601_192845.jpg"}, {"duration": 20, "file_name": "IMG_20250601_185017.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250601_185017.jpg"}, {"duration": 20, "file_name": "IMG_20250601_185019.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250601_185019.jpg"}, {"duration": 20, "file_name": "IMG_20250601_180727.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250601_180727.jpg"}, {"duration": 20, "file_name": "IMG_20250601_174724.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250601_174724.jpg"}, {"duration": 20, "file_name": "IMG-20250531-WA0070.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG-20250531-WA0070.jpg"}, {"duration": 20, "file_name": "IMG-20250604-WA0006.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG-20250604-WA0006.jpg"}]
{"playlist": [{"file_name": "edit_pencil.png", "url": "/home/pi/Desktop/signage-player/src/static/resurse/edit_pencil.png", "duration": 20}, {"file_name": "delete.png", "url": "/home/pi/Desktop/signage-player/src/static/resurse/delete.png", "duration": 20}], "version": 2}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB