commit ce4440bf6e0e904c736c6e70afb76471f919cb3d Author: ske087 Date: Fri Aug 22 06:10:33 2025 +0100 Initial commit after repository repair and requirements update diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eba74f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv/ \ No newline at end of file diff --git a/How to use.txt b/How to use.txt new file mode 100644 index 0000000..e5902ae --- /dev/null +++ b/How to use.txt @@ -0,0 +1,84 @@ + +Here is the complete content for the `How to use.txt` file, starting from **point 3** and including all the missing information: + +```plaintext +### How to Install, Start, and Use the Signage Player Application + +This guide provides step-by-step instructions for installing, starting, and using the signage player application. + +--- + +## 3. Using the Application + +### MediaPlayer Screen +- **Play/Pause Button**: + - Toggles between playing and pausing the media. + - Automatically resumes playback after 30 seconds if not toggled again. +- **Next/Previous Buttons**: + - Navigate to the next or previous media in the playlist. +- **Exit App Button**: + - Opens a password-protected popup. Enter the `quickconnect_key` from the configuration file to exit the app. + +### Settings Screen +- **Configuration Fields**: + - Update fields like `screen_orientation`, `screen_name`, `quickconnect_key`, `server_ip`, and `port`. +- **Save Button**: + - Saves the updated configuration and returns to the MediaPlayer screen. +- **Exit App Button**: + - Opens a password-protected popup. Enter the `quickconnect_key` to exit the app. + +--- + +## 4. Configuration + +### Configuration File +The configuration file is located at: +```bash +app_config.txt +``` + +### Example Configuration +```json +{ + "screen_orientation": "Landscape", + "screen_name": "MyScreen", + "quickconnect_key": "12345", + "server_ip": "192.168.1.1", + "port": "8080" +} +``` + +### Updating Configuration +1. Navigate to the **Settings Screen** in the app. +2. Update the fields and click **Save**. +3. The configuration will be saved to `app_config.txt`. + +--- + +## 5. Troubleshooting + +### Permission Denied for `run_app.sh` +- Ensure the script is executable: + ```bash + chmod +x /home/pi/Desktop/signage-player/run_app.sh + ``` + +### Application Does Not Start on Boot +- Verify the `~/.bashrc` or `crontab` entry is correct. +- Check the script's permissions and paths. + +### Media Files Not Playing +- Ensure media files are downloaded to the correct directory: + ```bash + /home/pi/Desktop/signage-player/src/static/resurse + ``` +- Check the playlist configuration and server connection. + +### Password for Exit App +- The password is the `quickconnect_key` from the configuration file (`app_config.txt`). + +--- + +Let me know if you need further clarification or additional details! +``` + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1756bc5 --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# ๐ŸŽฅ Kivy Media Player + +A media player application built using **Kivy** that allows users to play video files, display images, and manage settings for quick connect codes and server configurations. The application checks for updates to the playlist every five minutes while running in full-screen mode. + +--- + +## ๐Ÿ“‚ Project Structure + +``` +signage-player +โ”œโ”€โ”€ src +โ”‚ โ”œโ”€โ”€ media_player.py # Handles media playback +โ”‚ โ”œโ”€โ”€ python_functions.py # Utility functions for downloading files and updating playlists +โ”‚ โ”œโ”€โ”€ kv +โ”‚ โ”‚ โ””โ”€โ”€ media_player.kv # Layout for the media player screen +โ”‚ โ”œโ”€โ”€ Resurse +โ”‚ โ”‚ โ”œโ”€โ”€ app_config.txt # Configuration file +โ”‚ โ”‚ โ”œโ”€โ”€ log.txt # Log file for media events +โ”‚ โ”‚ โ”œโ”€โ”€ play.png # Play button icon +โ”‚ โ”‚ โ”œโ”€โ”€ pause.png # Pause button icon +โ”‚ โ”‚ โ””โ”€โ”€ other icons... # Additional icons for the UI +โ”‚ โ””โ”€โ”€ static +โ”‚ โ””โ”€โ”€ resurse # Directory for media files (images/videos) +โ”œโ”€โ”€ requirements.txt # Project dependencies +โ”œโ”€โ”€ run_app.sh # Script to run the application +โ”œโ”€โ”€ install.sh # Installation script +โ”œโ”€โ”€ How to use.txt # Detailed usage instructions +โ””โ”€โ”€ README.md # Documentation for the project +``` + +--- + +## ๐Ÿš€ Setup Instructions + +### Prerequisites +Before installing the application, ensure the following are installed on your Raspberry Pi: +- **Python 3.7 or higher** +- **pip3** (Python package manager) +- **ffmpeg** (for video conversion) +- **Internet connection** for downloading dependencies + +### Installation Steps + +1. **Clone the Repository** + ```bash + git clone https://gitea.moto-adv.com/ske087/signage-player.git + cd signage-player + ``` + +2. **Run the Installation Script** + ```bash + ./install.sh + ``` + + The installation script will: + - Update the system. + - Install required Python and system dependencies. + - Clone the repository to `/home/pi/Desktop/ds-player`. + - Add the run_app.sh script to `~/.bashrc` for autostart. + - Make the run_app.sh script executable. + +3. **Reboot the Device** + After installation, reboot the Raspberry Pi to start the application automatically: + ```bash + sudo reboot + ``` + +--- + +## ๐ŸŽฎ How to Use + +### MediaPlayer Screen +- **โ–ถ๏ธ Play/Pause Button**: + - Toggles between playing and pausing the media. + - Automatically resumes playback after 30 seconds if not toggled again. +- **โฉ Next/โช Previous Buttons**: + - Navigate to the next or previous media in the playlist. +- **โŒ Exit App Button**: + - Opens a password-protected popup. Enter the `quickconnect_key` from the configuration file to exit the app. + +### Settings Screen +- **Configuration Fields**: + - Update fields like `screen_orientation`, `screen_name`, `quickconnect_key`, `server_ip`, and `port`. +- **๐Ÿ’พ Save Button**: + - Saves the updated configuration and returns to the MediaPlayer screen. +- **โŒ Exit App Button**: + - Opens a password-protected popup. Enter the `quickconnect_key` to exit the app. + +--- + +## โš™๏ธ Configuration + +### Configuration File +The configuration file is located at: +```bash +/home/pi/Desktop/signage-player/src/Resurse/app_config.txt +``` + +### Example Configuration +```json +{ + "screen_orientation": "Landscape", + "screen_name": "tv-panou1", + "quickconnect_key": "8887779", + "server_ip": "172.20.10.9", + "port": "80" +} +``` + +### Updating Configuration +1. Navigate to the **Settings Screen** in the app. +2. Update the fields and click **Save**. +3. The configuration will be saved to app_config.txt. + +--- + +## ๐Ÿ› ๏ธ Troubleshooting + +### Permission Denied for run_app.sh +- Ensure the script is executable: + ```bash + chmod +x /home/pi/Desktop/signage-player/run_app.sh + ``` + +### Application Does Not Start on Boot +- Verify the `~/.bashrc` entry is correct. +- Check the script's permissions and paths. + +### Media Files Not Playing +- Ensure media files are downloaded to the correct directory: + ```bash + /home/pi/Desktop/signage-player/src/static/resurse + ``` +- Check the playlist configuration and server connection. + +### Password for Exit App +- The password is the `quickconnect_key` from the configuration file (`app_config.txt`). + +--- + +## ๐Ÿค Contributing + +Contributions are welcome! Please feel free to submit a pull request or open an issue for any suggestions or improvements. + +--- + +## ๐Ÿ“œ License + +This project is licensed under the **MIT License**. See the `LICENSE` file for more details. +```` \ No newline at end of file diff --git a/install_tkinter.sh b/install_tkinter.sh new file mode 100755 index 0000000..0707917 --- /dev/null +++ b/install_tkinter.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# Tkinter Media Player Installation Script + +echo "Installing Tkinter Media Player..." + +# Update system packages +echo "Updating system packages..." +sudo apt update +sudo apt upgrade -y + +# Install system dependencies +echo "Installing system dependencies..." +sudo apt install -y python3 python3-pip python3-venv python3-tk +sudo apt install -y ffmpeg libopencv-dev python3-opencv +sudo apt install -y libsdl2-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-ttf-dev +sudo apt install -y libjpeg-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev + +# Create project directory if it doesn't exist +PROJECT_DIR="/home/pi/Desktop/signage-player" +if [ ! -d "$PROJECT_DIR" ]; then + echo "Project directory not found. Please ensure the signage-player directory exists." + exit 1 +fi + +cd "$PROJECT_DIR" + +# Create virtual environment +echo "Creating Python virtual environment..." +python3 -m venv venv + +# Activate virtual environment and install requirements +echo "Installing Python dependencies..." +source venv/bin/activate +pip install --upgrade pip +pip install -r tkinter_requirements.txt +deactivate + +# Make launcher script executable +chmod +x run_tkinter_app.sh + +# Create systemd service for auto-start +echo "Creating systemd service..." +sudo tee /etc/systemd/system/tkinter-signage-player.service > /dev/null </dev/null; then + echo -e "${GREEN}โœ“${NC} Python package '$1' is available" + else + echo -e "${RED}โœ—${NC} Python package '$1' is NOT available" + ((ERRORS++)) + fi +} + +echo "Checking system packages..." +check_system_package "python3-dev" +check_system_package "python3-opencv" +check_system_package "libsdl2-dev" +check_system_package "libjpeg-dev" + +echo "" +echo "Checking Python packages..." +check_python_package "cv2" +check_python_package "pygame" +check_python_package "PIL" +check_python_package "requests" + +echo "" +echo "Checking application files..." +if [ -f "tkinter_app/src/main.py" ]; then + echo -e "${GREEN}โœ“${NC} Main application file exists" +else + echo -e "${RED}โœ—${NC} Main application file missing" + ((ERRORS++)) +fi + +if [ -f "tkinter_app/src/tkinter_simple_player.py" ]; then + echo -e "${GREEN}โœ“${NC} Player module exists" +else + echo -e "${RED}โœ—${NC} Player module missing" + ((ERRORS++)) +fi + +if [ -d "venv" ]; then + echo -e "${GREEN}โœ“${NC} Virtual environment exists" +else + echo -e "${YELLOW}โš ${NC} Virtual environment not found" +fi + +echo "" +if [ $ERRORS -eq 0 ]; then + echo -e "${GREEN}=== All dependencies are properly installed! ===${NC}" + echo "You can run the application with: ./run_tkinter_debug.sh" +else + echo -e "${RED}=== Found $ERRORS issues ===${NC}" + echo "Run the installation script: src/scripts/install_offline.sh" +fi \ No newline at end of file diff --git a/src/scripts/install_offline.sh b/src/scripts/install_offline.sh new file mode 100755 index 0000000..19d8b40 --- /dev/null +++ b/src/scripts/install_offline.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# Offline Installation Script for Signage Player +# This script installs all dependencies and sets up the application completely offline + +set -e # Exit on any error + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" + +echo "=== Signage Player Offline Installation ===" +echo "Project root: $PROJECT_ROOT" + +# Check if running as root for system packages +if [[ $EUID -eq 0 ]]; then + echo "ERROR: Please run this script as a regular user, not as root." + echo "The script will prompt for sudo when needed." + exit 1 +fi + +# Function to check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Install system packages +echo "Step 1: Installing system packages..." +if command_exists apt; then + echo "Installing APT packages..." + sudo apt update + + # Read and install packages from the list + while IFS= read -r package; do + # Skip comments and empty lines + if [[ ! "$package" =~ ^[[:space:]]*# ]] && [[ -n "${package// }" ]]; then + echo "Installing: $package" + sudo apt install -y "$package" || echo "Warning: Could not install $package" + fi + done < "$SCRIPT_DIR/../system_packages/apt_packages.txt" +else + echo "ERROR: apt package manager not found. This script is designed for Debian/Ubuntu systems." + exit 1 +fi + +# Create virtual environment +echo "Step 2: Creating virtual environment..." +cd "$PROJECT_ROOT" +if [ ! -d "venv" ]; then + python3 -m venv --system-site-packages venv + echo "Virtual environment created with system site packages access." +else + echo "Removing existing virtual environment and creating new one with system site packages..." + rm -rf venv + python3 -m venv --system-site-packages venv + echo "Virtual environment recreated with system site packages access." +fi + +# Activate virtual environment +echo "Step 3: Activating virtual environment..." +source venv/bin/activate + +# Install Python packages from offline wheels +echo "Step 4: Installing Python packages from offline wheels..." +OFFLINE_PACKAGES_DIR="$PROJECT_ROOT/src/offline_packages" +if [ -d "$OFFLINE_PACKAGES_DIR" ]; then + pip install --upgrade pip + pip install --no-index --find-links "$OFFLINE_PACKAGES_DIR" \ + requests pillow pygame certifi charset_normalizer idna urllib3 + echo "Python packages installed successfully." +else + echo "ERROR: Offline packages directory not found at: $OFFLINE_PACKAGES_DIR" + exit 1 +fi + +# Copy shared modules to tkinter app +echo "Step 5: Setting up shared modules..." +SHARED_MODULES_DIR="$PROJECT_ROOT/src/shared_modules" +TKINTER_APP_DIR="$PROJECT_ROOT/tkinter_app/src" +if [ -d "$SHARED_MODULES_DIR" ]; then + cp "$SHARED_MODULES_DIR"/*.py "$TKINTER_APP_DIR"/ 2>/dev/null || echo "Shared modules already in place." + echo "Shared modules configured." +else + echo "Warning: Shared modules directory not found at: $SHARED_MODULES_DIR" +fi + +# Create necessary directories +echo "Step 6: Creating application directories..." +mkdir -p "$PROJECT_ROOT/tkinter_app/resources/static/resurse" +mkdir -p "$PROJECT_ROOT/tkinter_app/src/static/resurse" + +# Set permissions +echo "Step 7: Setting permissions..." +chmod +x "$PROJECT_ROOT/run_tkinter_debug.sh" 2>/dev/null || true +chmod +x "$PROJECT_ROOT/install_tkinter.sh" 2>/dev/null || true +chmod +x "$PROJECT_ROOT/src/scripts"/*.sh 2>/dev/null || true + +echo "" +echo "=== Installation Complete! ===" +echo "" +echo "To run the signage player:" +echo " ./run_tkinter_debug.sh" +echo "" +echo "To configure settings, run the app and click the Settings button." +echo "" +echo "The application has been set up with:" +echo " - All system dependencies" +echo " - Python virtual environment with required packages" +echo " - Shared modules properly configured" +echo " - Necessary directories created" +echo "" \ No newline at end of file diff --git a/src/shared_modules/logging_config.py b/src/shared_modules/logging_config.py new file mode 100644 index 0000000..8710312 --- /dev/null +++ b/src/shared_modules/logging_config.py @@ -0,0 +1,27 @@ +import logging +import os + +# Path to the log file +# Update the path to point to the new resources directory +LOG_FILE_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'log.txt') + +# Create a logger instance +Logger = logging.getLogger('SignageApp') +Logger.setLevel(logging.INFO) # Set the logging level to INFO + +# Create a file handler to write logs to the log.txt file +file_handler = logging.FileHandler(LOG_FILE_PATH, mode='a') # Append logs to the file +file_handler.setLevel(logging.INFO) + +# Create a formatter for the log messages +formatter = logging.Formatter('[%(levelname)s] [%(name)s] %(message)s') +file_handler.setFormatter(formatter) + +# Add the file handler to the logger +Logger.addHandler(file_handler) + +# Optionally, add a stream handler to log messages to the console +stream_handler = logging.StreamHandler() +stream_handler.setLevel(logging.INFO) +stream_handler.setFormatter(formatter) +Logger.addHandler(stream_handler) \ No newline at end of file diff --git a/src/shared_modules/python_functions.py b/src/shared_modules/python_functions.py new file mode 100644 index 0000000..279b46e --- /dev/null +++ b/src/shared_modules/python_functions.py @@ -0,0 +1,196 @@ +import os +import json +import requests +from logging_config import Logger # Import the shared logger +import bcrypt +import time + +# Update paths to use the new directory structure +CONFIG_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'app_config.txt') +LOCAL_PLAYLIST_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'local_playlist.json') + +def load_config(): + """Load configuration from app_config.txt.""" + Logger.info("python_functions: Starting load_config function.") + 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 {} + else: + Logger.error(f"python_functions: Configuration file {CONFIG_FILE} not found.") + return {} + Logger.info("python_functions: Finished load_config function.") + +# 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", "") + +Logger.info(f"python_functions: Configuration loaded: server={server}, host={host}, quick={quick}, port={port}") + +def load_local_playlist(): + """Load the playlist and version from local storage.""" + Logger.info("python_functions: Starting load_local_playlist function.") + 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: + Logger.info("python_functions: Finished load_local_playlist function successfully.") + return local_playlist # Return the full playlist data + else: + Logger.error("python_functions: Invalid local playlist structure.") + return {'playlist': [], 'version': 0} + except json.JSONDecodeError as e: + Logger.error(f"python_functions: Failed to parse local playlist file. Error: {e}") + return {'playlist': [], 'version': 0} + else: + Logger.warning("python_functions: Local playlist file not found.") + return {'playlist': [], 'version': 0} + Logger.info("python_functions: Finished load_local_playlist function.") + +def save_local_playlist(playlist): + """Save the updated playlist locally.""" + Logger.info("python_functions: Starting save_local_playlist function.") + Logger.debug(f"python_functions: Playlist to save: {playlist}") + 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, indent=4) # Ensure proper formatting + 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}") + Logger.info("python_functions: Finished save_local_playlist function.") + +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 + } + Logger.info(f"Fetching playlist from URL: {url} with params: {params}") + response = requests.get(url, params=params) + + if response.status_code == 200: + response_data = response.json() + Logger.info(f"Server response: {response_data}") + 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: + if bcrypt.checkpw(quick.encode('utf-8'), hashed_quickconnect.encode('utf-8')): + Logger.info("Fetched updated playlist from server.") + + # Update the playlist version in app_config.txt + update_config_playlist_version(version) + + return {'playlist': playlist, 'version': version} + else: + Logger.error("Quickconnect code validation failed.") + else: + Logger.error("Failed to retrieve playlist or hashed quickconnect from the response.") + else: + Logger.error(f"Failed to fetch playlist. Status Code: {response.status_code}") + except requests.exceptions.RequestException as e: + Logger.error(f"Failed to fetch playlist: {e}") + + return {'playlist': [], 'version': 0} + +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') # 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 + local_path = os.path.join(base_dir, file_name) # Local file path + + Logger.debug(f"python_functions: Preparing to download {file_name} from {file_url}...") + + if os.path.exists(local_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(local_path, 'wb') as file: + file.write(response.content) + Logger.info(f"python_functions: Successfully downloaded {file_name} to {local_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 playlist entry to point to the local file path + updated_media = { + 'file_name': file_name, + 'url': f"static/resurse/{file_name}", # Update URL to local path + 'duration': duration + } + Logger.debug(f"python_functions: Updated media entry: {updated_media}") + updated_playlist.append(updated_media) + + # Save the updated playlist locally + save_local_playlist({'playlist': updated_playlist, 'version': version}) + Logger.info("python_functions: Finished media file download and updated local playlist.") + +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') + if not os.path.exists(base_dir): + Logger.debug(f"python_functions: Directory {base_dir} does not exist. No files to clean.") + return + + playlist_files = {media.get('file_name', '') for media in playlist} + all_files = set(os.listdir(base_dir)) + unused_files = all_files - playlist_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 update_config_playlist_version(version): + """Update the playlist version in app_config.txt.""" + if not os.path.exists(CONFIG_FILE): + Logger.error(f"python_functions: Configuration file {CONFIG_FILE} not found.") + return + + try: + with open(CONFIG_FILE, 'r') as file: + config_data = json.load(file) + + config_data['playlist_version'] = version # Add or update the playlist version + + with open(CONFIG_FILE, 'w') as file: + json.dump(config_data, file, indent=4) + Logger.info(f"python_functions: Updated playlist version in app_config.txt to {version}.") + except (IOError, json.JSONDecodeError) as e: + Logger.error(f"python_functions: Failed to update playlist version in app_config.txt. Error: {e}") \ No newline at end of file diff --git a/src/system_packages/apt_packages.txt b/src/system_packages/apt_packages.txt new file mode 100644 index 0000000..162f745 --- /dev/null +++ b/src/system_packages/apt_packages.txt @@ -0,0 +1,43 @@ +#!/bin/bash +# System Package Dependencies for Signage Player +# These packages need to be installed via apt before installing Python packages + +# Core system packages +python3-dev +python3-pip +python3-venv +python3-setuptools +python3-wheel + +# OpenCV system dependencies +libopencv-dev +python3-opencv +libopencv-core-dev +libopencv-imgproc-dev +libopencv-imgcodecs-dev +libopencv-videoio-dev + +# Audio/Video libraries +libasound2-dev +libsdl2-dev +libsdl2-image-dev +libsdl2-mixer-dev +libsdl2-ttf-dev +libfreetype6-dev +libportmidi-dev + +# Image processing libraries +libjpeg-dev +libpng-dev +libtiff-dev +libwebp-dev +libopenjp2-7-dev + +# Build tools (may be needed for some packages) +build-essential +cmake +pkg-config + +# Networking +curl +wget \ No newline at end of file diff --git a/test_centering.py b/test_centering.py new file mode 100644 index 0000000..a4e75fd --- /dev/null +++ b/test_centering.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +""" +Test script to verify window centering functionality +""" +import tkinter as tk +import sys +import os +sys.path.append('tkinter_app/src') +from tkinter_simple_player import SettingsWindow, SimpleMediaPlayerApp + +def test_settings_centering(): + """Test settings window centering""" + root = tk.Tk() + root.withdraw() # Hide main window + + # Create a mock app object + class MockApp: + def __init__(self): + self.playlist = [] + self.current_index = 0 + + def play_current_media(self): + print('play_current_media called') + + app = MockApp() + + # Test settings window centering + try: + print("Testing settings window centering...") + settings = SettingsWindow(root, app) + + # Get screen dimensions + screen_width = settings.window.winfo_screenwidth() + screen_height = settings.window.winfo_screenheight() + + # Get window position + settings.window.update_idletasks() + window_x = settings.window.winfo_x() + window_y = settings.window.winfo_y() + window_width = 900 + window_height = 700 + + # Calculate expected center position + expected_x = (screen_width - window_width) // 2 + expected_y = (screen_height - window_height) // 2 + + print(f"Screen size: {screen_width}x{screen_height}") + print(f"Window position: {window_x}, {window_y}") + print(f"Expected center: {expected_x}, {expected_y}") + print(f"Window size: {window_width}x{window_height}") + + # Check if window is roughly centered (allow some margin for window decorations) + margin = 50 + is_centered_x = abs(window_x - expected_x) <= margin + is_centered_y = abs(window_y - expected_y) <= margin + + if is_centered_x and is_centered_y: + print("โœ… Settings window is properly centered!") + else: + print("โŒ Settings window centering needs adjustment") + + # Keep window open for 3 seconds to visually verify + root.after(3000, root.quit) + root.mainloop() + + except Exception as e: + print(f"โŒ Error testing settings window: {e}") + +def test_exit_dialog_centering(): + """Test exit dialog centering""" + print("\nTesting exit dialog centering...") + + # Create a simple test for the centering function + root = tk.Tk() + root.withdraw() + + # Create a test dialog + dialog = tk.Toplevel(root) + dialog.title("Test Exit Dialog") + dialog.geometry("400x200") + dialog.configure(bg='#2d2d2d') + dialog.resizable(False, False) + + # Test the centering logic + dialog.update_idletasks() + screen_width = dialog.winfo_screenwidth() + screen_height = dialog.winfo_screenheight() + dialog_width = 400 + dialog_height = 200 + + # Calculate center position + center_x = int((screen_width - dialog_width) / 2) + center_y = int((screen_height - dialog_height) / 2) + + # Ensure the dialog doesn't go off-screen + center_x = max(0, min(center_x, screen_width - dialog_width)) + center_y = max(0, min(center_y, screen_height - dialog_height)) + + dialog.geometry(f"{dialog_width}x{dialog_height}+{center_x}+{center_y}") + dialog.lift() + + # Add test content + tk.Label(dialog, text="๐ŸŽฌ Test Exit Dialog", + font=('Arial', 16, 'bold'), + fg='white', bg='#2d2d2d').pack(pady=20) + + tk.Label(dialog, text="This dialog should be centered on screen", + font=('Arial', 12), + fg='white', bg='#2d2d2d').pack(pady=10) + + # Get actual position + dialog.update_idletasks() + actual_x = dialog.winfo_x() + actual_y = dialog.winfo_y() + + print(f"Screen size: {screen_width}x{screen_height}") + print(f"Dialog position: {actual_x}, {actual_y}") + print(f"Expected center: {center_x}, {center_y}") + + # Check centering + margin = 50 + is_centered_x = abs(actual_x - center_x) <= margin + is_centered_y = abs(actual_y - center_y) <= margin + + if is_centered_x and is_centered_y: + print("โœ… Exit dialog is properly centered!") + else: + print("โŒ Exit dialog centering needs adjustment") + + # Close dialog after 3 seconds + root.after(3000, root.quit) + root.mainloop() + +if __name__ == "__main__": + print("๐Ÿงช Testing Window Centering Functionality") + print("=" * 50) + + test_settings_centering() + test_exit_dialog_centering() + + print("\nโœ… Centering tests completed!") + print("\nThe windows should appear centered on your screen regardless of resolution.") + print("This works for any screen size: 1024x768, 1920x1080, 4K, etc.") diff --git a/test_image_display.py b/test_image_display.py new file mode 100644 index 0000000..f221d0f --- /dev/null +++ b/test_image_display.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +""" +Test script to verify image display functionality +""" +import tkinter as tk +from PIL import Image, ImageTk +import os + +def test_image_display(): + # Create a simple tkinter window + root = tk.Tk() + root.title("Image Display Test") + root.geometry("800x600") + root.configure(bg='black') + + # Create image label + image_label = tk.Label(root, bg='black') + image_label.pack(fill=tk.BOTH, expand=True) + + # Test image path + test_image = "/home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg" + + try: + if os.path.exists(test_image): + print(f"Loading image: {test_image}") + + # Load and display image + img = Image.open(test_image) + img.thumbnail((800, 600), Image.LANCZOS) + photo = ImageTk.PhotoImage(img) + + image_label.config(image=photo) + image_label.image = photo # Keep reference + + print(f"Image loaded successfully: {img.size}") + + # Close after 3 seconds + root.after(3000, root.quit) + + else: + print(f"Image file not found: {test_image}") + image_label.config(text="Image file not found", fg='white') + root.after(2000, root.quit) + + except Exception as e: + print(f"Error loading image: {e}") + image_label.config(text=f"Error: {e}", fg='red') + root.after(2000, root.quit) + + root.mainloop() + print("Image display test completed") + +if __name__ == "__main__": + test_image_display() diff --git a/test_imports.py b/test_imports.py new file mode 100644 index 0000000..7986826 --- /dev/null +++ b/test_imports.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import sys +print("Testing imports...") + +try: + import tkinter as tk + print("โœ“ tkinter imported successfully") +except Exception as e: + print(f"โœ— tkinter import failed: {e}") + +try: + from PIL import Image, ImageTk + print("โœ“ PIL and ImageTk imported successfully") +except Exception as e: + print(f"โœ— PIL import failed: {e}") + +try: + from virtual_keyboard import VirtualKeyboard + print("โœ“ Virtual keyboard imported successfully") +except Exception as e: + print(f"โœ— Virtual keyboard import failed: {e}") + +try: + from python_functions import load_local_playlist + print("โœ“ Python functions imported successfully") + + # Test loading playlist + playlist_data = load_local_playlist() + playlist = playlist_data.get('playlist', []) + print(f"โœ“ Local playlist loaded: {len(playlist)} items") + + for i, item in enumerate(playlist): + print(f" {i+1}. {item.get('file_name', 'Unknown')}") + +except Exception as e: + print(f"โœ— Python functions import/execution failed: {e}") + +print("Import test completed") diff --git a/test_scaling.py b/test_scaling.py new file mode 100644 index 0000000..5d62495 --- /dev/null +++ b/test_scaling.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +Test script for full-screen image scaling functionality +""" +import tkinter as tk +from PIL import Image, ImageTk +import os + +def test_scaling_modes(): + """Test different scaling modes for images""" + + def scale_image_to_screen(img, screen_width, screen_height, mode='fit'): + """Test scaling function""" + img_width, img_height = img.size + + if mode == 'stretch': + return img.resize((screen_width, screen_height), Image.LANCZOS), (0, 0) + + elif mode == 'fill': + screen_ratio = screen_width / screen_height + img_ratio = img_width / img_height + + if img_ratio > screen_ratio: + new_height = screen_height + new_width = int(screen_height * img_ratio) + x_offset = (screen_width - new_width) // 2 + y_offset = 0 + else: + new_width = screen_width + new_height = int(screen_width / img_ratio) + x_offset = 0 + y_offset = (screen_height - new_height) // 2 + + img_resized = img.resize((new_width, new_height), Image.LANCZOS) + final_img = Image.new('RGB', (screen_width, screen_height), 'black') + + if new_width > screen_width: + crop_x = (new_width - screen_width) // 2 + img_resized = img_resized.crop((crop_x, 0, crop_x + screen_width, new_height)) + x_offset = 0 + if new_height > screen_height: + crop_y = (new_height - screen_height) // 2 + img_resized = img_resized.crop((0, crop_y, new_width, crop_y + screen_height)) + y_offset = 0 + + final_img.paste(img_resized, (x_offset, y_offset)) + return final_img, (x_offset, y_offset) + + else: # fit mode + screen_ratio = screen_width / screen_height + img_ratio = img_width / img_height + + if img_ratio > screen_ratio: + new_width = screen_width + new_height = int(screen_width / img_ratio) + else: + new_height = screen_height + new_width = int(screen_height * img_ratio) + + img_resized = img.resize((new_width, new_height), Image.LANCZOS) + final_img = Image.new('RGB', (screen_width, screen_height), 'black') + x_offset = (screen_width - new_width) // 2 + y_offset = (screen_height - new_height) // 2 + final_img.paste(img_resized, (x_offset, y_offset)) + + return final_img, (x_offset, y_offset) + + # Test image path + test_image = "/home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg" + + if not os.path.exists(test_image): + print(f"Test image not found: {test_image}") + return + + try: + # Load test image + img = Image.open(test_image) + original_size = img.size + screen_width, screen_height = 800, 600 + + print(f"Testing scaling modes for image: {original_size}") + print(f"Target screen size: {screen_width}x{screen_height}") + + # Test each scaling mode + modes = ['fit', 'fill', 'stretch'] + + for mode in modes: + final_img, offset = scale_image_to_screen(img, screen_width, screen_height, mode) + print(f"{mode.upper()} mode: Final size: {final_img.size}, Offset: {offset}") + + print("โœ… All scaling modes tested successfully!") + + except Exception as e: + print(f"โŒ Error testing scaling: {e}") + +if __name__ == "__main__": + test_scaling_modes() diff --git a/test_touch.py b/test_touch.py new file mode 100644 index 0000000..ab3b82d --- /dev/null +++ b/test_touch.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +""" +Touch Display Test - Test the touch-optimized interface with virtual keyboard +""" +import tkinter as tk +import sys +import os +sys.path.append('tkinter_app/src') + +def test_touch_interface(): + """Test the touch-optimized settings interface""" + try: + from tkinter_simple_player import SettingsWindow + + # Create main window + root = tk.Tk() + root.title("๐ŸŽฎ Touch Display Test") + root.geometry("1024x768") + root.configure(bg='#2c3e50') + + # Create welcome screen + welcome_frame = tk.Frame(root, bg='#2c3e50', padx=40, pady=40) + welcome_frame.pack(fill=tk.BOTH, expand=True) + + # Title + title_label = tk.Label(welcome_frame, + text="๐ŸŽฌ Touch Display Digital Signage", + font=('Segoe UI', 24, 'bold'), + fg='white', bg='#2c3e50') + title_label.pack(pady=30) + + # Description + desc_text = ( + "Touch-Optimized Features:\n\n" + "๐Ÿ“ฑ Virtual On-Screen Keyboard\n" + "๐ŸŽฏ Larger Touch-Friendly Buttons\n" + "โŒจ๏ธ Auto-Show Keyboard on Input Focus\n" + "๐Ÿ‘† Enhanced Touch Feedback\n" + "๐ŸŽจ Dark Theme Optimized for Displays\n\n" + "Click the button below to test the settings interface:" + ) + + desc_label = tk.Label(welcome_frame, text=desc_text, + font=('Segoe UI', 14), + fg='#ecf0f1', bg='#2c3e50', + justify=tk.CENTER) + desc_label.pack(pady=20) + + # Create mock app for testing + class MockApp: + def __init__(self): + self.playlist = [] + self.current_index = 0 + + def play_current_media(self): + print("Mock: play_current_media called") + + mock_app = MockApp() + + # Test button to open touch-optimized settings + def open_touch_settings(): + try: + settings = SettingsWindow(root, mock_app) + print("โœ… Touch-optimized settings window opened successfully!") + except Exception as e: + print(f"โŒ Error opening settings: {e}") + import traceback + traceback.print_exc() + + # Large touch-friendly button + settings_btn = tk.Button(welcome_frame, + text="๐Ÿ”ง Open Touch Settings", + command=open_touch_settings, + bg='#3498db', fg='white', + font=('Segoe UI', 16, 'bold'), + relief=tk.FLAT, padx=40, pady=20, + cursor='hand2') + settings_btn.pack(pady=30) + + # Instructions + instructions = ( + "Touch Instructions:\n" + "โ€ข Tap input fields to show virtual keyboard\n" + "โ€ข Use large buttons for easy touch interaction\n" + "โ€ข Virtual keyboard stays on top for easy access\n" + "โ€ข Click outside input fields to hide keyboard" + ) + + instr_label = tk.Label(welcome_frame, text=instructions, + font=('Segoe UI', 11), + fg='#bdc3c7', bg='#2c3e50', + justify=tk.LEFT) + instr_label.pack(pady=20) + + # Exit button + exit_btn = tk.Button(welcome_frame, + text="โŒ Exit Test", + command=root.quit, + bg='#e74c3c', fg='white', + font=('Segoe UI', 12, 'bold'), + relief=tk.FLAT, padx=30, pady=15, + cursor='hand2') + exit_btn.pack(pady=20) + + # Add touch feedback to buttons + def add_touch_feedback(button): + def on_press(e): + button.configure(relief=tk.SUNKEN) + def on_release(e): + button.configure(relief=tk.FLAT) + def on_enter(e): + button.configure(relief=tk.RAISED) + def on_leave(e): + button.configure(relief=tk.FLAT) + + button.bind("", on_press) + button.bind("", on_release) + button.bind("", on_enter) + button.bind("", on_leave) + + add_touch_feedback(settings_btn) + add_touch_feedback(exit_btn) + + print("๐ŸŽฎ Touch Display Test Started") + print("=" * 50) + print("Features being tested:") + print("- Virtual keyboard integration") + print("- Touch-optimized input fields") + print("- Large, finger-friendly buttons") + print("- Enhanced visual feedback") + print("- Dark theme for displays") + print("\nClick 'Open Touch Settings' to test the interface!") + + root.mainloop() + + except Exception as e: + print(f"โŒ Error in touch interface test: {e}") + import traceback + traceback.print_exc() + +def test_virtual_keyboard_standalone(): + """Test just the virtual keyboard component""" + try: + from virtual_keyboard import VirtualKeyboard, TouchOptimizedEntry, TouchOptimizedButton + + root = tk.Tk() + root.title("๐ŸŽน Virtual Keyboard Test") + root.geometry("800x500") + root.configure(bg='#2f3136') + + # Create virtual keyboard + vk = VirtualKeyboard(root, dark_theme=True) + + # Test interface + test_frame = tk.Frame(root, bg='#2f3136', padx=30, pady=30) + test_frame.pack(fill=tk.BOTH, expand=True) + + tk.Label(test_frame, text="๐ŸŽน Virtual Keyboard Test", + font=('Segoe UI', 20, 'bold'), + bg='#2f3136', fg='white').pack(pady=20) + + tk.Label(test_frame, text="Click on the input fields below to test the virtual keyboard:", + font=('Segoe UI', 12), + bg='#2f3136', fg='#b9bbbe').pack(pady=10) + + # Test input fields + tk.Label(test_frame, text="Server IP:", bg='#2f3136', fg='white', + font=('Segoe UI', 11, 'bold')).pack(anchor=tk.W, pady=(20, 5)) + entry1 = TouchOptimizedEntry(test_frame, vk, width=40, bg='#36393f', + fg='white', insertbackground='white') + entry1.pack(pady=5, fill=tk.X) + + tk.Label(test_frame, text="Device Name:", bg='#2f3136', fg='white', + font=('Segoe UI', 11, 'bold')).pack(anchor=tk.W, pady=(15, 5)) + entry2 = TouchOptimizedEntry(test_frame, vk, width=40, bg='#36393f', + fg='white', insertbackground='white') + entry2.pack(pady=5, fill=tk.X) + + tk.Label(test_frame, text="Password:", bg='#2f3136', fg='white', + font=('Segoe UI', 11, 'bold')).pack(anchor=tk.W, pady=(15, 5)) + entry3 = TouchOptimizedEntry(test_frame, vk, width=40, bg='#36393f', + fg='white', insertbackground='white', show='*') + entry3.pack(pady=5, fill=tk.X) + + # Control buttons + btn_frame = tk.Frame(test_frame, bg='#2f3136') + btn_frame.pack(pady=30) + + TouchOptimizedButton(btn_frame, text="๐ŸŽน Show Keyboard", + command=lambda: vk.show_keyboard(entry1), + bg='#7289da', fg='white').pack(side=tk.LEFT, padx=10) + + TouchOptimizedButton(btn_frame, text="โŒ Hide Keyboard", + command=vk.hide_keyboard, + bg='#ed4245', fg='white').pack(side=tk.LEFT, padx=10) + + TouchOptimizedButton(btn_frame, text="๐Ÿ”„ Clear All", + command=lambda: [e.delete(0, tk.END) for e in [entry1, entry2, entry3]], + bg='#faa61a', fg='white').pack(side=tk.LEFT, padx=10) + + print("๐ŸŽน Virtual Keyboard Test Started") + print("- Click input fields to auto-show keyboard") + print("- Type using virtual or physical keyboard") + print("- Test touch-friendly interface") + + root.mainloop() + + except Exception as e: + print(f"โŒ Error in virtual keyboard test: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Touch Display Tests") + parser.add_argument("--keyboard-only", action="store_true", + help="Test only the virtual keyboard component") + + args = parser.parse_args() + + print("๐ŸŽฎ Touch Display Digital Signage Tests") + print("=" * 50) + + if args.keyboard_only: + test_virtual_keyboard_standalone() + else: + test_touch_interface() + + print("\nโœ… Touch display tests completed!") diff --git a/tkinter_app/resources/app_config.txt b/tkinter_app/resources/app_config.txt new file mode 100644 index 0000000..6814d33 --- /dev/null +++ b/tkinter_app/resources/app_config.txt @@ -0,0 +1,10 @@ +{ + "screen_orientation": "Landscape", + "screen_name": "tv-terasa", + "quickconnect_key": "8887779", + "server_ip": "digi-signage.moto-adv.com", + "port": "8880", + "screen_w": "1920", + "screen_h": "1080", + "playlist_version": 5 +} \ No newline at end of file diff --git a/tkinter_app/resources/demo1.jpg b/tkinter_app/resources/demo1.jpg new file mode 100644 index 0000000..5f3b4b8 Binary files /dev/null and b/tkinter_app/resources/demo1.jpg differ diff --git a/tkinter_app/resources/demo2.jpeg b/tkinter_app/resources/demo2.jpeg new file mode 100644 index 0000000..29d8372 Binary files /dev/null and b/tkinter_app/resources/demo2.jpeg differ diff --git a/tkinter_app/resources/demo_playlist.json b/tkinter_app/resources/demo_playlist.json new file mode 100644 index 0000000..5982d7f --- /dev/null +++ b/tkinter_app/resources/demo_playlist.json @@ -0,0 +1,15 @@ +{ + "playlist": [ + { + "file_name": "demo1.jpg", + "url": "Resurse/demo1.jpg", + "duration": 20 + }, + { + "file_name": "demo2.jpg", + "url": "Resurse/demo2.jpg", + "duration": 20 + } + ], + "version": 1 +} \ No newline at end of file diff --git a/tkinter_app/resources/home_icon.png b/tkinter_app/resources/home_icon.png new file mode 100644 index 0000000..7be1c59 Binary files /dev/null and b/tkinter_app/resources/home_icon.png differ diff --git a/tkinter_app/resources/left-arrow-blue.png b/tkinter_app/resources/left-arrow-blue.png new file mode 100644 index 0000000..a4fc859 Binary files /dev/null and b/tkinter_app/resources/left-arrow-blue.png differ diff --git a/tkinter_app/resources/left-arrow-green.png b/tkinter_app/resources/left-arrow-green.png new file mode 100644 index 0000000..2d2d930 Binary files /dev/null and b/tkinter_app/resources/left-arrow-green.png differ diff --git a/tkinter_app/resources/local_playlist.json b/tkinter_app/resources/local_playlist.json new file mode 100644 index 0000000..c8813bc --- /dev/null +++ b/tkinter_app/resources/local_playlist.json @@ -0,0 +1,20 @@ +{ + "playlist": [ + { + "file_name": "1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg", + "url": "static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg", + "duration": 20 + }, + { + "file_name": "wp2782770-1846651530.jpg", + "url": "static/resurse/wp2782770-1846651530.jpg", + "duration": 15 + }, + { + "file_name": "SampleVideo_1280x720_1mb.mp4", + "url": "static/resurse/SampleVideo_1280x720_1mb.mp4", + "duration": 5 + } + ], + "version": 5 +} \ No newline at end of file diff --git a/tkinter_app/resources/log.txt b/tkinter_app/resources/log.txt new file mode 100644 index 0000000..5576e56 --- /dev/null +++ b/tkinter_app/resources/log.txt @@ -0,0 +1,1186 @@ +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.74, host=, quick=8887779, port=5000 +[INFO] [SignageApp] Screen size set to 1920x1080 +[INFO] [SignageApp] MediaPlayer: Starting on_enter method. +[INFO] [SignageApp] MediaPlayer: Loaded server settings: server=192.168.1.74, host=, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[WARNING] [SignageApp] python_functions: Local playlist file not found. +[INFO] [SignageApp] MediaPlayer: Loaded local playlist: [], Version: 0 +[WARNING] [SignageApp] MediaPlayer: No local playlist found. Attempting to load demo playlist... +[INFO] [SignageApp] MediaPlayer: Demo playlist loaded successfully. +[WARNING] [SignageApp] MediaPlayer: Invalid server settings. Using demo playlist. +[INFO] [SignageApp] MediaPlayer: Starting play_media method. +[INFO] [SignageApp] MediaPlayer: Playing media: Resurse/demo1.jpg +[INFO] [SignageApp] MediaPlayer: Starting log_event function. +2025-06-25 15:53:54 - STARTED: demo1.jpg +[INFO] [SignageApp] MediaPlayer: Logged event: 2025-06-25 15:53:54 - STARTED: demo1.jpg +[INFO] [SignageApp] MediaPlayer: Finished log_event function. +[INFO] [SignageApp] Showing image: Resurse/demo1.jpg +[INFO] [SignageApp] MediaPlayer: Finished play_media method. +[INFO] [SignageApp] MediaPlayer: Finished on_enter method. +[INFO] [SignageApp] SettingsScreen: Log messages loaded successfully. +[INFO] [SignageApp] SettingsScreen: Configuration saved. +[INFO] [SignageApp] MediaPlayer: Starting on_enter method. +[INFO] [SignageApp] MediaPlayer: Loaded server settings: server=192.168.1.74, host=rpi-tv11, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[WARNING] [SignageApp] python_functions: Local playlist file not found. +[INFO] [SignageApp] MediaPlayer: Loaded local playlist: [], Version: 0 +[WARNING] [SignageApp] MediaPlayer: No local playlist found. Attempting to load demo playlist... +[INFO] [SignageApp] MediaPlayer: Demo playlist loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.74:5000/api/playlists with params: {'hostname': '', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.74, host=rpi-tv11, quick=8887779, port=5000 +[INFO] [SignageApp] Screen size set to 1920x1080 +[INFO] [SignageApp] MediaPlayer: Starting on_enter method. +[INFO] [SignageApp] MediaPlayer: Loaded server settings: server=192.168.1.74, host=rpi-tv11, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[WARNING] [SignageApp] python_functions: Local playlist file not found. +[INFO] [SignageApp] MediaPlayer: Loaded local playlist: [], Version: 0 +[WARNING] [SignageApp] MediaPlayer: No local playlist found. Attempting to load demo playlist... +[INFO] [SignageApp] MediaPlayer: Demo playlist loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.74:5000/api/playlists with params: {'hostname': 'rpi-tv11', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.74, host=rpi-tv11, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[WARNING] [SignageApp] python_functions: Local playlist file not found. +[WARNING] [SignageApp] No local playlist found, creating demo content +[INFO] [SignageApp] Created demo playlist with 9 images +[INFO] [SignageApp] Playing media: left-arrow-green.png +2025-08-05 12:22:33 - STARTED: left-arrow-green.png +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: pause.png +2025-08-05 12:22:38 - STARTED: pause.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: home_icon.png +2025-08-05 12:22:44 - STARTED: home_icon.png +[INFO] [SignageApp] Playing media: play.png +2025-08-05 12:22:49 - STARTED: play.png +[INFO] [SignageApp] Playing media: demo1.jpg +2025-08-05 12:22:54 - STARTED: demo1.jpg +[INFO] [SignageApp] Playing media: demo2.jpeg +2025-08-05 12:22:59 - STARTED: demo2.jpeg +[INFO] [SignageApp] Playing media: left-arrow-blue.png +2025-08-05 12:23:04 - STARTED: left-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-blue.png +2025-08-05 12:23:09 - STARTED: right-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-green.png +2025-08-05 12:23:15 - STARTED: right-arrow-green.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: left-arrow-green.png +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 12:23:20 - STARTED: left-arrow-green.png +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.74:5000/api/playlists with params: {'hostname': 'rpi-tv11', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.74', port=5000): Max retries exceeded with url: /api/playlists?hostname=rpi-tv11&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 113] No route to host')) +[INFO] [SignageApp] Playing media: pause.png +2025-08-05 12:23:25 - STARTED: pause.png +[INFO] [SignageApp] Playing media: home_icon.png +2025-08-05 12:23:30 - STARTED: home_icon.png +[INFO] [SignageApp] Playing media: play.png +2025-08-05 12:23:35 - STARTED: play.png +[INFO] [SignageApp] Playing media: demo1.jpg +2025-08-05 12:23:40 - STARTED: demo1.jpg +[INFO] [SignageApp] Playing media: demo2.jpeg +2025-08-05 12:23:45 - STARTED: demo2.jpeg +[INFO] [SignageApp] Playing media: left-arrow-blue.png +2025-08-05 12:23:51 - STARTED: left-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-blue.png +2025-08-05 12:23:56 - STARTED: right-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-green.png +2025-08-05 12:24:01 - STARTED: right-arrow-green.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: left-arrow-green.png +2025-08-05 12:24:06 - STARTED: left-arrow-green.png +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.74:5000/api/playlists with params: {'hostname': 'rpi-tv11', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.74', port=5000): Max retries exceeded with url: /api/playlists?hostname=rpi-tv11&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 113] No route to host')) +[INFO] [SignageApp] Playing media: pause.png +2025-08-05 12:24:11 - STARTED: pause.png +[INFO] [SignageApp] Playing media: home_icon.png +2025-08-05 12:24:16 - STARTED: home_icon.png +[INFO] [SignageApp] Playing media: play.png +2025-08-05 12:24:21 - STARTED: play.png +[INFO] [SignageApp] Playing media: demo1.jpg +2025-08-05 12:24:26 - STARTED: demo1.jpg +[INFO] [SignageApp] Playing media: demo2.jpeg +2025-08-05 12:24:31 - STARTED: demo2.jpeg +[INFO] [SignageApp] Playing media: left-arrow-blue.png +2025-08-05 12:24:37 - STARTED: left-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-blue.png +2025-08-05 12:24:42 - STARTED: right-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-green.png +2025-08-05 12:24:47 - STARTED: right-arrow-green.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: left-arrow-green.png +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 12:24:52 - STARTED: left-arrow-green.png +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.74:5000/api/playlists with params: {'hostname': 'rpi-tv11', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.74', port=5000): Max retries exceeded with url: /api/playlists?hostname=rpi-tv11&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 113] No route to host')) +[INFO] [SignageApp] Playing media: pause.png +2025-08-05 12:24:57 - STARTED: pause.png +[INFO] [SignageApp] Playing media: home_icon.png +2025-08-05 12:25:02 - STARTED: home_icon.png +[INFO] [SignageApp] Playing media: play.png +2025-08-05 12:25:07 - STARTED: play.png +[INFO] [SignageApp] Playing media: demo1.jpg +2025-08-05 12:25:12 - STARTED: demo1.jpg +[INFO] [SignageApp] Playing media: demo2.jpeg +2025-08-05 12:25:17 - STARTED: demo2.jpeg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Configuration saved via settings window +[INFO] [SignageApp] Playing media: left-arrow-blue.png +2025-08-05 12:25:23 - STARTED: left-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-blue.png +2025-08-05 12:25:28 - STARTED: right-arrow-blue.png +[INFO] [SignageApp] Playing media: right-arrow-green.png +2025-08-05 12:25:33 - STARTED: right-arrow-green.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: left-arrow-green.png +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 12:25:38 - STARTED: left-arrow-green.png +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.74:5000/api/playlists with params: {'hostname': 'rpi-tv11', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.74', port=5000): Max retries exceeded with url: /api/playlists?hostname=rpi-tv11&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 113] No route to host')) +[INFO] [SignageApp] Playing media: pause.png +2025-08-05 12:25:43 - STARTED: pause.png +[INFO] [SignageApp] Playing media: home_icon.png +2025-08-05 12:25:48 - STARTED: home_icon.png +[INFO] [SignageApp] Playing media: play.png +2025-08-05 12:25:50 - STARTED: play.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: demo1.jpg +2025-08-05 12:25:55 - STARTED: demo1.jpg +[INFO] [SignageApp] Playing media: demo2.jpeg +2025-08-05 12:26:01 - STARTED: demo2.jpeg +[INFO] [SignageApp] Playing media: left-arrow-blue.png +2025-08-05 12:26:06 - STARTED: left-arrow-blue.png +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[WARNING] [SignageApp] python_functions: Local playlist file not found. +[WARNING] [SignageApp] No local playlist found, creating demo content +[INFO] [SignageApp] Created demo playlist with 9 images +[INFO] [SignageApp] Playing media: left-arrow-green.png +2025-08-05 12:29:21 - STARTED: left-arrow-green.png +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: pause.png +2025-08-05 12:29:26 - STARTED: pause.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: home_icon.png +2025-08-05 12:29:31 - STARTED: home_icon.png +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: play.png +2025-08-05 12:29:36 - STARTED: play.png +[INFO] [SignageApp] Playing media: demo1.jpg +2025-08-05 12:29:41 - STARTED: demo1.jpg +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[ERROR] [SignageApp] python_functions: Configuration file ./Resurse/app_config.txt not found. +[INFO] [SignageApp] python_functions: Configuration loaded: server=, host=, quick=, port= +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': 'har_page_001.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_001.jpg'}, {'duration': 20, 'file_name': 'har_page_002.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_002.jpg'}, {'duration': 20, 'file_name': 'har_page_003.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_003.jpg'}, {'duration': 20, 'file_name': 'har_page_004.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_004.jpg'}, {'duration': 20, 'file_name': 'har_page_005.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_005.jpg'}, {'duration': 20, 'file_name': 'har_page_006.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_006.jpg'}, {'duration': 20, 'file_name': 'har_page_007.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_007.jpg'}, {'duration': 20, 'file_name': 'har_page_008.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_008.jpg'}, {'duration': 20, 'file_name': 'har_page_009.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_009.jpg'}, {'duration': 20, 'file_name': 'har_page_010.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_010.jpg'}], 'playlist_version': 4} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 4. +[INFO] [SignageApp] Server playlist found with 10 items, version 4 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: Created directory /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse for media files. +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_001.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_001.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_002.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_002.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_003.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_003.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_004.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_004.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_005.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_005.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_006.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_006.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_007.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_007.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_008.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_008.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_009.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_009.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded har_page_010.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/har_page_010.jpg +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 4. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': 'har_page_001.jpg', 'url': 'static/resurse/har_page_001.jpg', 'duration': 20}, {'file_name': 'har_page_002.jpg', 'url': 'static/resurse/har_page_002.jpg', 'duration': 20}, {'file_name': 'har_page_003.jpg', 'url': 'static/resurse/har_page_003.jpg', 'duration': 20}, {'file_name': 'har_page_004.jpg', 'url': 'static/resurse/har_page_004.jpg', 'duration': 20}, {'file_name': 'har_page_005.jpg', 'url': 'static/resurse/har_page_005.jpg', 'duration': 20}, {'file_name': 'har_page_006.jpg', 'url': 'static/resurse/har_page_006.jpg', 'duration': 20}, {'file_name': 'har_page_007.jpg', 'url': 'static/resurse/har_page_007.jpg', 'duration': 20}, {'file_name': 'har_page_008.jpg', 'url': 'static/resurse/har_page_008.jpg', 'duration': 20}, {'file_name': 'har_page_009.jpg', 'url': 'static/resurse/har_page_009.jpg', 'duration': 20}, {'file_name': 'har_page_010.jpg', 'url': 'static/resurse/har_page_010.jpg', 'duration': 20}], 'version': 4} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 10 items from server +[INFO] [SignageApp] Playing media: har_page_001.jpg from static/resurse/har_page_001.jpg +2025-08-05 13:52:51 - STARTED: har_page_001.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': 'har_page_001.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_001.jpg'}, {'duration': 20, 'file_name': 'har_page_002.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_002.jpg'}, {'duration': 20, 'file_name': 'har_page_003.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_003.jpg'}, {'duration': 20, 'file_name': 'har_page_004.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_004.jpg'}, {'duration': 20, 'file_name': 'har_page_005.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_005.jpg'}, {'duration': 20, 'file_name': 'har_page_006.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_006.jpg'}, {'duration': 20, 'file_name': 'har_page_007.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_007.jpg'}, {'duration': 20, 'file_name': 'har_page_008.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_008.jpg'}, {'duration': 20, 'file_name': 'har_page_009.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_009.jpg'}, {'duration': 20, 'file_name': 'har_page_010.jpg', 'url': 'http://192.168.1.245:5000/media/har_page_010.jpg'}], 'playlist_version': 4} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 4. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': 'har_page_001.jpg', 'url': 'static/resurse/har_page_001.jpg', 'duration': 20}, {'file_name': 'har_page_002.jpg', 'url': 'static/resurse/har_page_002.jpg', 'duration': 20}, {'file_name': 'har_page_003.jpg', 'url': 'static/resurse/har_page_003.jpg', 'duration': 20}, {'file_name': 'har_page_004.jpg', 'url': 'static/resurse/har_page_004.jpg', 'duration': 20}, {'file_name': 'har_page_005.jpg', 'url': 'static/resurse/har_page_005.jpg', 'duration': 20}, {'file_name': 'har_page_006.jpg', 'url': 'static/resurse/har_page_006.jpg', 'duration': 20}, {'file_name': 'har_page_007.jpg', 'url': 'static/resurse/har_page_007.jpg', 'duration': 20}, {'file_name': 'har_page_008.jpg', 'url': 'static/resurse/har_page_008.jpg', 'duration': 20}, {'file_name': 'har_page_009.jpg', 'url': 'static/resurse/har_page_009.jpg', 'duration': 20}, {'file_name': 'har_page_010.jpg', 'url': 'static/resurse/har_page_010.jpg', 'duration': 20}], 'version': 4} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] python_functions: Cleaning unused media files... +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File har_page_001.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_002.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_003.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_004.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_005.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_006.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_007.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_008.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_009.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File har_page_010.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 4. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': 'har_page_001.jpg', 'url': 'static/resurse/har_page_001.jpg', 'duration': 20}, {'file_name': 'har_page_002.jpg', 'url': 'static/resurse/har_page_002.jpg', 'duration': 20}, {'file_name': 'har_page_003.jpg', 'url': 'static/resurse/har_page_003.jpg', 'duration': 20}, {'file_name': 'har_page_004.jpg', 'url': 'static/resurse/har_page_004.jpg', 'duration': 20}, {'file_name': 'har_page_005.jpg', 'url': 'static/resurse/har_page_005.jpg', 'duration': 20}, {'file_name': 'har_page_006.jpg', 'url': 'static/resurse/har_page_006.jpg', 'duration': 20}, {'file_name': 'har_page_007.jpg', 'url': 'static/resurse/har_page_007.jpg', 'duration': 20}, {'file_name': 'har_page_008.jpg', 'url': 'static/resurse/har_page_008.jpg', 'duration': 20}, {'file_name': 'har_page_009.jpg', 'url': 'static/resurse/har_page_009.jpg', 'duration': 20}, {'file_name': 'har_page_010.jpg', 'url': 'static/resurse/har_page_010.jpg', 'duration': 20}], 'version': 4} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Playing media: har_page_001.jpg from static/resurse/har_page_001.jpg +2025-08-05 13:53:08 - STARTED: har_page_001.jpg +[INFO] [SignageApp] Playing media: har_page_002.jpg from static/resurse/har_page_002.jpg +2025-08-05 13:53:15 - STARTED: har_page_002.jpg +[INFO] [SignageApp] Playing media: har_page_003.jpg from static/resurse/har_page_003.jpg +2025-08-05 13:53:17 - STARTED: har_page_003.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 2 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: Successfully downloaded 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Successfully downloaded wp2782770-1846651530.jpg to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 2 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 13:55:59 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from static/resurse/wp2782770-1846651530.jpg +2025-08-05 13:56:19 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 2 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File wp2782770-1846651530.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 2 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 14:03:41 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:04:01 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 2 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File wp2782770-1846651530.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 2 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 14:09:20 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:09:40 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:09:56 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:10:16 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:10:32 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:10:52 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:11:09 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:11:29 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 14:11:45 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:12:05 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 3 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File wp2782770-1846651530.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: Successfully downloaded SampleVideo_1280x720_1mb.mp4 to /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 3 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 14:24:44 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:25:04 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 14:25:20 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 14:25:25 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:25:46 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 14:26:02 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:26:07 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 3 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File wp2782770-1846651530.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File SampleVideo_1280x720_1mb.mp4 already exists. Skipping download. +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 3 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 14:40:29 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:40:50 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 14:41:06 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:41:27 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:41:47 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 14:42:03 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:42:25 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:42:45 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 14:43:01 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:43:21 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:43:42 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 14:43:58 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 14:44:18 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 14:44:38 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 14:44:54 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 14:45:15 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 3 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File wp2782770-1846651530.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File SampleVideo_1280x720_1mb.mp4 already exists. Skipping download. +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 3 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 15:13:11 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 15:13:31 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 15:13:47 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 15:14:08 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 3 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File wp2782770-1846651530.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File SampleVideo_1280x720_1mb.mp4 already exists. Skipping download. +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 3 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 15:34:34 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$ReP1AQ/YknPWyoFLGwSgH.80kBv.Bm13XdRUTALyA6vD20T1vEOl.', 'playlist': [{'duration': 20, 'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'http://192.168.1.245:5000/media/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg'}, {'duration': 15, 'file_name': 'wp2782770-1846651530.jpg', 'url': 'http://192.168.1.245:5000/media/wp2782770-1846651530.jpg'}, {'duration': 5, 'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'http://192.168.1.245:5000/media/SampleVideo_1280x720_1mb.mp4'}], 'playlist_version': 5} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] Server playlist found with 3 items, version 5 +[INFO] [SignageApp] python_functions: Starting media file download... +[INFO] [SignageApp] python_functions: File 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File wp2782770-1846651530.jpg already exists. Skipping download. +[INFO] [SignageApp] python_functions: File SampleVideo_1280x720_1mb.mp4 already exists. Skipping download. +[INFO] [SignageApp] python_functions: Starting save_local_playlist function. +[INFO] [SignageApp] python_functions: Updated local playlist with server data. +[INFO] [SignageApp] python_functions: Finished save_local_playlist function. +[INFO] [SignageApp] python_functions: Finished media file download and updated local playlist. +[INFO] [SignageApp] python_functions: Updated playlist version in app_config.txt to 5. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Successfully loaded 3 items from server +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 15:35:55 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.245', port=5000): Max retries exceeded with url: /api/playlists?hostname=tv-holba1&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[WARNING] [SignageApp] Server returned empty playlist +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Loaded existing local playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 15:58:32 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.245', port=5000): Max retries exceeded with url: /api/playlists?hostname=tv-holba1&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 16:04:48 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 16:05:08 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 16:05:24 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.245', port=5000): Max retries exceeded with url: /api/playlists?hostname=tv-holba1&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 16:15:53 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 16:16:14 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 16:16:30 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.245', port=5000): Max retries exceeded with url: /api/playlists?hostname=tv-holba1&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 16:28:45 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.245', port=5000): Max retries exceeded with url: /api/playlists?hostname=tv-holba1&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 16:34:00 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Loading configuration in settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-holba1', 'quickconnect_key': '8887779', 'server_ip': '192.168.1.245', 'port': '5000', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in settings +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 16:34:20 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.245', port=5000): Max retries exceeded with url: /api/playlists?hostname=tv-holba1&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 16:44:37 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.245', port=5000): Max retries exceeded with url: /api/playlists?hostname=tv-holba1&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 17:00:43 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Loading configuration in settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-holba1', 'quickconnect_key': '8887779', 'server_ip': '192.168.1.245', 'port': '5000', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in settings +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-05 19:12:35 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Loading configuration in settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in settings +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-05 19:12:55 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-05 19:13:10 - STARTED: SampleVideo_1280x720_1mb.mp4 +[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-05 19:13:16 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] Loading configuration in settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in settings +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 01:11:39 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-06 01:11:59 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-06 01:12:14 - STARTED: SampleVideo_1280x720_1mb.mp4 +[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined +[INFO] [SignageApp] Loading configuration in settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in settings +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-06 01:12:20 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-06 01:12:40 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-06 01:12:55 - STARTED: SampleVideo_1280x720_1mb.mp4 +[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 01:13:00 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] Loading configuration in enhanced settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 01:31:08 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-06 01:31:28 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] Loading configuration in enhanced settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 01:51:49 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Loading configuration in enhanced settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-06 01:52:09 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-06 01:52:24 - STARTED: SampleVideo_1280x720_1mb.mp4 +[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-06 01:52:30 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-06 01:52:50 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-06 01:53:05 - STARTED: SampleVideo_1280x720_1mb.mp4 +[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-06 01:53:05 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[INFO] [SignageApp] No playlist updates available +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 02:02:39 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg ((1600, 1000)) +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-signage.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-signage.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=digi-signage.moto-adv.com, host=tv-terasa, port=8880 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 522 +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loading configuration in enhanced settings window +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-signage.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5} +[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 02:14:39 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 3840x2160, Mode: fit, Offset: (192, 0)) +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-06 02:15:03 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Successfully displayed image: wp2782770-1846651530.jpg (Original: (3840, 2400), Screen: 3840x2160, Mode: fit, Offset: (192, 0)) +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-06 02:15:23 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] Video dimensions: 1280x720, Screen dimensions: 3840x2160 +[INFO] [SignageApp] Media paused +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +2025-08-06 02:17:55 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 3840x2160, Mode: fit, Offset: (192, 0)) +[INFO] [SignageApp] Application exit requested +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-signage.moto-adv.com, host=tv-terasa, quick=8887779, port=8880 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] python_functions: Starting load_local_playlist function. +[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5} +[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully. +[INFO] [SignageApp] Found fallback playlist with 3 items +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Initializing with settings: server=digi-signage.moto-adv.com, host=tv-terasa, port=8880 +[INFO] [SignageApp] Attempting to connect to server... +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 522 +[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist +[INFO] [SignageApp] Loaded fallback playlist with 3 items +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 02:20:20 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 1920x1080, Mode: fit, Offset: (96, 0)) +[INFO] [SignageApp] Starting Simple Tkinter Media Player +[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg +2025-08-06 02:20:40 - STARTED: wp2782770-1846651530.jpg +[INFO] [SignageApp] Successfully displayed image: wp2782770-1846651530.jpg (Original: (3840, 2400), Screen: 1920x1080, Mode: fit, Offset: (96, 0)) +[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 +2025-08-06 02:20:57 - STARTED: SampleVideo_1280x720_1mb.mp4 +[INFO] [SignageApp] Video dimensions: 1280x720, Screen dimensions: 1920x1080 +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +2025-08-06 02:21:44 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 1920x1080, Mode: fit, Offset: (96, 0)) +[INFO] [SignageApp] python_functions: Starting load_config function. +[INFO] [SignageApp] python_functions: Configuration file loaded successfully. +[INFO] [SignageApp] Application exit requested diff --git a/tkinter_app/resources/pause.png b/tkinter_app/resources/pause.png new file mode 100644 index 0000000..97c06c9 Binary files /dev/null and b/tkinter_app/resources/pause.png differ diff --git a/tkinter_app/resources/play.png b/tkinter_app/resources/play.png new file mode 100644 index 0000000..00391ae Binary files /dev/null and b/tkinter_app/resources/play.png differ diff --git a/tkinter_app/resources/right-arrow-blue.png b/tkinter_app/resources/right-arrow-blue.png new file mode 100644 index 0000000..6d98721 Binary files /dev/null and b/tkinter_app/resources/right-arrow-blue.png differ diff --git a/tkinter_app/resources/right-arrow-green.png b/tkinter_app/resources/right-arrow-green.png new file mode 100644 index 0000000..2560948 Binary files /dev/null and b/tkinter_app/resources/right-arrow-green.png differ diff --git a/tkinter_app/src/__pycache__/logging_config.cpython-311.pyc b/tkinter_app/src/__pycache__/logging_config.cpython-311.pyc new file mode 100644 index 0000000..b8fd7cc Binary files /dev/null and b/tkinter_app/src/__pycache__/logging_config.cpython-311.pyc differ diff --git a/tkinter_app/src/__pycache__/python_functions.cpython-311.pyc b/tkinter_app/src/__pycache__/python_functions.cpython-311.pyc new file mode 100644 index 0000000..aafc50f Binary files /dev/null and b/tkinter_app/src/__pycache__/python_functions.cpython-311.pyc differ diff --git a/tkinter_app/src/__pycache__/tkinter_simple_player.cpython-311.pyc b/tkinter_app/src/__pycache__/tkinter_simple_player.cpython-311.pyc new file mode 100644 index 0000000..789a54a Binary files /dev/null and b/tkinter_app/src/__pycache__/tkinter_simple_player.cpython-311.pyc differ diff --git a/tkinter_app/src/__pycache__/virtual_keyboard.cpython-311.pyc b/tkinter_app/src/__pycache__/virtual_keyboard.cpython-311.pyc new file mode 100644 index 0000000..b664ca8 Binary files /dev/null and b/tkinter_app/src/__pycache__/virtual_keyboard.cpython-311.pyc differ diff --git a/tkinter_app/src/logging_config.py b/tkinter_app/src/logging_config.py new file mode 100644 index 0000000..8710312 --- /dev/null +++ b/tkinter_app/src/logging_config.py @@ -0,0 +1,27 @@ +import logging +import os + +# Path to the log file +# Update the path to point to the new resources directory +LOG_FILE_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'log.txt') + +# Create a logger instance +Logger = logging.getLogger('SignageApp') +Logger.setLevel(logging.INFO) # Set the logging level to INFO + +# Create a file handler to write logs to the log.txt file +file_handler = logging.FileHandler(LOG_FILE_PATH, mode='a') # Append logs to the file +file_handler.setLevel(logging.INFO) + +# Create a formatter for the log messages +formatter = logging.Formatter('[%(levelname)s] [%(name)s] %(message)s') +file_handler.setFormatter(formatter) + +# Add the file handler to the logger +Logger.addHandler(file_handler) + +# Optionally, add a stream handler to log messages to the console +stream_handler = logging.StreamHandler() +stream_handler.setLevel(logging.INFO) +stream_handler.setFormatter(formatter) +Logger.addHandler(stream_handler) \ No newline at end of file diff --git a/tkinter_app/src/main.py b/tkinter_app/src/main.py new file mode 100644 index 0000000..119c0dd --- /dev/null +++ b/tkinter_app/src/main.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +""" +Main entry point for the tkinter-based signage player application. +This file acts as the main executable for launching the tkinter player. +""" +import os +import sys +import cv2 # Import OpenCV to confirm it's available + +# Add the current directory to the path so we can import our modules +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +# Import the player module +from tkinter_simple_player import SimpleMediaPlayerApp + +if __name__ == "__main__": + print(f"Using OpenCV version: {cv2.__version__}") + # Create and run the player + player = SimpleMediaPlayerApp() + player.run() \ No newline at end of file diff --git a/tkinter_app/src/python_functions.py b/tkinter_app/src/python_functions.py new file mode 100644 index 0000000..279b46e --- /dev/null +++ b/tkinter_app/src/python_functions.py @@ -0,0 +1,196 @@ +import os +import json +import requests +from logging_config import Logger # Import the shared logger +import bcrypt +import time + +# Update paths to use the new directory structure +CONFIG_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'app_config.txt') +LOCAL_PLAYLIST_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'local_playlist.json') + +def load_config(): + """Load configuration from app_config.txt.""" + Logger.info("python_functions: Starting load_config function.") + 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 {} + else: + Logger.error(f"python_functions: Configuration file {CONFIG_FILE} not found.") + return {} + Logger.info("python_functions: Finished load_config function.") + +# 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", "") + +Logger.info(f"python_functions: Configuration loaded: server={server}, host={host}, quick={quick}, port={port}") + +def load_local_playlist(): + """Load the playlist and version from local storage.""" + Logger.info("python_functions: Starting load_local_playlist function.") + 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: + Logger.info("python_functions: Finished load_local_playlist function successfully.") + return local_playlist # Return the full playlist data + else: + Logger.error("python_functions: Invalid local playlist structure.") + return {'playlist': [], 'version': 0} + except json.JSONDecodeError as e: + Logger.error(f"python_functions: Failed to parse local playlist file. Error: {e}") + return {'playlist': [], 'version': 0} + else: + Logger.warning("python_functions: Local playlist file not found.") + return {'playlist': [], 'version': 0} + Logger.info("python_functions: Finished load_local_playlist function.") + +def save_local_playlist(playlist): + """Save the updated playlist locally.""" + Logger.info("python_functions: Starting save_local_playlist function.") + Logger.debug(f"python_functions: Playlist to save: {playlist}") + 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, indent=4) # Ensure proper formatting + 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}") + Logger.info("python_functions: Finished save_local_playlist function.") + +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 + } + Logger.info(f"Fetching playlist from URL: {url} with params: {params}") + response = requests.get(url, params=params) + + if response.status_code == 200: + response_data = response.json() + Logger.info(f"Server response: {response_data}") + 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: + if bcrypt.checkpw(quick.encode('utf-8'), hashed_quickconnect.encode('utf-8')): + Logger.info("Fetched updated playlist from server.") + + # Update the playlist version in app_config.txt + update_config_playlist_version(version) + + return {'playlist': playlist, 'version': version} + else: + Logger.error("Quickconnect code validation failed.") + else: + Logger.error("Failed to retrieve playlist or hashed quickconnect from the response.") + else: + Logger.error(f"Failed to fetch playlist. Status Code: {response.status_code}") + except requests.exceptions.RequestException as e: + Logger.error(f"Failed to fetch playlist: {e}") + + return {'playlist': [], 'version': 0} + +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') # 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 + local_path = os.path.join(base_dir, file_name) # Local file path + + Logger.debug(f"python_functions: Preparing to download {file_name} from {file_url}...") + + if os.path.exists(local_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(local_path, 'wb') as file: + file.write(response.content) + Logger.info(f"python_functions: Successfully downloaded {file_name} to {local_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 playlist entry to point to the local file path + updated_media = { + 'file_name': file_name, + 'url': f"static/resurse/{file_name}", # Update URL to local path + 'duration': duration + } + Logger.debug(f"python_functions: Updated media entry: {updated_media}") + updated_playlist.append(updated_media) + + # Save the updated playlist locally + save_local_playlist({'playlist': updated_playlist, 'version': version}) + Logger.info("python_functions: Finished media file download and updated local playlist.") + +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') + if not os.path.exists(base_dir): + Logger.debug(f"python_functions: Directory {base_dir} does not exist. No files to clean.") + return + + playlist_files = {media.get('file_name', '') for media in playlist} + all_files = set(os.listdir(base_dir)) + unused_files = all_files - playlist_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 update_config_playlist_version(version): + """Update the playlist version in app_config.txt.""" + if not os.path.exists(CONFIG_FILE): + Logger.error(f"python_functions: Configuration file {CONFIG_FILE} not found.") + return + + try: + with open(CONFIG_FILE, 'r') as file: + config_data = json.load(file) + + config_data['playlist_version'] = version # Add or update the playlist version + + with open(CONFIG_FILE, 'w') as file: + json.dump(config_data, file, indent=4) + Logger.info(f"python_functions: Updated playlist version in app_config.txt to {version}.") + except (IOError, json.JSONDecodeError) as e: + Logger.error(f"python_functions: Failed to update playlist version in app_config.txt. Error: {e}") \ No newline at end of file diff --git a/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg b/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg new file mode 100644 index 0000000..ad8cba1 Binary files /dev/null and b/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg differ diff --git a/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 b/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 new file mode 100644 index 0000000..ed139d6 Binary files /dev/null and b/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4 differ diff --git a/tkinter_app/src/static/resurse/har_page_001.jpg b/tkinter_app/src/static/resurse/har_page_001.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_001.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_002.jpg b/tkinter_app/src/static/resurse/har_page_002.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_002.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_003.jpg b/tkinter_app/src/static/resurse/har_page_003.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_003.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_004.jpg b/tkinter_app/src/static/resurse/har_page_004.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_004.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_005.jpg b/tkinter_app/src/static/resurse/har_page_005.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_005.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_006.jpg b/tkinter_app/src/static/resurse/har_page_006.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_006.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_007.jpg b/tkinter_app/src/static/resurse/har_page_007.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_007.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_008.jpg b/tkinter_app/src/static/resurse/har_page_008.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_008.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_009.jpg b/tkinter_app/src/static/resurse/har_page_009.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_009.jpg differ diff --git a/tkinter_app/src/static/resurse/har_page_010.jpg b/tkinter_app/src/static/resurse/har_page_010.jpg new file mode 100644 index 0000000..d3278b0 Binary files /dev/null and b/tkinter_app/src/static/resurse/har_page_010.jpg differ diff --git a/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg b/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg new file mode 100644 index 0000000..d6613fb Binary files /dev/null and b/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg differ diff --git a/tkinter_app/src/tkinter_simple_player.py b/tkinter_app/src/tkinter_simple_player.py new file mode 100644 index 0000000..b43a3dc --- /dev/null +++ b/tkinter_app/src/tkinter_simple_player.py @@ -0,0 +1,2107 @@ +#!/usr/bin/env python3 +""" +Tkinter Simple Media Player - A lightweight version with minimal dependencies +Features: +- Image display +- Basic playlist management +- Settings configuration +- Auto-hiding controls +- Touch display optimization with virtual keyboard +""" + +import tkinter as tk +from tkinter import ttk, messagebox, simpledialog +import threading +import time +import os +import json +import datetime +from pathlib import Path +import subprocess +import sys +import requests # Required for server communication +import queue +import cv2 # For video playback +import pygame # For video audio + +# Try importing PIL but provide fallback +try: + from PIL import Image, ImageTk + PIL_AVAILABLE = True +except ImportError: + PIL_AVAILABLE = False + print("WARNING: PIL not available. Image display functionality will be limited.") + +# Import existing functions +from python_functions import ( + load_local_playlist, download_media_files, clean_unused_files, + save_local_playlist, update_config_playlist_version, fetch_server_playlist, + load_config +) +from logging_config import Logger + +# Import virtual keyboard components +from virtual_keyboard import VirtualKeyboard, TouchOptimizedEntry, TouchOptimizedButton + +# Update the config file path to use the resources directory +CONFIG_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'app_config.txt') + +class SimpleMediaPlayerApp: + def __init__(self): + self.root = tk.Tk() + self.setup_window() + + # Media player state + self.playlist = [] + self.current_index = 0 + self.is_paused = False + self.is_fullscreen = True + self.auto_advance_timer = None + self.hide_controls_timer = None + self.video_thread = None + self.update_queue = queue.Queue() + + # UI Elements + self.image_label = None + self.status_label = None + self.control_frame = None + self.settings_window = None + + # Running state + self.running = True + + # Display scaling mode ('fit', 'fill', 'stretch') + self.scaling_mode = 'fit' # Default to fit (maintain aspect ratio with black bars) + + # Initialize pygame for video audio + try: + pygame.init() + pygame.mixer.init() + except Exception as e: + Logger.warning(f"Failed to initialize pygame mixer for audio: {e}") + + self.setup_ui() + + # Initialize from server + self.initialize_playlist_from_server() + + # Start periodic playlist checks + self.start_periodic_checks() + + def setup_window(self): + """Configure the main window""" + self.root.title("Simple Signage Player") + self.root.configure(bg='black') + + # Load window size from config + try: + config = load_config() + width = int(config.get('screen_w', 1920)) + height = int(config.get('screen_h', 1080)) + # Load scaling mode preference + self.scaling_mode = config.get('scaling_mode', 'fit') + except: + width, height = 800, 600 # Fallback size if config fails + self.scaling_mode = 'fit' # Default scaling mode + + self.root.geometry(f"{width}x{height}") + self.root.attributes('-fullscreen', True) + + # Bind events + self.root.bind('', self.on_key_press) + self.root.bind('', self.on_mouse_click) + self.root.bind('', self.on_mouse_motion) + self.root.focus_set() + + def setup_ui(self): + """Create the user interface""" + # Main content area - make sure it's black and fill the entire window + self.content_frame = tk.Frame(self.root, bg='black') + self.content_frame.pack(fill=tk.BOTH, expand=True) + + # Image display - expand to fill the entire window + self.image_label = tk.Label(self.content_frame, bg='black') + self.image_label.pack(fill=tk.BOTH, expand=True) + + # Status label - hidden by default + self.status_label = tk.Label( + self.content_frame, + bg='black', + fg='white', + font=('Arial', 24), + text="" + ) + # Don't place the status label by default to keep it hidden + + # Control panel + self.create_control_panel() + self.show_controls() + self.schedule_hide_controls() + + def create_control_panel(self): + """Create touch-optimized control panel with larger buttons""" + # Create control frame with larger size for touch + self.control_frame = tk.Frame( + self.root, + bg='#1a1a1a', # Dark background + bd=2, + relief=tk.RAISED, + padx=15, + pady=15 + ) + self.control_frame.place(relx=0.98, rely=0.98, anchor='se') + + # Touch-optimized button configuration + button_config = { + 'bg': '#333333', + 'fg': 'white', + 'activebackground': '#555555', + 'activeforeground': 'white', + 'relief': tk.FLAT, + 'borderwidth': 0, + 'width': 10, # Larger for touch + 'height': 3, # Larger for touch + 'font': ('Segoe UI', 10, 'bold'), # Larger font + 'cursor': 'hand2' + } + + # Previous button + self.prev_btn = tk.Button( + self.control_frame, + text="โฎ Prev", + command=self.previous_media, + **button_config + ) + self.prev_btn.grid(row=0, column=0, padx=5) + + # Play/Pause button + self.play_pause_btn = tk.Button( + self.control_frame, + text="โธ Pause" if not self.is_paused else "โ–ถ Play", + command=self.toggle_play_pause, + bg='#27ae60', # Green for play/pause + activebackground='#35d974', + **{k: v for k, v in button_config.items() if k not in ['bg', 'activebackground']} + ) + self.play_pause_btn.grid(row=0, column=1, padx=5) + + # Next button + self.next_btn = tk.Button( + self.control_frame, + text="Next โญ", + command=self.next_media, + **button_config + ) + self.next_btn.grid(row=0, column=2, padx=5) + + # Settings button + self.settings_btn = tk.Button( + self.control_frame, + text="โš™๏ธ Settings", + command=self.open_settings, + bg='#9b59b6', # Purple for settings + activebackground='#bb8fce', + **{k: v for k, v in button_config.items() if k not in ['bg', 'activebackground']} + ) + self.settings_btn.grid(row=0, column=3, padx=5) + + # Exit button with special styling + self.exit_btn = tk.Button( + self.control_frame, + text="โŒ EXIT", + command=self.show_exit_dialog, + bg='#e74c3c', # Red background + fg='white', + activebackground='#ec7063', + activeforeground='white', + relief=tk.FLAT, + borderwidth=0, + width=8, + height=3, + font=('Segoe UI', 10, 'bold'), + cursor='hand2' + ) + self.exit_btn.grid(row=0, column=4, padx=5) + + # Add touch feedback to all buttons + for button in [self.prev_btn, self.play_pause_btn, self.next_btn, + self.settings_btn, self.exit_btn]: + self.add_touch_feedback_to_control_button(button) + + def scale_image_to_screen(self, img, screen_width, screen_height, mode='fit'): + """ + Scale image to screen with different modes: + - 'fit': Maintain aspect ratio, add black bars if needed (letterbox/pillarbox) + - 'fill': Maintain aspect ratio, crop if needed to fill entire screen + - 'stretch': Ignore aspect ratio, stretch to fill entire screen + """ + img_width, img_height = img.size + + if mode == 'stretch': + # Stretch to fill entire screen, ignoring aspect ratio + return img.resize((screen_width, screen_height), Image.LANCZOS), (0, 0) + + elif mode == 'fill': + # Maintain aspect ratio, crop to fill entire screen + screen_ratio = screen_width / screen_height + img_ratio = img_width / img_height + + if img_ratio > screen_ratio: + # Image is wider - scale by height and crop width + new_height = screen_height + new_width = int(screen_height * img_ratio) + x_offset = (screen_width - new_width) // 2 + y_offset = 0 + else: + # Image is taller - scale by width and crop height + new_width = screen_width + new_height = int(screen_width / img_ratio) + x_offset = 0 + y_offset = (screen_height - new_height) // 2 + + # Resize and crop + img_resized = img.resize((new_width, new_height), Image.LANCZOS) + + # Create final image and paste (this will crop automatically) + final_img = Image.new('RGB', (screen_width, screen_height), 'black') + + # Calculate crop area if image is larger than screen + if new_width > screen_width: + crop_x = (new_width - screen_width) // 2 + img_resized = img_resized.crop((crop_x, 0, crop_x + screen_width, new_height)) + x_offset = 0 + if new_height > screen_height: + crop_y = (new_height - screen_height) // 2 + img_resized = img_resized.crop((0, crop_y, new_width, crop_y + screen_height)) + y_offset = 0 + + final_img.paste(img_resized, (x_offset, y_offset)) + return final_img, (x_offset, y_offset) + + else: # mode == 'fit' (default) + # Maintain aspect ratio, add black bars if needed + screen_ratio = screen_width / screen_height + img_ratio = img_width / img_height + + if img_ratio > screen_ratio: + # Image is wider than screen - fit to width + new_width = screen_width + new_height = int(screen_width / img_ratio) + else: + # Image is taller than screen - fit to height + new_height = screen_height + new_width = int(screen_height * img_ratio) + + # Resize image + img_resized = img.resize((new_width, new_height), Image.LANCZOS) + + # Create black background and center the image + final_img = Image.new('RGB', (screen_width, screen_height), 'black') + x_offset = (screen_width - new_width) // 2 + y_offset = (screen_height - new_height) // 2 + final_img.paste(img_resized, (x_offset, y_offset)) + + return final_img, (x_offset, y_offset) + + def add_touch_feedback_to_control_button(self, button): + """Add touch feedback effects to control panel buttons""" + original_bg = button.cget('bg') + + def on_press(e): + button.configure(relief=tk.SUNKEN) + + def on_release(e): + button.configure(relief=tk.FLAT) + + def on_enter(e): + button.configure(relief=tk.RAISED) + + def on_leave(e): + button.configure(relief=tk.FLAT) + + button.bind("", on_press) + button.bind("", on_release) + button.bind("", on_enter) + button.bind("", on_leave) + + def initialize_playlist_from_server(self): + """Initialize the playlist from the server on startup with fallback to local playlist""" + # First try to load any existing local playlist as fallback + fallback_playlist = None + try: + local_playlist_data = load_local_playlist() + fallback_playlist = local_playlist_data.get('playlist', []) + if fallback_playlist: + Logger.info(f"Found fallback playlist with {len(fallback_playlist)} items") + except Exception as e: + Logger.warning(f"No fallback playlist available: {e}") + + # Show connection status + self.status_label.config(text="Connecting to server...\nPlease wait") + self.status_label.place(relx=0.5, rely=0.5, anchor='center') + self.root.update() + + # Load configuration + config = load_config() + server = config.get("server_ip", "") + host = config.get("screen_name", "") + quick = config.get("quickconnect_key", "") + port = config.get("port", "") + + Logger.info(f"Initializing with settings: server={server}, host={host}, port={port}") + + if not server or not host or not quick or not port: + Logger.warning("Missing server configuration, using fallback playlist") + self.status_label.place_forget() + self.load_fallback_playlist(fallback_playlist) + return + + # Attempt to fetch server playlist with timeout + server_connection_successful = False + try: + # Add connection timeout and retry logic + Logger.info("Attempting to connect to server...") + self.status_label.config(text="Connecting to server...\nAttempting connection") + self.root.update() + + server_playlist_data = fetch_server_playlist() + server_playlist = server_playlist_data.get('playlist', []) + server_version = server_playlist_data.get('version', 0) + + if server_playlist: + Logger.info(f"Server playlist found with {len(server_playlist)} items, version {server_version}") + server_connection_successful = True + + # Download media files and update local playlist + self.status_label.config(text="Downloading media files...\nPlease wait") + self.root.update() + + download_media_files(server_playlist, server_version) + update_config_playlist_version(server_version) + + # Load the updated local playlist + local_playlist_data = load_local_playlist() + self.playlist = local_playlist_data.get('playlist', []) + + if self.playlist: + Logger.info(f"Successfully loaded {len(self.playlist)} items from server") + self.status_label.place_forget() + self.play_current_media() + return + else: + Logger.warning("Server playlist was empty, falling back to local playlist") + else: + Logger.warning("Server returned empty playlist, falling back to local playlist") + + except requests.exceptions.ConnectTimeout: + Logger.error("Server connection timeout, using fallback playlist") + except requests.exceptions.ConnectionError: + Logger.error("Cannot connect to server, using fallback playlist") + except requests.exceptions.Timeout: + Logger.error("Server request timeout, using fallback playlist") + except Exception as e: + Logger.error(f"Failed to fetch playlist from server: {e}, using fallback playlist") + + # If we reach here, server connection failed - use fallback + if not server_connection_successful: + self.status_label.config(text="Server unavailable\nLoading last playlist...") + self.root.update() + time.sleep(1) # Brief pause to show message + + self.status_label.place_forget() + self.load_fallback_playlist(fallback_playlist) + + def load_fallback_playlist(self, fallback_playlist): + """Load fallback playlist when server is unavailable""" + if fallback_playlist and len(fallback_playlist) > 0: + self.playlist = fallback_playlist + Logger.info(f"Loaded fallback playlist with {len(self.playlist)} items") + self.play_current_media() + else: + Logger.warning("No fallback playlist available, loading demo content") + self.load_demo_or_local_playlist() + + def load_demo_or_local_playlist(self): + """Load either the existing local playlist or demo content""" + # First try to load the local playlist + local_playlist_data = load_local_playlist() + self.playlist = local_playlist_data.get('playlist', []) + + if self.playlist: + Logger.info(f"Loaded existing local playlist with {len(self.playlist)} items") + self.play_current_media() + return + + # If no local playlist, try loading demo content + Logger.info("No local playlist found, loading demo content") + self.create_demo_content() + + if self.playlist: + self.play_current_media() + else: + self.show_no_content_message() + + def create_demo_content(self): + """Create demo content for testing""" + demo_images = [] + + # First check static/resurse folder for any media + static_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') + if os.path.exists(static_dir): + for file in os.listdir(static_dir): + if file.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')): + full_path = os.path.join(static_dir, file) + demo_images.append({ + 'file_name': file, + 'url': full_path, + 'duration': 5 + }) + + # If no files found in static/resurse, look in Resurse folder + if not demo_images: + demo_dir = './Resurse' + if os.path.exists(demo_dir): + for file in os.listdir(demo_dir): + if file.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')): + demo_images.append({ + 'file_name': file, + 'url': os.path.join(demo_dir, file), + 'duration': 5 + }) + + if demo_images: + self.playlist = demo_images + Logger.info(f"Created demo playlist with {len(demo_images)} images") + else: + # Create a text-only demo if no images found + self.playlist = [{ + 'file_name': 'Demo Text', + 'url': 'text://Welcome to Tkinter Media Player!\n\nPlease configure server settings', + 'duration': 5 + }] + + def show_no_content_message(self): + """Show message when no content is available""" + self.image_label.config(image='') + self.status_label.config( + text="No media content available.\nPress Settings to configure server connection." + ) + self.status_label.place(relx=0.5, rely=0.5, anchor='center') + + def show_error_message(self, message): + """Show error message""" + self.image_label.config(image='') + self.status_label.config(text=f"Error: {message}") + self.status_label.place(relx=0.5, rely=0.5, anchor='center') + + def play_current_media(self): + """Play the current media item""" + if not self.playlist or self.current_index >= len(self.playlist): + self.show_no_content_message() + return + + media = self.playlist[self.current_index] + file_path = media.get('url', '') + file_name = media.get('file_name', '') + duration = media.get('duration', 10) + + # Handle relative paths by converting to absolute paths + if file_path.startswith('static/resurse/'): + # Convert relative path to absolute + absolute_path = os.path.join(os.path.dirname(__file__), file_path) + file_path = absolute_path + + Logger.info(f"Playing media: {file_name} from {file_path}") + + # Log media start + self.log_event(file_name, "STARTED") + + # Cancel existing timers + self.cancel_timers() + + # Handle different media types + if file_path.startswith('text://'): + self.show_text_content(file_path[7:], duration) + elif file_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')): + self.play_video(file_path) + elif os.path.exists(file_path) and file_path.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')): + self.show_image(file_path, duration) + else: + Logger.error(f"Unsupported or missing media: {file_path}") + self.status_label.config(text=f"Missing or unsupported media:\n{file_name}") + # Schedule next media after short delay + self.auto_advance_timer = self.root.after(5000, self.next_media) + + def play_video(self, file_path): + """Play video file""" + # Clear any status text and hide status label + self.status_label.place_forget() + + # Check if PIL is available for video playback + if not PIL_AVAILABLE: + Logger.error("PIL not available - cannot play video") + self.status_label.config(text="PIL/Pillow not available\nCannot play video files") + self.status_label.place(relx=0.5, rely=0.5, anchor='center') + self.auto_advance_timer = self.root.after(5000, self.next_media) + return + + # Use OpenCV to play video in a separate thread + def video_player(): + try: + cap = cv2.VideoCapture(file_path) + + if not cap.isOpened(): + raise ValueError(f"Cannot open video file: {file_path}") + + # Get video properties + fps = cap.get(cv2.CAP_PROP_FPS) + delay = int(1000 / fps) if fps > 0 else 30 # Default to 30 FPS if unknown + + # Get video dimensions + video_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + video_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + + # Get screen dimensions + screen_width = self.root.winfo_width() + screen_height = self.root.winfo_height() + + # Ensure we have valid screen dimensions + if screen_width <= 1 or screen_height <= 1: + screen_width = 1920 # Default fallback + screen_height = 1080 + + Logger.info(f"Video dimensions: {video_width}x{video_height}, " + f"Screen dimensions: {screen_width}x{screen_height}") + + while self.current_index < len(self.playlist) and not self.is_paused and self.running: + ret, frame = cap.read() + + if not ret: + break # End of video + + # Convert color from BGR to RGB + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + # Convert to PIL Image for better scaling + try: + img = Image.fromarray(frame) + + # Scale video frame using the scaling helper + final_img, offset = self.scale_image_to_screen(img, screen_width, screen_height, self.scaling_mode) + + # Convert to PhotoImage + photo = ImageTk.PhotoImage(final_img) + + # Update UI from main thread + self.root.after_idle(lambda p=photo: self._update_video_frame(p)) + + except Exception as img_error: + Logger.error(f"Video frame processing error: {img_error}") + break + + # Wait for the next frame + time.sleep(delay / 1000) + + cap.release() + + # Schedule next media from main thread + self.root.after_idle(lambda: setattr(self, 'auto_advance_timer', + self.root.after(1000, self.next_media))) + + except Exception as e: + Logger.error(f"Error playing video {file_path}: {e}") + # Show error from main thread + self.root.after_idle(lambda: self._show_video_error(str(e))) + + # Start video player thread + threading.Thread(target=video_player, daemon=True).start() + + def _update_video_frame(self, photo): + """Update video frame from main thread""" + try: + self.image_label.config(image=photo) + self.image_label.image = photo # Keep reference + except Exception as e: + Logger.error(f"Error updating video frame: {e}") + + def _show_video_error(self, error_msg): + """Show video error from main thread""" + try: + self.status_label.config(text=f"Video Error:\n{error_msg}") + self.status_label.place(relx=0.5, rely=0.5, anchor='center') + self.auto_advance_timer = self.root.after(5000, self.next_media) + except Exception as e: + Logger.error(f"Error showing video error: {e}") + self.auto_advance_timer = self.root.after(5000, self.next_media) + + def show_text_content(self, text, duration): + """Display text content""" + self.image_label.config(image='') + self.status_label.config(text=text) + + # Schedule next media + self.auto_advance_timer = self.root.after( + int(duration * 1000), + self.next_media + ) + + def show_image(self, file_path, duration): + """Display an image in full screen, properly fitted to screen size""" + try: + # Hide status label and clear any previous text + self.status_label.place_forget() + self.status_label.config(text="") + + if PIL_AVAILABLE: + # Use PIL for better image handling + img = Image.open(file_path) + original_size = img.size + + # Get actual screen dimensions + screen_width = self.root.winfo_width() + screen_height = self.root.winfo_height() + + # Ensure we have valid screen dimensions + if screen_width <= 1 or screen_height <= 1: + screen_width = 1920 # Default fallback + screen_height = 1080 + + # Scale image using the scaling helper + final_img, offset = self.scale_image_to_screen(img, screen_width, screen_height, self.scaling_mode) + + # Convert to PhotoImage + photo = ImageTk.PhotoImage(final_img) + + # Clear previous image and display new one + self.image_label.config(image=photo) + self.image_label.image = photo # Keep reference + + Logger.info(f"Successfully displayed image: {os.path.basename(file_path)} " + f"(Original: {original_size}, Screen: {screen_width}x{screen_height}, " + f"Mode: {self.scaling_mode}, Offset: {offset})") + else: + # Fall back to basic text display if PIL not available + self.image_label.config(image='') + self.status_label.config(text=f"IMAGE: {os.path.basename(file_path)}\n\n(Install PIL for image display)") + self.status_label.place(relx=0.5, rely=0.5, anchor='center') + Logger.warning("PIL not available - showing text placeholder for image") + + # Schedule next media + self.auto_advance_timer = self.root.after( + int(duration * 1000), + self.next_media + ) + + except Exception as e: + Logger.error(f"Failed to show image {file_path}: {e}") + self.image_label.config(image='') + self.status_label.config(text=f"Image Error:\n{os.path.basename(file_path)}\n{str(e)}") + self.status_label.place(relx=0.5, rely=0.5, anchor='center') + self.auto_advance_timer = self.root.after(5000, self.next_media) + + def next_media(self): + """Move to next media""" + self.cancel_timers() + + if not self.playlist: + return + + self.current_index = (self.current_index + 1) % len(self.playlist) + + # Check for playlist updates at end of cycle + if self.current_index == 0: + threading.Thread(target=self.check_playlist_updates, daemon=True).start() + + self.play_current_media() + + def previous_media(self): + """Move to previous media""" + self.cancel_timers() + + if not self.playlist: + return + + self.current_index = (self.current_index - 1) % len(self.playlist) + self.play_current_media() + + def toggle_play_pause(self): + """Toggle play/pause state""" + self.is_paused = not self.is_paused + + if self.is_paused: + self.play_pause_btn.config(text="โ–ถ Play") + self.cancel_timers() + else: + self.play_pause_btn.config(text="โธ Pause") + # Resume current media + self.play_current_media() + + Logger.info(f"Media {'paused' if self.is_paused else 'resumed'}") + + def cancel_timers(self): + """Cancel all active timers""" + if self.auto_advance_timer: + self.root.after_cancel(self.auto_advance_timer) + self.auto_advance_timer = None + + def show_controls(self): + """Show control panel""" + if self.control_frame: + self.control_frame.place(relx=0.98, rely=0.98, anchor='se') + + def hide_controls(self): + """Hide control panel""" + if self.control_frame: + self.control_frame.place_forget() + + def schedule_hide_controls(self): + """Schedule hiding controls after delay""" + if self.hide_controls_timer: + self.root.after_cancel(self.hide_controls_timer) + self.hide_controls_timer = self.root.after(10000, self.hide_controls) + + def on_mouse_click(self, event): + """Handle mouse clicks""" + self.show_controls() + self.schedule_hide_controls() + + def on_mouse_motion(self, event): + """Handle mouse motion""" + self.show_controls() + self.schedule_hide_controls() + + def on_key_press(self, event): + """Handle keyboard events""" + key = event.keysym.lower() + + if key == 'f': + self.toggle_fullscreen() + elif key == 'space': + self.toggle_play_pause() + elif key == 'left': + self.previous_media() + elif key == 'right': + self.next_media() + elif key == 'escape': + self.show_exit_dialog() + elif key == '1': + self.set_scaling_mode('fit') + elif key == '2': + self.set_scaling_mode('fill') + elif key == '3': + self.set_scaling_mode('stretch') + elif event.state & 0x4: # Ctrl key pressed + if key == 's': + self.open_settings() + + self.show_controls() + self.schedule_hide_controls() + + def set_scaling_mode(self, mode): + """Change the scaling mode and refresh current media""" + old_mode = self.scaling_mode + self.scaling_mode = mode + Logger.info(f"Scaling mode changed from '{old_mode}' to '{mode}'") + + # Show temporary notification + self.status_label.config(text=f"Scaling Mode: {mode.title()}\n" + f"1=Fit 2=Fill 3=Stretch") + self.status_label.place(relx=0.5, rely=0.05, anchor='center') + + # Hide notification after 2 seconds + self.root.after(2000, lambda: self.status_label.place_forget()) + + # Refresh current media with new scaling + if self.playlist and 0 <= self.current_index < len(self.playlist): + self.cancel_timers() + self.play_current_media() + + def toggle_fullscreen(self): + """Toggle fullscreen mode""" + self.is_fullscreen = not self.is_fullscreen + self.root.attributes('-fullscreen', self.is_fullscreen) + + def open_settings(self): + """Open settings window""" + if hasattr(self, 'settings_window') and self.settings_window and self.settings_window.winfo_exists(): + self.settings_window.lift() + return + + self.settings_window = SettingsWindow(self.root, self) + + def show_exit_dialog(self): + """Show modern password-protected exit dialog""" + try: + config = load_config() + quickconnect_key = config.get('quickconnect_key', '') + except: + quickconnect_key = '' + + # Create modern exit dialog + exit_dialog = tk.Toplevel(self.root) + exit_dialog.title("Exit Application") + exit_dialog.geometry("400x200") + exit_dialog.configure(bg='#2d2d2d') + exit_dialog.transient(self.root) + exit_dialog.grab_set() + exit_dialog.resizable(False, False) + + # Center the dialog using helper method + self.center_dialog_on_screen(exit_dialog, 400, 200) + + # Header with icon + header_frame = tk.Frame(exit_dialog, bg='#cc0000', height=60) + header_frame.pack(fill=tk.X) + header_frame.pack_propagate(False) + + icon_label = tk.Label(header_frame, text="โš ", font=('Arial', 20, 'bold'), + fg='white', bg='#cc0000') + icon_label.pack(side=tk.LEFT, padx=15, pady=15) + + title_label = tk.Label(header_frame, text="Exit Application", + font=('Arial', 14, 'bold'), fg='white', bg='#cc0000') + title_label.pack(side=tk.LEFT, pady=15) + + # Content frame + content_frame = tk.Frame(exit_dialog, bg='#2d2d2d', padx=20, pady=20) + content_frame.pack(fill=tk.BOTH, expand=True) + + # Password prompt + prompt_label = tk.Label(content_frame, text="Enter password to exit:", + font=('Arial', 11), fg='white', bg='#2d2d2d') + prompt_label.pack(pady=(0, 10)) + + # Password entry + password_var = tk.StringVar() + password_entry = tk.Entry(content_frame, textvariable=password_var, + font=('Arial', 11), show='*', width=25, + bg='#404040', fg='white', insertbackground='white', + relief=tk.FLAT, bd=5) + password_entry.pack(pady=(0, 15)) + password_entry.focus_set() + + # Button frame + button_frame = tk.Frame(content_frame, bg='#2d2d2d') + button_frame.pack(fill=tk.X) + + def check_password(): + if password_var.get() == quickconnect_key: + exit_dialog.destroy() + self.exit_application() + elif password_var.get(): # Only show error if password was entered + # Show error in red + error_label.config(text="โœ— Incorrect password", fg='#ff4444') + password_entry.delete(0, tk.END) + password_entry.focus_set() + + def cancel_exit(): + exit_dialog.destroy() + + # Error label (hidden initially) + error_label = tk.Label(content_frame, text="", font=('Arial', 9), + bg='#2d2d2d') + error_label.pack() + + # Buttons + cancel_btn = tk.Button(button_frame, text="Cancel", command=cancel_exit, + bg='#555555', fg='white', font=('Arial', 10, 'bold'), + relief=tk.FLAT, padx=20, pady=8, width=10) + cancel_btn.pack(side=tk.RIGHT, padx=(10, 0)) + + exit_btn = tk.Button(button_frame, text="Exit", command=check_password, + bg='#cc0000', fg='white', font=('Arial', 10, 'bold'), + relief=tk.FLAT, padx=20, pady=8, width=10) + exit_btn.pack(side=tk.RIGHT) + + # Bind Enter key to check password + password_entry.bind('', lambda e: check_password()) + exit_dialog.bind('', lambda e: cancel_exit()) + + def exit_application(self): + """Exit the application""" + Logger.info("Application exit requested") + self.running = False + self.root.quit() + self.root.destroy() + + def center_dialog_on_screen(self, dialog, width, height): + """Center a dialog window on screen regardless of screen size""" + dialog.update_idletasks() # Ensure geometry is calculated + screen_width = dialog.winfo_screenwidth() + screen_height = dialog.winfo_screenheight() + + # Calculate center position + center_x = int((screen_width - width) / 2) + center_y = int((screen_height - height) / 2) + + # Ensure the dialog doesn't go off-screen on smaller displays + center_x = max(0, min(center_x, screen_width - width)) + center_y = max(0, min(center_y, screen_height - height)) + + dialog.geometry(f"{width}x{height}+{center_x}+{center_y}") + + # Bring to front and focus + dialog.lift() + dialog.focus_force() + + return center_x, center_y + + def check_playlist_updates(self): + """Check for playlist updates from server with fallback protection""" + try: + config = load_config() + local_version = config.get('playlist_version', 0) + + server_playlist_data = fetch_server_playlist() + server_version = server_playlist_data.get('version', 0) + + if server_version > local_version: + Logger.info(f"Updating playlist: {local_version} -> {server_version}") + + # Clean old files + local_playlist_data = load_local_playlist() + clean_unused_files(local_playlist_data.get('playlist', [])) + + # Download new content + download_media_files( + server_playlist_data.get('playlist', []), + server_version + ) + + # Update local playlist + local_playlist_data = load_local_playlist() + self.playlist = local_playlist_data.get('playlist', []) + + # Reset to beginning of playlist + self.current_index = 0 + + Logger.info("Playlist updated successfully") + + # Continue with current media after update + self.play_current_media() + else: + Logger.info("No playlist updates available") + + except requests.exceptions.ConnectTimeout: + Logger.warning("Server connection timeout during update check - continuing with current playlist") + except requests.exceptions.ConnectionError: + Logger.warning("Cannot connect to server during update check - continuing with current playlist") + except requests.exceptions.Timeout: + Logger.warning("Server request timeout during update check - continuing with current playlist") + except Exception as e: + Logger.warning(f"Failed to check playlist updates: {e} - continuing with current playlist") + + def log_event(self, file_name, event): + """Log media events""" + try: + timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + log_message = f"{timestamp} - {event}: {file_name}\n" + + # Update the log file path to the resources directory + log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'log.txt') + with open(log_file, 'a') as f: + f.write(log_message) + + except Exception as e: + Logger.error(f"Failed to log event: {e}") + + def start_periodic_checks(self): + """Start periodic playlist checks""" + def check_loop(): + """Background thread for periodic checks""" + while self.running: + try: + time.sleep(300) # Check every 5 minutes + if self.running: + self.check_playlist_updates() + except Exception as e: + Logger.error(f"Error in periodic check: {e}") + + # Start background thread + threading.Thread(target=check_loop, daemon=True).start() + + def run(self): + """Start the application""" + Logger.info("Starting Simple Tkinter Media Player") + try: + self.root.mainloop() + except KeyboardInterrupt: + self.exit_application() + except Exception as e: + Logger.error(f"Application error: {e}") + print(f"Error: {e}") + + +class SettingsWindow: + def __init__(self, parent, app): + self.parent = parent + self.app = app + self.window = tk.Toplevel(parent) + + # Initialize virtual keyboard for touch displays + self.virtual_keyboard = VirtualKeyboard(self.window, dark_theme=True) + + self.setup_window() + self.create_widgets() + self.load_config() + + # Setup touch optimization + self.setup_touch_optimization() + + def setup_window(self): + """Setup settings window with enhanced dark theme""" + self.window.title("๐ŸŽฌ Signage Player Settings") + self.window.geometry("900x700") + + # Enhanced dark theme colors + self.colors = { + 'bg_primary': '#1e2124', # Very dark background + 'bg_secondary': '#2f3136', # Slightly lighter background + 'bg_tertiary': '#36393f', # Card backgrounds + 'accent': '#7289da', # Discord-like blue accent + 'accent_hover': '#677bc4', # Darker accent for hover + 'success': '#43b581', # Green for success + 'warning': '#faa61a', # Orange for warnings + 'danger': '#f04747', # Red for errors + 'text_primary': '#ffffff', # White text + 'text_secondary': '#b9bbbe', # Gray text + 'text_muted': '#72767d', # Muted text + 'border': '#202225' # Border color + } + + self.window.configure(bg=self.colors['bg_primary']) + self.window.transient(self.parent) + self.window.grab_set() + + # Set window properties + self.window.resizable(True, True) + self.window.minsize(700, 500) + + # Set window icon if available + try: + # Try to use a simple emoji icon + self.window.iconname("๐ŸŽฌ") + except: + pass + + # Center the window on screen using helper method + self.center_window_on_screen(self.window, 900, 700) + + # Add subtle window border effect + self.window.configure(highlightbackground=self.colors['border'], highlightthickness=1) + + def create_widgets(self): + """Create settings widgets with enhanced dark theme styling""" + # Configure enhanced custom styles + style = ttk.Style() + style.theme_use('clam') + + # Enhanced dark theme styles + style.configure('Dark.TNotebook', + background=self.colors['bg_secondary'], + borderwidth=0, + tabmargins=[2, 5, 2, 0]) + + style.configure('Dark.TNotebook.Tab', + padding=[20, 12], + font=('Segoe UI', 11, 'bold'), + background=self.colors['bg_tertiary'], + foreground=self.colors['text_secondary'], + borderwidth=1, + focuscolor='none') + + style.map('Dark.TNotebook.Tab', + background=[('selected', self.colors['accent']), + ('active', self.colors['accent_hover'])], + foreground=[('selected', self.colors['text_primary']), + ('active', self.colors['text_primary'])]) + + style.configure('Dark.TFrame', background=self.colors['bg_secondary']) + style.configure('Dark.TLabel', + background=self.colors['bg_secondary'], + foreground=self.colors['text_primary'], + font=('Segoe UI', 10)) + + style.configure('Dark.TEntry', + fieldbackground=self.colors['bg_tertiary'], + foreground=self.colors['text_primary'], + bordercolor=self.colors['border'], + lightcolor=self.colors['bg_tertiary'], + darkcolor=self.colors['bg_tertiary'], + font=('Segoe UI', 10), + insertcolor=self.colors['text_primary']) + + # Main container frame + main_frame = tk.Frame(self.window, bg=self.colors['bg_primary']) + main_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) + + # Header section with gradient-like effect + header_frame = tk.Frame(main_frame, bg=self.colors['bg_secondary'], height=80) + header_frame.pack(fill=tk.X, padx=0, pady=0) + header_frame.pack_propagate(False) + + # Title with enhanced styling + title_container = tk.Frame(header_frame, bg=self.colors['bg_secondary']) + title_container.pack(expand=True, fill=tk.BOTH) + + title_label = tk.Label(title_container, + text="๐ŸŽฌ Digital Signage Control Center", + font=('Segoe UI', 24, 'bold'), + fg=self.colors['text_primary'], + bg=self.colors['bg_secondary']) + title_label.pack(expand=True) + + subtitle_label = tk.Label(title_container, + text="Configure your digital signage display settings", + font=('Segoe UI', 11), + fg=self.colors['text_secondary'], + bg=self.colors['bg_secondary']) + subtitle_label.pack() + + # Content area + content_frame = tk.Frame(main_frame, bg=self.colors['bg_primary']) + content_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) + + # Create enhanced notebook with dark theme + notebook = ttk.Notebook(content_frame, style='Dark.TNotebook') + notebook.pack(fill=tk.BOTH, expand=True, pady=(0, 20)) + + # Create tabs with enhanced styling + self.create_connection_tab_enhanced(notebook) + self.create_display_tab_enhanced(notebook) + self.create_advanced_tab_enhanced(notebook) + self.create_logs_tab_enhanced(notebook) + self.create_about_tab_enhanced(notebook) + + # Enhanced bottom button section + self.create_bottom_controls(content_frame) + + # Load initial data + self.load_logs() + + def create_connection_tab_enhanced(self, notebook): + """Create enhanced connection settings tab""" + tab_frame = tk.Frame(notebook, bg=self.colors['bg_secondary']) + notebook.add(tab_frame, text="๐ŸŒ Connection") + + # Create scrollable content + canvas = tk.Canvas(tab_frame, bg=self.colors['bg_secondary'], highlightthickness=0) + scrollbar = tk.Scrollbar(tab_frame, orient="vertical", command=canvas.yview, + bg=self.colors['bg_tertiary'], troughcolor=self.colors['bg_primary']) + scrollable_frame = tk.Frame(canvas, bg=self.colors['bg_secondary']) + + scrollable_frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) + canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas.configure(yscrollcommand=scrollbar.set) + + # Server connection card + server_card = self.create_settings_card(scrollable_frame, "๐Ÿ–ฅ๏ธ Server Connection", + "Configure your signage server connection details") + + # Server IP/Domain + self.create_input_field(server_card, "Server IP/Domain:", "server_ip_var", + placeholder="e.g., digi-server.example.com") + + # Port + self.create_input_field(server_card, "Port:", "port_var", width=15, + placeholder="8880") + + # Device settings card + device_card = self.create_settings_card(scrollable_frame, "๐Ÿ“ฑ Device Settings", + "Identify this display device") + + # Screen/Device name + self.create_input_field(device_card, "Device Name:", "screen_name_var", + placeholder="e.g., lobby-display-01") + + # QuickConnect key + self.create_input_field(device_card, "QuickConnect Key:", "quickconnect_var", + password=True, placeholder="Enter your access key") + + # Connection testing card + test_card = self.create_settings_card(scrollable_frame, "๐Ÿ”— Connection Test", + "Test your server connection") + + test_btn_frame = tk.Frame(test_card, bg=self.colors['bg_tertiary']) + test_btn_frame.pack(fill=tk.X, pady=10) + + test_btn = self.create_action_button(test_btn_frame, "๐Ÿ”— Test Connection", + self.test_connection, self.colors['accent']) + test_btn.pack(side=tk.LEFT, padx=5) + + canvas.pack(side="left", fill="both", expand=True, padx=20, pady=20) + scrollbar.pack(side="right", fill="y") + + def create_display_tab_enhanced(self, notebook): + """Create enhanced display settings tab""" + tab_frame = tk.Frame(notebook, bg=self.colors['bg_secondary']) + notebook.add(tab_frame, text="๐Ÿ–ผ๏ธ Display") + + main_container = tk.Frame(tab_frame, bg=self.colors['bg_secondary']) + main_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) + + # Resolution settings card + resolution_card = self.create_settings_card(main_container, "๐Ÿ–ฅ๏ธ Screen Resolution", + "Configure display resolution settings") + + # Resolution input fields + resolution_inputs = tk.Frame(resolution_card, bg=self.colors['bg_tertiary']) + resolution_inputs.pack(fill=tk.X, pady=10) + + self.create_input_field(resolution_inputs, "Width (px):", "screen_w_var", + width=15, placeholder="1920") + self.create_input_field(resolution_inputs, "Height (px):", "screen_h_var", + width=15, placeholder="1080") + + # Preset buttons with enhanced styling + presets_frame = tk.Frame(resolution_card, bg=self.colors['bg_tertiary']) + presets_frame.pack(fill=tk.X, pady=15) + + tk.Label(presets_frame, text="Quick Presets:", + font=('Segoe UI', 11, 'bold'), + bg=self.colors['bg_tertiary'], + fg=self.colors['text_primary']).pack(anchor=tk.W, pady=(0, 10)) + + preset_buttons = tk.Frame(presets_frame, bg=self.colors['bg_tertiary']) + preset_buttons.pack(anchor=tk.W) + + presets = [("๐Ÿ“ฑ HD", "1366", "768"), ("๐Ÿ’ป Full HD", "1920", "1080"), + ("๐Ÿ–ฅ๏ธ 4K", "3840", "2160"), ("๐Ÿ“บ Classic", "1024", "768")] + + for preset_name, width, height in presets: + btn = self.create_preset_button(preset_buttons, preset_name, width, height) + btn.pack(side=tk.LEFT, padx=3, pady=2) + + # Scaling mode settings card + scaling_card = self.create_settings_card(main_container, "๐Ÿ“ Image/Video Scaling", + "Configure how images and videos are displayed on screen") + + # Scaling mode options + self.scaling_mode_var = tk.StringVar(value=self.app.scaling_mode if hasattr(self.app, 'scaling_mode') else 'fit') + + scaling_label = tk.Label(scaling_card, text="Scaling Mode:", + font=('Segoe UI', 12, 'bold'), + bg=self.colors['bg_tertiary'], + fg=self.colors['text_primary']) + scaling_label.pack(anchor=tk.W, pady=(0, 10)) + + scaling_options = tk.Frame(scaling_card, bg=self.colors['bg_tertiary']) + scaling_options.pack(fill=tk.X, pady=5) + + # Radio buttons for scaling modes + modes = [ + ("fit", "๐Ÿ–ผ๏ธ Fit", "Maintain aspect ratio, add black bars if needed"), + ("fill", "๐Ÿ” Fill", "Maintain aspect ratio, crop to fill entire screen"), + ("stretch", "โ†”๏ธ Stretch", "Ignore aspect ratio, stretch to fill screen") + ] + + for mode_value, mode_label, mode_desc in modes: + mode_frame = tk.Frame(scaling_options, bg=self.colors['bg_tertiary']) + mode_frame.pack(fill=tk.X, pady=2) + + radio_btn = tk.Radiobutton(mode_frame, + text=mode_label, + variable=self.scaling_mode_var, + value=mode_value, + bg=self.colors['bg_tertiary'], + fg=self.colors['text_primary'], + font=('Segoe UI', 11, 'bold'), + selectcolor=self.colors['bg_primary'], + activebackground=self.colors['bg_tertiary'], + activeforeground=self.colors['text_primary'], + command=lambda: self.update_scaling_mode()) + radio_btn.pack(side=tk.LEFT, anchor=tk.W) + + desc_label = tk.Label(mode_frame, text=f" - {mode_desc}", + font=('Segoe UI', 9), + bg=self.colors['bg_tertiary'], + fg=self.colors['text_secondary']) + desc_label.pack(side=tk.LEFT, anchor=tk.W, padx=(10, 0)) + + # Keyboard shortcuts info + shortcuts_frame = tk.Frame(scaling_card, bg=self.colors['bg_tertiary']) + shortcuts_frame.pack(fill=tk.X, pady=(15, 0)) + + shortcuts_label = tk.Label(shortcuts_frame, + text="๐Ÿ’ก Tip: Use keyboard shortcuts 1, 2, 3 to quickly change scaling mode during playback", + font=('Segoe UI', 9, 'italic'), + bg=self.colors['bg_tertiary'], + fg=self.colors['text_muted'], + wraplength=400) + shortcuts_label.pack(anchor=tk.W) + + def update_scaling_mode(self): + """Update the scaling mode in the main app""" + new_mode = self.scaling_mode_var.get() + if hasattr(self.app, 'set_scaling_mode'): + self.app.set_scaling_mode(new_mode) + else: + self.app.scaling_mode = new_mode + + def create_advanced_tab_enhanced(self, notebook): + """Create enhanced advanced settings tab""" + tab_frame = tk.Frame(notebook, bg=self.colors['bg_secondary']) + notebook.add(tab_frame, text="โš™๏ธ Advanced") + + main_container = tk.Frame(tab_frame, bg=self.colors['bg_secondary']) + main_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) + + # Sync settings card + sync_card = self.create_settings_card(main_container, "๐Ÿ”„ Synchronization", + "Configure automatic content updates") + + self.create_input_field(sync_card, "Auto-refresh (minutes):", "refresh_interval_var", + width=15, placeholder="15") + + # Performance settings card + perf_card = self.create_settings_card(main_container, "โšก Performance", + "Optimize playback performance") + + self.hardware_accel_var = tk.BooleanVar(value=True) + self.create_checkbox(perf_card, "Enable hardware acceleration", self.hardware_accel_var) + + self.cache_media_var = tk.BooleanVar(value=True) + self.create_checkbox(perf_card, "Cache media files locally", self.cache_media_var) + + self.auto_retry_var = tk.BooleanVar(value=True) + self.create_checkbox(perf_card, "Auto-retry failed downloads", self.auto_retry_var) + + def create_logs_tab_enhanced(self, notebook): + """Create enhanced logs tab""" + tab_frame = tk.Frame(notebook, bg=self.colors['bg_secondary']) + notebook.add(tab_frame, text="๐Ÿ“‹ Logs") + + main_container = tk.Frame(tab_frame, bg=self.colors['bg_secondary']) + main_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=20) + + # Log header + log_header = tk.Frame(main_container, bg=self.colors['bg_secondary']) + log_header.pack(fill=tk.X, pady=(0, 15)) + + tk.Label(log_header, text="๐Ÿ“‹ Application Logs", + font=('Segoe UI', 16, 'bold'), + bg=self.colors['bg_secondary'], + fg=self.colors['text_primary']).pack(side=tk.LEFT) + + refresh_btn = self.create_action_button(log_header, "๐Ÿ”„ Refresh Logs", + self.load_logs, self.colors['accent']) + refresh_btn.pack(side=tk.RIGHT) + + # Log display area + log_container = tk.Frame(main_container, bg=self.colors['bg_tertiary'], + relief=tk.FLAT, bd=2) + log_container.pack(fill=tk.BOTH, expand=True) + + self.log_text = tk.Text(log_container, + font=('Consolas', 9), + bg=self.colors['bg_primary'], + fg=self.colors['text_primary'], + insertbackground=self.colors['accent'], + selectbackground=self.colors['accent'], + selectforeground=self.colors['text_primary'], + relief=tk.FLAT, + bd=10, + wrap=tk.WORD) + + log_scrollbar = tk.Scrollbar(log_container, command=self.log_text.yview, + bg=self.colors['bg_tertiary']) + self.log_text.configure(yscrollcommand=log_scrollbar.set) + + self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + def create_about_tab_enhanced(self, notebook): + """Create enhanced about tab""" + tab_frame = tk.Frame(notebook, bg=self.colors['bg_secondary']) + notebook.add(tab_frame, text="โ„น๏ธ About") + + main_container = tk.Frame(tab_frame, bg=self.colors['bg_secondary']) + main_container.pack(fill=tk.BOTH, expand=True, padx=40, pady=40) + + # App branding section + branding_frame = tk.Frame(main_container, bg=self.colors['bg_secondary']) + branding_frame.pack(fill=tk.X, pady=(0, 40)) + + # Large app icon + icon_label = tk.Label(branding_frame, text="๐ŸŽฌ", + font=('Segoe UI Emoji', 64), + bg=self.colors['bg_secondary'], + fg=self.colors['accent']) + icon_label.pack() + + # App title and version + title_label = tk.Label(branding_frame, text="Digital Signage Player", + font=('Segoe UI', 24, 'bold'), + bg=self.colors['bg_secondary'], + fg=self.colors['text_primary']) + title_label.pack(pady=(10, 5)) + + version_label = tk.Label(branding_frame, text="Version 2.1 - Enhanced Dark Edition", + font=('Segoe UI', 12), + bg=self.colors['bg_secondary'], + fg=self.colors['text_secondary']) + version_label.pack() + + # Feature highlights + features_text = ( + "๐Ÿš€ Features:\n" + "โ€ข Modern dark theme interface\n" + "โ€ข High-resolution media display\n" + "โ€ข Remote playlist management\n" + "โ€ข Real-time content synchronization\n" + "โ€ข Hardware acceleration support\n" + "โ€ข Cross-platform compatibility\n" + "โ€ข Advanced logging and diagnostics\n\n" + "โŒจ๏ธ Keyboard Shortcuts:\n" + "F11 - Toggle fullscreen mode\n" + "Space - Play/Pause media\n" + "โ† โ†’ - Navigate between media\n" + "1 - Set scaling mode to Fit (maintain aspect ratio with black bars)\n" + "2 - Set scaling mode to Fill (maintain aspect ratio, crop to fill screen)\n" + "3 - Set scaling mode to Stretch (ignore aspect ratio, stretch to fill)\n" + "Ctrl+S - Open settings panel\n" + "Escape - Exit application (password protected)\n\n" + "Built with โค๏ธ using Python & Tkinter" + ) + + features_label = tk.Label(main_container, text=features_text, + justify=tk.LEFT, + font=('Segoe UI', 11), + bg=self.colors['bg_secondary'], + fg=self.colors['text_primary'], + wraplength=600) + features_label.pack(anchor=tk.W) + + def create_bottom_controls(self, parent): + """Create enhanced bottom control buttons""" + controls_frame = tk.Frame(parent, bg=self.colors['bg_secondary'], + relief=tk.FLAT, bd=1) + controls_frame.pack(fill=tk.X, pady=(10, 0)) + + # Left side buttons + left_buttons = tk.Frame(controls_frame, bg=self.colors['bg_secondary']) + left_buttons.pack(side=tk.LEFT, padx=10, pady=15) + + save_btn = self.create_action_button(left_buttons, "๐Ÿ’พ Save Configuration", + self.save_config, self.colors['success']) + save_btn.pack(side=tk.LEFT, padx=5) + + test_btn = self.create_action_button(left_buttons, "๐Ÿ”— Test Connection", + self.test_connection, self.colors['accent']) + test_btn.pack(side=tk.LEFT, padx=5) + + refresh_btn = self.create_action_button(left_buttons, "๐Ÿ”„ Refresh Playlist", + self.force_playlist_refresh, self.colors['warning']) + refresh_btn.pack(side=tk.LEFT, padx=5) + + # Right side buttons + right_buttons = tk.Frame(controls_frame, bg=self.colors['bg_secondary']) + right_buttons.pack(side=tk.RIGHT, padx=10, pady=15) + + cancel_btn = self.create_action_button(right_buttons, "โŒ Cancel", + self.window.destroy, self.colors['danger']) + cancel_btn.pack(side=tk.RIGHT, padx=5) + + # Status display + self.status_frame = tk.Frame(parent, bg=self.colors['bg_primary']) + self.status_frame.pack(fill=tk.X, pady=(10, 0)) + + self.connection_status = tk.Label(self.status_frame, + text="Ready to configure your digital signage settings", + bg=self.colors['bg_primary'], + fg=self.colors['text_secondary'], + font=('Segoe UI', 9)) + self.connection_status.pack(anchor=tk.W, padx=10, pady=5) + + def create_settings_card(self, parent, title, description): + """Create a settings card with enhanced styling""" + card_frame = tk.Frame(parent, bg=self.colors['bg_tertiary'], + relief=tk.FLAT, bd=2) + card_frame.pack(fill=tk.X, pady=(0, 20), padx=5) + + # Card header + header_frame = tk.Frame(card_frame, bg=self.colors['bg_tertiary']) + header_frame.pack(fill=tk.X, padx=20, pady=(15, 5)) + + title_label = tk.Label(header_frame, text=title, + font=('Segoe UI', 14, 'bold'), + bg=self.colors['bg_tertiary'], + fg=self.colors['text_primary']) + title_label.pack(anchor=tk.W) + + desc_label = tk.Label(header_frame, text=description, + font=('Segoe UI', 9), + bg=self.colors['bg_tertiary'], + fg=self.colors['text_secondary']) + desc_label.pack(anchor=tk.W, pady=(2, 0)) + + # Card content area + content_frame = tk.Frame(card_frame, bg=self.colors['bg_tertiary']) + content_frame.pack(fill=tk.X, padx=20, pady=(10, 15)) + + return content_frame + + def create_input_field(self, parent, label_text, var_name, width=35, placeholder="", password=False): + """Create a touch-optimized input field with virtual keyboard support""" + field_frame = tk.Frame(parent, bg=self.colors['bg_tertiary']) + field_frame.pack(fill=tk.X, pady=12) # Increased padding for touch + + label = tk.Label(field_frame, text=label_text, + font=('Segoe UI', 12, 'bold'), # Larger font for touch + bg=self.colors['bg_tertiary'], + fg=self.colors['text_primary'], + width=18, anchor='w') + label.pack(side=tk.LEFT) + + # Create StringVar if it doesn't exist + if not hasattr(self, var_name): + setattr(self, var_name, tk.StringVar()) + + var = getattr(self, var_name) + + # Use touch-optimized entry with virtual keyboard + entry = TouchOptimizedEntry(field_frame, + virtual_keyboard=self.virtual_keyboard, + textvariable=var, + width=width, + font=('Segoe UI', 12), # Larger font + bg=self.colors['bg_primary'], + fg=self.colors['text_primary'], + insertbackground=self.colors['accent'], + relief=tk.FLAT, + bd=10) # Larger border for easier touch + + if password: + entry.configure(show='*') + + entry.pack(side=tk.LEFT, padx=(15, 0), pady=5) + + # Add placeholder text effect + if placeholder: + self.add_placeholder(entry, placeholder) + + return entry + + def create_checkbox(self, parent, text, variable): + """Create a checkbox with dark theme styling""" + cb_frame = tk.Frame(parent, bg=self.colors['bg_tertiary']) + cb_frame.pack(fill=tk.X, pady=5) + + checkbox = tk.Checkbutton(cb_frame, text=text, variable=variable, + bg=self.colors['bg_tertiary'], + fg=self.colors['text_primary'], + font=('Segoe UI', 10), + selectcolor=self.colors['bg_primary'], + activebackground=self.colors['bg_tertiary'], + activeforeground=self.colors['text_primary']) + checkbox.pack(anchor=tk.W, padx=5) + + def create_action_button(self, parent, text, command, color): + """Create a touch-optimized action button with enhanced styling""" + button = TouchOptimizedButton(parent, + text=text, + command=command, + bg=color, + fg=self.colors['text_primary'], + font=('Segoe UI', 12, 'bold'), # Larger font + relief=tk.FLAT, + padx=25, # Larger padding for touch + pady=15, # Larger padding for touch + cursor='hand2', + bd=3) + + # Add enhanced hover effects for touch feedback + def on_enter(e): + button.configure(bg=self.lighten_color(color), relief=tk.RAISED) + + def on_leave(e): + button.configure(bg=color, relief=tk.FLAT) + + def on_press(e): + button.configure(relief=tk.SUNKEN) + + def on_release(e): + button.configure(relief=tk.FLAT) + + button.bind("", on_enter) + button.bind("", on_leave) + button.bind("", on_press) + button.bind("", on_release) + + return button + + def create_preset_button(self, parent, text, width, height): + """Create a touch-optimized preset resolution button""" + button = TouchOptimizedButton(parent, + text=text, + command=lambda: self.set_resolution_preset(width, height), + bg=self.colors['bg_primary'], + fg=self.colors['text_primary'], + font=('Segoe UI', 10, 'bold'), + relief=tk.FLAT, + padx=20, # Larger for touch + pady=10, # Larger for touch + cursor='hand2') + + def on_enter(e): + button.configure(bg=self.colors['accent'], relief=tk.RAISED) + + def on_leave(e): + button.configure(bg=self.colors['bg_primary'], relief=tk.FLAT) + + def on_press(e): + button.configure(relief=tk.SUNKEN) + + def on_release(e): + button.configure(relief=tk.FLAT) + + button.bind("", on_enter) + button.bind("", on_leave) + button.bind("", on_press) + button.bind("", on_release) + + return button + + def add_placeholder(self, entry, placeholder_text): + """Add placeholder text to entry widget""" + entry.insert(0, placeholder_text) + entry.configure(fg=self.colors['text_muted']) + + def on_focus_in(event): + if entry.get() == placeholder_text: + entry.delete(0, tk.END) + entry.configure(fg=self.colors['text_primary']) + + def on_focus_out(event): + if not entry.get(): + entry.insert(0, placeholder_text) + entry.configure(fg=self.colors['text_muted']) + + entry.bind('', on_focus_in) + entry.bind('', on_focus_out) + + def lighten_color(self, color): + """Lighten a hex color for hover effects""" + color = color.lstrip('#') + rgb = tuple(int(color[i:i+2], 16) for i in (0, 2, 4)) + lighter_rgb = tuple(min(255, int(c * 1.2)) for c in rgb) + return f"#{lighter_rgb[0]:02x}{lighter_rgb[1]:02x}{lighter_rgb[2]:02x}" + + def center_window_on_screen(self, window, width, height): + """Center a window on screen regardless of screen size""" + window.update_idletasks() # Ensure geometry is calculated + screen_width = window.winfo_screenwidth() + screen_height = window.winfo_screenheight() + + # Calculate center position + center_x = int((screen_width - width) / 2) + center_y = int((screen_height - height) / 2) + + # Ensure the window doesn't go off-screen on smaller displays + center_x = max(0, min(center_x, screen_width - width)) + center_y = max(0, min(center_y, screen_height - height)) + + window.geometry(f"{width}x{height}+{center_x}+{center_y}") + + # Bring to front and focus + window.lift() + window.focus_force() + + return center_x, center_y + + def setup_touch_optimization(self): + """Setup touch-friendly optimizations""" + # Hide virtual keyboard when clicking outside input fields + def hide_keyboard_on_click(event): + # Check if click is not on an entry widget + if not isinstance(event.widget, (tk.Entry, TouchOptimizedEntry)): + self.virtual_keyboard.hide_keyboard() + + self.window.bind("", hide_keyboard_on_click, "+") + + # Make window touch-friendly by increasing minimum size + self.window.minsize(800, 600) + + # Override window close to hide keyboard first + original_destroy = self.window.destroy + def enhanced_destroy(): + self.virtual_keyboard.hide_keyboard() + original_destroy() + + self.window.destroy = enhanced_destroy + self.window.protocol("WM_DELETE_WINDOW", enhanced_destroy) + + def set_resolution_preset(self, width, height): + """Set resolution to preset values with visual feedback""" + self.screen_w_var.set(width) + self.screen_h_var.set(height) + self.connection_status.configure( + text=f"โœ… Resolution preset applied: {width}x{height}", + fg=self.colors['success'] + ) + + # Reset status color after 3 seconds + self.window.after(3000, lambda: self.connection_status.configure( + fg=self.colors['text_secondary'] + )) + + def load_config(self): + """Load current configuration with enhanced feedback""" + try: + Logger.info("Loading configuration in enhanced settings window") + config = load_config() + Logger.info(f"Config loaded: {config}") + + # Set values for connection settings + if hasattr(self, 'screen_name_var'): + self.screen_name_var.set(config.get('screen_name', '')) + if hasattr(self, 'server_ip_var'): + self.server_ip_var.set(config.get('server_ip', '')) + if hasattr(self, 'port_var'): + self.port_var.set(config.get('port', '8880')) + if hasattr(self, 'quickconnect_var'): + self.quickconnect_var.set(config.get('quickconnect_key', '')) + + # Set values for display settings + if hasattr(self, 'screen_w_var'): + self.screen_w_var.set(config.get('screen_w', '1920')) + if hasattr(self, 'screen_h_var'): + self.screen_h_var.set(config.get('screen_h', '1080')) + if hasattr(self, 'scaling_mode_var'): + self.scaling_mode_var.set(config.get('scaling_mode', 'fit')) + + # Set values for advanced settings + if hasattr(self, 'refresh_interval_var'): + self.refresh_interval_var.set(config.get('refresh_interval', '15')) + if hasattr(self, 'hardware_accel_var'): + self.hardware_accel_var.set(config.get('hardware_acceleration', True)) + if hasattr(self, 'cache_media_var'): + self.cache_media_var.set(config.get('cache_media', True)) + if hasattr(self, 'auto_retry_var'): + self.auto_retry_var.set(config.get('auto_retry', True)) + + # Update status with success message + if hasattr(self, 'connection_status'): + self.connection_status.configure( + text="โœ… Configuration loaded successfully", + fg=self.colors['success'] + ) + # Reset status color after 3 seconds + self.window.after(3000, lambda: self.connection_status.configure( + fg=self.colors['text_secondary'] + )) + + Logger.info("Configuration values loaded successfully in enhanced settings") + + except Exception as e: + Logger.error(f"Failed to load config in enhanced settings: {e}") + + # Set default values if loading fails + if hasattr(self, 'screen_name_var'): + self.screen_name_var.set('') + if hasattr(self, 'server_ip_var'): + self.server_ip_var.set('') + if hasattr(self, 'port_var'): + self.port_var.set('8880') + if hasattr(self, 'quickconnect_var'): + self.quickconnect_var.set('') + if hasattr(self, 'screen_w_var'): + self.screen_w_var.set('1920') + if hasattr(self, 'screen_h_var'): + self.screen_h_var.set('1080') + + # Set advanced defaults + if hasattr(self, 'refresh_interval_var'): + self.refresh_interval_var.set('15') + if hasattr(self, 'hardware_accel_var'): + self.hardware_accel_var.set(True) + if hasattr(self, 'cache_media_var'): + self.cache_media_var.set(True) + if hasattr(self, 'auto_retry_var'): + self.auto_retry_var.set(True) + + # Show error message with enhanced styling + if hasattr(self, 'connection_status'): + self.connection_status.configure( + text=f"โš ๏ธ Warning: Could not load existing config: {str(e)[:50]}...", + fg=self.colors['warning'] + ) + + def save_config(self): + """Save configuration with enhanced feedback""" + try: + # Load existing config or create new one + try: + config = load_config() + except: + config = {} + + # Update with new values from the enhanced interface + if hasattr(self, 'screen_name_var'): + config['screen_name'] = self.screen_name_var.get() + if hasattr(self, 'server_ip_var'): + config['server_ip'] = self.server_ip_var.get() + if hasattr(self, 'port_var'): + config['port'] = self.port_var.get() + if hasattr(self, 'quickconnect_var'): + config['quickconnect_key'] = self.quickconnect_var.get() + if hasattr(self, 'screen_w_var'): + config['screen_w'] = self.screen_w_var.get() + if hasattr(self, 'screen_h_var'): + config['screen_h'] = self.screen_h_var.get() + + # Save advanced settings if they exist + if hasattr(self, 'refresh_interval_var'): + config['refresh_interval'] = self.refresh_interval_var.get() + if hasattr(self, 'hardware_accel_var'): + config['hardware_acceleration'] = self.hardware_accel_var.get() + if hasattr(self, 'cache_media_var'): + config['cache_media'] = self.cache_media_var.get() + if hasattr(self, 'auto_retry_var'): + config['auto_retry'] = self.auto_retry_var.get() + + # Save display settings + if hasattr(self, 'scaling_mode_var'): + config['scaling_mode'] = self.scaling_mode_var.get() + # Also update the main app's scaling mode + if hasattr(self.app, 'scaling_mode'): + self.app.scaling_mode = self.scaling_mode_var.get() + + # Ensure directory exists + config_dir = os.path.dirname(CONFIG_FILE) + os.makedirs(config_dir, exist_ok=True) + + with open(CONFIG_FILE, 'w') as f: + json.dump(config, f, indent=4) + + Logger.info(f"Enhanced configuration saved to {CONFIG_FILE}") + + # Show enhanced success message + self.show_enhanced_success_message("Configuration saved successfully!") + + # Ask if user wants to refresh playlist now + if messagebox.askyesno("Refresh Playlist", + "Configuration saved! Would you like to refresh the playlist from server now?", + icon='question'): + self.force_playlist_refresh() + + self.window.destroy() + + except Exception as e: + Logger.error(f"Failed to save enhanced configuration: {e}") + self.show_enhanced_error_message(f"Failed to save configuration: {e}") + + def show_enhanced_success_message(self, message): + """Show an enhanced success message with dark theme""" + success_window = tk.Toplevel(self.window) + success_window.title("Success") + success_window.geometry("400x150") + success_window.configure(bg=self.colors['bg_primary']) + success_window.transient(self.window) + success_window.grab_set() + success_window.resizable(False, False) + + # Center the window using helper method + self.center_window_on_screen(success_window, 400, 150) + + # Main frame + main_frame = tk.Frame(success_window, bg=self.colors['bg_primary'], padx=30, pady=20) + main_frame.pack(fill=tk.BOTH, expand=True) + + # Success icon + icon_label = tk.Label(main_frame, text="โœ…", font=('Segoe UI Emoji', 32), + fg=self.colors['success'], bg=self.colors['bg_primary']) + icon_label.pack(pady=(0, 10)) + + # Success message + msg_label = tk.Label(main_frame, text=message, font=('Segoe UI', 12, 'bold'), + fg=self.colors['text_primary'], bg=self.colors['bg_primary'], + wraplength=340, justify=tk.CENTER) + msg_label.pack(pady=(0, 20)) + + # OK button + ok_btn = self.create_action_button(main_frame, "OK", success_window.destroy, + self.colors['success']) + ok_btn.pack() + + # Auto-close after 3 seconds + success_window.after(3000, success_window.destroy) + + def show_enhanced_error_message(self, message): + """Show an enhanced error message with dark theme""" + error_window = tk.Toplevel(self.window) + error_window.title("Error") + error_window.geometry("400x150") + error_window.configure(bg=self.colors['bg_primary']) + error_window.transient(self.window) + error_window.grab_set() + error_window.resizable(False, False) + + # Center the window using helper method + self.center_window_on_screen(error_window, 400, 150) + + # Main frame + main_frame = tk.Frame(error_window, bg=self.colors['bg_primary'], padx=30, pady=20) + main_frame.pack(fill=tk.BOTH, expand=True) + + # Error icon + icon_label = tk.Label(main_frame, text="โŒ", font=('Segoe UI Emoji', 32), + fg=self.colors['danger'], bg=self.colors['bg_primary']) + icon_label.pack(pady=(0, 10)) + + # Error message + msg_label = tk.Label(main_frame, text=message, font=('Segoe UI', 11), + fg=self.colors['text_primary'], bg=self.colors['bg_primary'], + wraplength=340, justify=tk.CENTER) + msg_label.pack(pady=(0, 20)) + + # OK button + ok_btn = self.create_action_button(main_frame, "OK", error_window.destroy, + self.colors['danger']) + ok_btn.pack() + + def show_success_message(self, message): + """Show a modern success message""" + success_window = tk.Toplevel(self.window) + success_window.title("Success") + success_window.geometry("300x120") + success_window.configure(bg='#2d5a2d') + success_window.transient(self.window) + success_window.grab_set() + + # Center the window using helper method + self.center_window_on_screen(success_window, 300, 120) + + # Success icon and message + icon_label = tk.Label(success_window, text="โœ“", font=('Arial', 24, 'bold'), + fg='white', bg='#2d5a2d') + icon_label.pack(pady=10) + + msg_label = tk.Label(success_window, text=message, font=('Arial', 10), + fg='white', bg='#2d5a2d', wraplength=250) + msg_label.pack(pady=5) + + ok_btn = tk.Button(success_window, text="OK", command=success_window.destroy, + bg='#4d7d4d', fg='white', font=('Arial', 10, 'bold'), + relief=tk.FLAT, padx=20, pady=5) + ok_btn.pack(pady=10) + + def force_playlist_refresh(self): + """Force refresh of playlist from server""" + try: + # Show connection message + self.connection_status.configure(text="Refreshing playlist from server...") + self.window.update() + + # Fetch 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: + # Clean old files + local_playlist_data = load_local_playlist() + clean_unused_files(local_playlist_data.get('playlist', [])) + + # Download new content + download_media_files(server_playlist, server_version) + update_config_playlist_version(server_version) + + # Let the app know to reload + self.app.playlist = load_local_playlist().get('playlist', []) + self.app.current_index = 0 # Reset to beginning + + self.connection_status.configure(text=f"Playlist refreshed! Downloaded {len(server_playlist)} media files.") + + # Force the app to play the current media + if self.app.playlist: + self.app.play_current_media() + else: + self.connection_status.configure(text="Server returned empty playlist. Check server connection settings.") + + except Exception as e: + self.connection_status.configure(text=f"Error refreshing playlist: {str(e)[:50]}...") + Logger.error(f"Failed to refresh playlist: {e}") + + def test_connection(self): + """Test server connection""" + try: + self.connection_status.configure(text="Testing connection...") + self.window.update() + + server_ip = self.server_ip_var.get() + port = self.port_var.get() + screen_name = self.screen_name_var.get() + quickconnect = self.quickconnect_var.get() + + if not all([server_ip, port, screen_name, quickconnect]): + self.connection_status.configure(text="Please fill all connection fields") + return + + url = f"http://{server_ip}:{port}/api/playlists" + params = { + 'hostname': screen_name, + 'quickconnect_code': quickconnect + } + + response = requests.get(url, params=params, timeout=10) + + if response.status_code == 200: + data = response.json() + version = data.get('playlist_version', 'Unknown') + num_items = len(data.get('playlist', [])) + self.connection_status.configure( + text=f"โœ“ Connected! Server playlist version: {version}, {num_items} media items available" + ) + else: + self.connection_status.configure( + text=f"โœ— Connection failed (Status: {response.status_code})" + ) + + except Exception as e: + self.connection_status.configure(text=f"โœ— Connection error: {str(e)[:50]}...") + + def load_logs(self): + """Load recent log entries""" + try: + # Update to use the resources directory + log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'log.txt') + + if os.path.exists(log_file): + with open(log_file, 'r') as f: + lines = f.readlines() + + # Show last 20 lines + recent_lines = lines[-20:] if len(lines) > 20 else lines + + self.log_text.delete(1.0, tk.END) + self.log_text.insert(tk.END, ''.join(recent_lines)) + self.log_text.see(tk.END) + else: + self.log_text.delete(1.0, tk.END) + self.log_text.insert(tk.END, "No log file found") + + except Exception as e: + self.log_text.delete(1.0, tk.END) + self.log_text.insert(tk.END, f"Error loading logs: {e}") + + def load_playlist_view(self): + """Load playlist items into treeview""" + try: + # Clear existing items + for item in self.playlist_view.get_children(): + self.playlist_view.delete(item) + except Exception as e: + Logger.error(f"Failed to load playlist view: {e}") + messagebox.showerror("Error", f"Failed to load playlist: {e}") + + def show_video_placeholder(self, filename, duration): + """Show placeholder for video files""" + self.image_label.config(image='') + self.status_label.config(text="") # Clear any status text for cleaner display + + # Schedule next media + self.auto_advance_timer = self.root.after( + int(duration * 1000), + self.next_media + ) diff --git a/tkinter_app/src/virtual_keyboard.py b/tkinter_app/src/virtual_keyboard.py new file mode 100644 index 0000000..911c26b --- /dev/null +++ b/tkinter_app/src/virtual_keyboard.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python3 +""" +Virtual Keyboard Component for Touch Displays +Provides an on-screen keyboard for touch-friendly input +""" +import tkinter as tk +from tkinter import ttk + +class VirtualKeyboard: + def __init__(self, parent, target_entry=None, dark_theme=True): + self.parent = parent + self.target_entry = target_entry + self.dark_theme = dark_theme + self.keyboard_window = None + self.caps_lock = False + self.shift_pressed = False + + # Define color schemes + if dark_theme: + self.colors = { + 'bg_primary': '#1e2124', + 'bg_secondary': '#2f3136', + 'bg_tertiary': '#36393f', + 'accent': '#7289da', + 'accent_hover': '#677bc4', + 'text_primary': '#ffffff', + 'text_secondary': '#b9bbbe', + 'key_normal': '#4f545c', + 'key_hover': '#5865f2', + 'key_special': '#ed4245', + 'key_function': '#57f287' + } + else: + self.colors = { + 'bg_primary': '#ffffff', + 'bg_secondary': '#f8f9fa', + 'bg_tertiary': '#e9ecef', + 'accent': '#0d6efd', + 'accent_hover': '#0b5ed7', + 'text_primary': '#000000', + 'text_secondary': '#6c757d', + 'key_normal': '#dee2e6', + 'key_hover': '#0d6efd', + 'key_special': '#dc3545', + 'key_function': '#198754' + } + + def show_keyboard(self, entry_widget=None): + """Show the virtual keyboard""" + if entry_widget: + self.target_entry = entry_widget + + if self.keyboard_window and self.keyboard_window.winfo_exists(): + self.keyboard_window.lift() + return + + self.create_keyboard() + + def hide_keyboard(self): + """Hide the virtual keyboard""" + if self.keyboard_window and self.keyboard_window.winfo_exists(): + self.keyboard_window.destroy() + self.keyboard_window = None + + def create_keyboard(self): + """Create the virtual keyboard window""" + self.keyboard_window = tk.Toplevel(self.parent) + self.keyboard_window.title("Virtual Keyboard") + self.keyboard_window.configure(bg=self.colors['bg_primary']) + self.keyboard_window.resizable(False, False) + + # Make keyboard stay on top + self.keyboard_window.attributes('-topmost', True) + + # Position keyboard at bottom of screen + self.position_keyboard() + + # Create keyboard layout + self.create_keyboard_layout() + + # Bind events + self.keyboard_window.protocol("WM_DELETE_WINDOW", self.hide_keyboard) + + def position_keyboard(self): + """Position keyboard at bottom center of screen""" + self.keyboard_window.update_idletasks() + + # Get screen dimensions + screen_width = self.keyboard_window.winfo_screenwidth() + screen_height = self.keyboard_window.winfo_screenheight() + + # Keyboard dimensions + kb_width = 800 + kb_height = 300 + + # Position at bottom center + x = (screen_width - kb_width) // 2 + y = screen_height - kb_height - 50 # 50px from bottom + + self.keyboard_window.geometry(f"{kb_width}x{kb_height}+{x}+{y}") + + def create_keyboard_layout(self): + """Create the keyboard layout""" + main_frame = tk.Frame(self.keyboard_window, bg=self.colors['bg_primary'], padx=10, pady=10) + main_frame.pack(fill=tk.BOTH, expand=True) + + # Title bar + title_frame = tk.Frame(main_frame, bg=self.colors['bg_secondary'], height=40) + title_frame.pack(fill=tk.X, pady=(0, 10)) + title_frame.pack_propagate(False) + + title_label = tk.Label(title_frame, text="โŒจ๏ธ Virtual Keyboard", + font=('Segoe UI', 12, 'bold'), + bg=self.colors['bg_secondary'], fg=self.colors['text_primary']) + title_label.pack(side=tk.LEFT, padx=10, pady=10) + + # Close button + close_btn = tk.Button(title_frame, text="โœ•", command=self.hide_keyboard, + bg=self.colors['key_special'], fg=self.colors['text_primary'], + font=('Segoe UI', 12, 'bold'), relief=tk.FLAT, width=3) + close_btn.pack(side=tk.RIGHT, padx=10, pady=5) + + # Keyboard rows + self.create_keyboard_rows(main_frame) + + def create_keyboard_rows(self, parent): + """Create keyboard rows""" + # Define keyboard layout + rows = [ + ['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'Backspace'], + ['Tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'], + ['Caps', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", 'Enter'], + ['Shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'Shift'], + ['Ctrl', 'Alt', 'Space', 'Alt', 'Ctrl'] + ] + + # Special keys with different sizes + special_keys = { + 'Backspace': 2, + 'Tab': 1.5, + 'Enter': 2, + 'Caps': 1.8, + 'Shift': 2.3, + 'Ctrl': 1.2, + 'Alt': 1.2, + 'Space': 6 + } + + for row_index, row in enumerate(rows): + row_frame = tk.Frame(parent, bg=self.colors['bg_primary']) + row_frame.pack(fill=tk.X, pady=2) + + for key in row: + width = special_keys.get(key, 1) + self.create_key_button(row_frame, key, width) + + def create_key_button(self, parent, key, width=1): + """Create a keyboard key button""" + # Determine key type and color + if key in ['Backspace', 'Tab', 'Enter', 'Caps', 'Shift', 'Ctrl', 'Alt']: + bg_color = self.colors['key_function'] + elif key == 'Space': + bg_color = self.colors['key_normal'] + else: + bg_color = self.colors['key_normal'] + + # Calculate button width + base_width = 4 + button_width = int(base_width * width) + + # Display text for special keys + display_text = { + 'Backspace': 'โŒซ', + 'Tab': 'โ‡ฅ', + 'Enter': 'โŽ', + 'Caps': 'โ‡ช', + 'Shift': 'โ‡ง', + 'Ctrl': 'Ctrl', + 'Alt': 'Alt', + 'Space': '___' + }.get(key, key.upper() if self.caps_lock or self.shift_pressed else key) + + button = tk.Button(parent, text=display_text, + command=lambda k=key: self.key_pressed(k), + bg=bg_color, fg=self.colors['text_primary'], + font=('Segoe UI', 10, 'bold'), + relief=tk.FLAT, bd=1, + width=button_width, height=2) + + # Add hover effects + def on_enter(e, btn=button): + btn.configure(bg=self.colors['key_hover']) + + def on_leave(e, btn=button): + btn.configure(bg=bg_color) + + button.bind("", on_enter) + button.bind("", on_leave) + + button.pack(side=tk.LEFT, padx=1, pady=1) + + def key_pressed(self, key): + """Handle key press""" + if not self.target_entry: + return + + if key == 'Backspace': + current_pos = self.target_entry.index(tk.INSERT) + if current_pos > 0: + self.target_entry.delete(current_pos - 1) + + elif key == 'Tab': + self.target_entry.insert(tk.INSERT, '\t') + + elif key == 'Enter': + # Try to trigger any bound return event + self.target_entry.event_generate('') + + elif key == 'Caps': + self.caps_lock = not self.caps_lock + self.update_key_display() + + elif key == 'Shift': + self.shift_pressed = not self.shift_pressed + self.update_key_display() + + elif key == 'Space': + self.target_entry.insert(tk.INSERT, ' ') + + elif key in ['Ctrl', 'Alt']: + # These could be used for key combinations in the future + pass + + else: + # Regular character + char = key.upper() if self.caps_lock or self.shift_pressed else key + + # Handle shifted characters + if self.shift_pressed and not self.caps_lock: + shift_map = { + '1': '!', '2': '@', '3': '#', '4': '$', '5': '%', + '6': '^', '7': '&', '8': '*', '9': '(', '0': ')', + '-': '_', '=': '+', '[': '{', ']': '}', '\\': '|', + ';': ':', "'": '"', ',': '<', '.': '>', '/': '?', + '`': '~' + } + char = shift_map.get(key, char) + + self.target_entry.insert(tk.INSERT, char) + + # Reset shift after character input + if self.shift_pressed: + self.shift_pressed = False + self.update_key_display() + + def update_key_display(self): + """Update key display based on caps lock and shift state""" + # This would update the display of keys, but for simplicity + # we'll just recreate the keyboard when needed + pass + + +class TouchOptimizedEntry(tk.Entry): + """Entry widget optimized for touch displays with virtual keyboard""" + + def __init__(self, parent, virtual_keyboard=None, **kwargs): + # Make entry larger for touch + kwargs.setdefault('font', ('Segoe UI', 12)) + kwargs.setdefault('relief', tk.FLAT) + kwargs.setdefault('bd', 8) + + super().__init__(parent, **kwargs) + + self.virtual_keyboard = virtual_keyboard + + # Bind focus events to show/hide keyboard + self.bind('', self.on_focus_in) + self.bind('', self.on_click) + + def on_focus_in(self, event): + """Show virtual keyboard when entry gets focus""" + if self.virtual_keyboard: + self.virtual_keyboard.show_keyboard(self) + + def on_click(self, event): + """Show virtual keyboard when entry is clicked""" + if self.virtual_keyboard: + self.virtual_keyboard.show_keyboard(self) + + +class TouchOptimizedButton(tk.Button): + """Button widget optimized for touch displays""" + + def __init__(self, parent, **kwargs): + # Make buttons larger for touch + kwargs.setdefault('font', ('Segoe UI', 11, 'bold')) + kwargs.setdefault('relief', tk.FLAT) + kwargs.setdefault('padx', 20) + kwargs.setdefault('pady', 12) + kwargs.setdefault('cursor', 'hand2') + + super().__init__(parent, **kwargs) + + # Add touch feedback + self.bind('', self.on_touch_down) + self.bind('', self.on_touch_up) + + def on_touch_down(self, event): + """Visual feedback when button is touched""" + self.configure(relief=tk.SUNKEN) + + def on_touch_up(self, event): + """Reset visual feedback when touch is released""" + self.configure(relief=tk.FLAT) + + +# Test the virtual keyboard +if __name__ == "__main__": + def test_virtual_keyboard(): + root = tk.Tk() + root.title("Virtual Keyboard Test") + root.geometry("600x400") + root.configure(bg='#2f3136') + + # Create virtual keyboard instance + vk = VirtualKeyboard(root, dark_theme=True) + + # Test frame + test_frame = tk.Frame(root, bg='#2f3136', padx=20, pady=20) + test_frame.pack(fill=tk.BOTH, expand=True) + + # Title + tk.Label(test_frame, text="๐ŸŽฎ Touch Display Test", + font=('Segoe UI', 16, 'bold'), + bg='#2f3136', fg='white').pack(pady=20) + + # Test entries + tk.Label(test_frame, text="Click entries to show virtual keyboard:", + bg='#2f3136', fg='white', font=('Segoe UI', 12)).pack(pady=10) + + entry1 = TouchOptimizedEntry(test_frame, vk, width=30, bg='#36393f', + fg='white', insertbackground='white') + entry1.pack(pady=10) + + entry2 = TouchOptimizedEntry(test_frame, vk, width=30, bg='#36393f', + fg='white', insertbackground='white') + entry2.pack(pady=10) + + # Test buttons + TouchOptimizedButton(test_frame, text="Show Keyboard", + command=lambda: vk.show_keyboard(entry1), + bg='#7289da', fg='white').pack(pady=10) + + TouchOptimizedButton(test_frame, text="Hide Keyboard", + command=vk.hide_keyboard, + bg='#ed4245', fg='white').pack(pady=5) + + root.mainloop() + + test_virtual_keyboard() diff --git a/tkinter_requirements.txt b/tkinter_requirements.txt new file mode 100644 index 0000000..1e8804c --- /dev/null +++ b/tkinter_requirements.txt @@ -0,0 +1,15 @@ +# Tkinter Media Player Requirements - Raspberry Pi Compatible +# Core GUI and media handling (lighter alternatives) +opencv-python-headless>=4.8.0 +Pillow>=9.0.0 +pygame>=2.1.0 + +# Networking and data handling +requests>=2.28.0 + +# System utilities for Python 3.9+ +# pathlib2 not needed on modern Python versions + +# Optional: Basic image processing without heavy dependencies +# numpy - will be installed with opencv-python-headless +bcrypt \ No newline at end of file diff --git a/tkinter_requirements_minimal.txt b/tkinter_requirements_minimal.txt new file mode 100644 index 0000000..875c714 --- /dev/null +++ b/tkinter_requirements_minimal.txt @@ -0,0 +1,7 @@ +# Minimal Tkinter Media Player Requirements - Raspberry Pi Compatible +# Core dependencies only +requests>=2.28.0 + +# Optional but recommended if available via apt +# python3-pil (install via apt instead of pip) +# python3-tk (should already be installed with Python) \ No newline at end of file