final task
This commit is contained in:
@@ -6,5 +6,5 @@
|
||||
"port": "5000",
|
||||
"screen_w": "1920",
|
||||
"screen_h": "1080",
|
||||
"playlist_version": 5
|
||||
"playlist_version": 6
|
||||
}
|
||||
@@ -1,27 +1,6 @@
|
||||
2025-06-24 14:10:03 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:10:22 - STARTED: delete.png
|
||||
2025-06-24 14:16:01 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:16:21 - STARTED: delete.png
|
||||
2025-06-24 14:17:04 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:17:24 - STARTED: delete.png
|
||||
2025-06-24 14:17:44 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:17:59 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:18:14 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:19:22 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:19:37 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:19:52 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:20:08 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:44:17 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:44:32 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:44:47 - STARTED: edit_pencil.png
|
||||
2025-06-24 14:48:16 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:23:13 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:23:33 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:27:25 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:27:45 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:29:48 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:30:08 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:30:09 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:39:48 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:39:55 - STARTED: 20250605_09h44m12s_grim.png
|
||||
2025-06-24 15:40:16 - STARTED: 123.jpeg
|
||||
2025-06-24 16:02:45 - STARTED: 123.jpeg
|
||||
2025-06-24 16:04:27 - STARTED: 123.jpeg
|
||||
2025-06-24 16:05:01 - STARTED: 123.jpeg
|
||||
2025-06-24 16:08:00 - STARTED: 123.jpeg
|
||||
2025-06-24 16:09:01 - STARTED: 123.jpeg
|
||||
2025-06-24 16:09:31 - STARTED: 20250417_09h25m37s_grim.png
|
||||
|
||||
BIN
src/__pycache__/logging_config.cpython-311.pyc
Normal file
BIN
src/__pycache__/logging_config.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -179,6 +179,20 @@
|
||||
text: "Server connection status will appear here."
|
||||
size_hint_x: 0.7
|
||||
|
||||
# Multi-row label box for log messages
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
size_hint_y: None
|
||||
height: 200 # Adjust height as needed
|
||||
|
||||
Label:
|
||||
id: log_messages_label
|
||||
text: "Log messages will appear here."
|
||||
font_size: 14
|
||||
halign: 'left'
|
||||
valign: 'top'
|
||||
text_size: self.size
|
||||
|
||||
# Buttons in the lower part of the screen
|
||||
BoxLayout:
|
||||
size_hint_y: 0.4 # Allocate 40% of the screen height for buttons
|
||||
|
||||
18
src/logging_config.py
Normal file
18
src/logging_config.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
# Path to the log file
|
||||
LOG_FILE_PATH = os.path.join(os.path.dirname(__file__), 'Resurse', 'log.txt')
|
||||
|
||||
# Configure the logger to write to the log file
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, # Set the logging level to INFO
|
||||
format='[%(levelname)s] [%(name)s] %(message)s', # Format for log messages
|
||||
handlers=[
|
||||
logging.FileHandler(LOG_FILE_PATH, mode='a'), # Append logs to the file
|
||||
logging.StreamHandler() # Also log to the console
|
||||
]
|
||||
)
|
||||
|
||||
# Create a shared logger instance
|
||||
Logger = logging.getLogger('SignageApp')
|
||||
@@ -9,7 +9,7 @@ from kivy.clock import Clock # Import Clock for scheduling tasks
|
||||
from kivy.core.window import Window # Import Window for handling window events
|
||||
from kivy.uix.video import Video # Import Video widget for video playback
|
||||
from kivy.uix.image import Image # Import Image widget for displaying images
|
||||
from kivy.logger import Logger # Import Logger for logging messages
|
||||
from kivy.logger import Logger, LoggerHistory # Import Logger for logging messages
|
||||
from kivy.lang import Builder # Import Builder for loading KV files
|
||||
from kivy.animation import Animation # Import Animation for fade effects
|
||||
from kivy.uix.popup import Popup
|
||||
@@ -22,9 +22,11 @@ import json # Import json for handling JSON data
|
||||
import datetime # Import datetime for timestamping logs
|
||||
import subprocess
|
||||
import requests
|
||||
import logging
|
||||
|
||||
# Import functions from python_functions.py
|
||||
from python_functions import load_local_playlist, download_media_files, clean_unused_files,save_local_playlist, fetch_server_playlist
|
||||
from logging_config import Logger # Import the shared logger
|
||||
|
||||
# Load the KV file for UI layout
|
||||
Builder.load_file('kv/media_player.kv')
|
||||
@@ -32,6 +34,7 @@ Builder.load_file('kv/media_player.kv')
|
||||
# Path to the configuration file
|
||||
CONFIG_FILE = './Resurse/app_config.txt'
|
||||
|
||||
|
||||
class MediaPlayer(Screen):
|
||||
# Main screen for media playback.
|
||||
|
||||
@@ -443,6 +446,27 @@ class SettingsScreen(Screen):
|
||||
"playlist_version": 0 # Default playlist version
|
||||
}
|
||||
|
||||
def load_log_messages(self):
|
||||
"""Load the last 10 log messages and update the label."""
|
||||
log_file_path = os.path.join(os.path.dirname(__file__), 'Resurse', 'log.txt')
|
||||
if not os.path.exists(log_file_path):
|
||||
self.ids.log_messages_label.text = "No log messages available."
|
||||
Logger.warning("SettingsScreen: Log file not found.")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(log_file_path, 'r') as log_file:
|
||||
lines = log_file.readlines()
|
||||
# Get the last 10 log messages
|
||||
last_messages = lines[-10:] if len(lines) > 10 else lines
|
||||
# Format the messages for display
|
||||
formatted_messages = "\n".join([line.strip() for line in last_messages])
|
||||
self.ids.log_messages_label.text = formatted_messages
|
||||
Logger.info("SettingsScreen: Log messages loaded successfully.")
|
||||
except Exception as e:
|
||||
self.ids.log_messages_label.text = "Failed to load log messages."
|
||||
Logger.error(f"SettingsScreen: Error loading log messages: {e}")
|
||||
|
||||
def save_config(self):
|
||||
"""Save the configuration to the config file."""
|
||||
self.config_data["screen_orientation"] = self.ids.orientation_input.text
|
||||
@@ -470,6 +494,9 @@ class SettingsScreen(Screen):
|
||||
self.ids.screen_width_input.text = self.config_data.get("screen_w", "")
|
||||
self.ids.screen_height_input.text = self.config_data.get("screen_h", "")
|
||||
|
||||
# Load the last 10 log messages
|
||||
self.load_log_messages()
|
||||
|
||||
def show_exit_popup(self):
|
||||
# Create the popup layout
|
||||
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
||||
@@ -559,16 +586,7 @@ class MediaPlayerApp(App):
|
||||
"""Switch to the SettingsScreen."""
|
||||
self.root.current = 'settings'
|
||||
|
||||
def convert_video_to_mp4(input_path, output_path):
|
||||
"""Convert a video to H.264 MP4 format."""
|
||||
try:
|
||||
subprocess.run(
|
||||
['ffmpeg', '-i', input_path, '-vcodec', 'libx264', '-acodec', 'aac', '-strict', 'experimental', output_path],
|
||||
check=True
|
||||
)
|
||||
Logger.info(f"Converted video: {input_path} to {output_path}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.error(f"Failed to convert video: {input_path}. Error: {e}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
MediaPlayerApp().run() # Run the app
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from kivy.logger import Logger
|
||||
from logging_config import Logger # Import the shared logger
|
||||
import bcrypt
|
||||
import time
|
||||
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
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.")
|
||||
@@ -1 +1 @@
|
||||
{"playlist": [{"file_name": "123.jpeg", "url": "/home/pi/Desktop/signage-player/src/static/resurse/123.jpeg", "duration": 30}], "version": 5}
|
||||
{"playlist": [{"file_name": "20250417_09h25m37s_grim.png", "url": "/home/pi/Desktop/signage-player/src/static/resurse/20250417_09h25m37s_grim.png", "duration": 20}], "version": 6}
|
||||
BIN
src/static/resurse/20250417_09h25m37s_grim.png
Normal file
BIN
src/static/resurse/20250417_09h25m37s_grim.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 374 KiB |
Reference in New Issue
Block a user