Compare commits

..

4 Commits

Author SHA1 Message Date
a4cc026e38 changes 2025-08-06 02:27:50 +03:00
65843c255a feat: Full-screen scaling system with three display modes
- Implemented comprehensive image/video scaling system
- Added fit, fill, and stretch scaling modes
- Keyboard shortcuts 1,2,3 for real-time mode switching
- Enhanced PIL/Pillow integration for image processing
- OpenCV video playback with full-screen scaling
- Settings integration for scaling preferences
- Fixed touch feedback for control buttons
- Thread-safe video frame processing
- Perfect aspect ratio calculations for any resolution
2025-08-06 02:26:12 +03:00
7e69b12f71 saved 2025-08-05 17:01:19 +03:00
1197077954 Add complete offline installation package
- Added offline Python packages (requests, pillow, pygame, etc.)
- Created comprehensive installation scripts for offline deployment
- Added system package dependency lists
- Included shared modules for the tkinter application
- Added complete documentation for offline installation
- Removed old Kivy-related files to clean up repository
- Package supports complete offline installation on Raspberry Pi systems
2025-08-05 16:59:27 +03:00
26 changed files with 2999 additions and 180 deletions

111
src/README.md Normal file
View File

@@ -0,0 +1,111 @@
# Signage Player - Complete Offline Installation Package
This directory contains everything needed to install the Signage Player application completely offline on a Raspberry Pi or similar Debian-based system.
## Directory Structure
```
src/
├── offline_packages/ # Pre-downloaded Python packages (.whl files)
│ ├── requests-2.32.4-py3-none-any.whl
│ ├── pillow-11.1.0-cp311-cp311-linux_armv7l.whl
│ ├── pygame-2.6.1-cp311-cp311-linux_armv7l.whl
│ └── ... (dependencies)
├── shared_modules/ # Shared Python modules
│ ├── logging_config.py
│ └── python_functions.py
├── system_packages/ # System dependency information
│ └── apt_packages.txt # List of required APT packages
└── scripts/ # Installation and utility scripts
├── install_offline.sh # Main offline installation script
└── check_dependencies.sh # Dependency verification script
```
## Quick Installation
1. **Run the offline installer:**
```bash
cd /path/to/signage-player
chmod +x src/scripts/install_offline.sh
./src/scripts/install_offline.sh
```
2. **Verify installation:**
```bash
chmod +x src/scripts/check_dependencies.sh
./src/scripts/check_dependencies.sh
```
3. **Run the application:**
```bash
./run_tkinter_debug.sh
```
## What Gets Installed
### System Packages (via APT)
- Python 3 development tools
- OpenCV libraries and Python bindings
- SDL2 libraries for pygame
- Image processing libraries (JPEG, PNG, TIFF, WebP)
- Audio libraries
- Build tools
### Python Packages (from offline wheels)
- **requests** - HTTP library for server communication
- **pillow** - Image processing library
- **pygame** - Audio and input handling
- **certifi, charset_normalizer, idna, urllib3** - Dependencies
### Application Components
- Shared logging and playlist management modules
- Modern tkinter-based media player
- Configuration management
- Resource directories
## Manual Installation Steps
If you prefer to install manually:
1. **Install system packages:**
```bash
sudo apt update
cat src/system_packages/apt_packages.txt | grep -v '^#' | xargs sudo apt install -y
```
2. **Create virtual environment:**
```bash
python3 -m venv venv
source venv/bin/activate
```
3. **Install Python packages:**
```bash
pip install --no-index --find-links src/offline_packages requests pillow pygame
```
4. **Copy shared modules:**
```bash
cp src/shared_modules/*.py tkinter_app/src/
```
## Troubleshooting
- **Permission errors:** Make sure scripts are executable with `chmod +x`
- **Missing packages:** Run `src/scripts/check_dependencies.sh` to verify installation
- **Virtual environment issues:** Delete `venv` folder and re-run installer
- **OpenCV errors:** Ensure `python3-opencv` system package is installed
## Requirements
- Debian/Ubuntu-based system (Raspberry Pi OS recommended)
- Internet connection for system package installation (APT only)
- Sudo privileges for system package installation
- At least 200MB free space
## Notes
- This package includes all Python dependencies as pre-compiled wheels
- No internet connection needed for Python packages during installation
- Compatible with ARM-based systems (Raspberry Pi)
- Includes fallback mechanisms for offline operation

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,77 @@
#!/bin/bash
# Dependency Verification Script
# Checks if all required dependencies are properly installed
echo "=== Signage Player Dependency Check ==="
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
ERRORS=0
# Function to check system package
check_system_package() {
if dpkg -l | grep -q "^ii.*$1"; then
echo -e "${GREEN}${NC} $1 is installed"
else
echo -e "${RED}${NC} $1 is NOT installed"
((ERRORS++))
fi
}
# Function to check Python package
check_python_package() {
if python3 -c "import $1" 2>/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

109
src/scripts/install_offline.sh Executable file
View File

@@ -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 ""

View File

@@ -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)

View File

@@ -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}")

View File

@@ -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

143
test_centering.py Normal file
View File

@@ -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.")

54
test_image_display.py Normal file
View File

@@ -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()

38
test_imports.py Normal file
View File

@@ -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")

97
test_scaling.py Normal file
View File

@@ -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()

230
test_touch.py Normal file
View File

@@ -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("<Button-1>", on_press)
button.bind("<ButtonRelease-1>", on_release)
button.bind("<Enter>", on_enter)
button.bind("<Leave>", 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!")

View File

@@ -1,9 +1,9 @@
{ {
"screen_orientation": "Landscape", "screen_orientation": "Landscape",
"screen_name": "tv-holba1", "screen_name": "tv-terasa",
"quickconnect_key": "8887779", "quickconnect_key": "8887779",
"server_ip": "192.168.1.245", "server_ip": "digi-signage.moto-adv.com",
"port": "5000", "port": "8880",
"screen_w": "1920", "screen_w": "1920",
"screen_h": "1080", "screen_h": "1080",
"playlist_version": 5 "playlist_version": 5

View File

@@ -831,3 +831,356 @@
[INFO] [SignageApp] python_functions: Starting load_config function. [INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully. [INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Application exit requested [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('<urllib3.connection.HTTPConnection object at 0xd5623410>: 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('<urllib3.connection.HTTPConnection object at 0xd59e9190>: 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('<urllib3.connection.HTTPConnection object at 0xd59fe590>: 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('<urllib3.connection.HTTPConnection object at 0xd59e94d0>: 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('<urllib3.connection.HTTPConnection object at 0xd5cf6670>: 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('<urllib3.connection.HTTPConnection object at 0xd5d07670>: 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('<urllib3.connection.HTTPConnection object at 0xd5d00570>: 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('<urllib3.connection.HTTPConnection object at 0xd5c15e30>: 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('<urllib3.connection.HTTPConnection object at 0xd5b844f0>: 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('<urllib3.connection.HTTPConnection object at 0xd5a552b0>: 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('<urllib3.connection.HTTPConnection object at 0xd5a29970>: 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('<urllib3.connection.HTTPConnection object at 0xd586dfb0>: 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

File diff suppressed because it is too large Load Diff

View File

@@ -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("<Enter>", on_enter)
button.bind("<Leave>", 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('<Return>')
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('<FocusIn>', self.on_focus_in)
self.bind('<Button-1>', 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('<Button-1>', self.on_touch_down)
self.bind('<ButtonRelease-1>', 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()