diff --git a/src/README.md b/src/README.md deleted file mode 100644 index a329c3d..0000000 --- a/src/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# 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 \ No newline at end of file diff --git a/src/offline_packages/certifi-2025.8.3-py3-none-any.whl b/src/offline_packages/certifi-2025.8.3-py3-none-any.whl deleted file mode 100644 index b4158ec..0000000 Binary files a/src/offline_packages/certifi-2025.8.3-py3-none-any.whl and /dev/null differ diff --git a/src/offline_packages/charset_normalizer-3.4.2-py3-none-any.whl b/src/offline_packages/charset_normalizer-3.4.2-py3-none-any.whl deleted file mode 100644 index 61a1d21..0000000 Binary files a/src/offline_packages/charset_normalizer-3.4.2-py3-none-any.whl and /dev/null differ diff --git a/src/offline_packages/idna-3.10-py3-none-any.whl b/src/offline_packages/idna-3.10-py3-none-any.whl deleted file mode 100644 index 52759bd..0000000 Binary files a/src/offline_packages/idna-3.10-py3-none-any.whl and /dev/null differ diff --git a/src/offline_packages/pillow-11.1.0-cp311-cp311-linux_armv7l.whl b/src/offline_packages/pillow-11.1.0-cp311-cp311-linux_armv7l.whl deleted file mode 100644 index 29187f6..0000000 Binary files a/src/offline_packages/pillow-11.1.0-cp311-cp311-linux_armv7l.whl and /dev/null differ diff --git a/src/offline_packages/pygame-2.6.1-cp311-cp311-linux_armv7l.whl b/src/offline_packages/pygame-2.6.1-cp311-cp311-linux_armv7l.whl deleted file mode 100644 index 2e38b05..0000000 Binary files a/src/offline_packages/pygame-2.6.1-cp311-cp311-linux_armv7l.whl and /dev/null differ diff --git a/src/offline_packages/requests-2.32.4-py3-none-any.whl b/src/offline_packages/requests-2.32.4-py3-none-any.whl deleted file mode 100644 index d52fad0..0000000 Binary files a/src/offline_packages/requests-2.32.4-py3-none-any.whl and /dev/null differ diff --git a/src/offline_packages/urllib3-2.5.0-py3-none-any.whl b/src/offline_packages/urllib3-2.5.0-py3-none-any.whl deleted file mode 100644 index 81b580f..0000000 Binary files a/src/offline_packages/urllib3-2.5.0-py3-none-any.whl and /dev/null differ diff --git a/src/scripts/check_dependencies.sh b/src/scripts/check_dependencies.sh deleted file mode 100755 index cb25c54..0000000 --- a/src/scripts/check_dependencies.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/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 \ No newline at end of file diff --git a/src/scripts/install_offline.sh b/src/scripts/install_offline.sh deleted file mode 100755 index 19d8b40..0000000 --- a/src/scripts/install_offline.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/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 deleted file mode 100644 index 8710312..0000000 --- a/src/shared_modules/logging_config.py +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 279b46e..0000000 --- a/src/shared_modules/python_functions.py +++ /dev/null @@ -1,196 +0,0 @@ -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 deleted file mode 100644 index 162f745..0000000 --- a/src/system_packages/apt_packages.txt +++ /dev/null @@ -1,43 +0,0 @@ -#!/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