Compare commits

...

15 Commits

Author SHA1 Message Date
21722c5c85 Add local Pillow build support and update 32-bit download script for ARMhf compatibility 2025-09-11 13:36:57 +03:00
ad4d71e0b6 Remove requirements.txt - replaced by install scripts with proper offline library management 2025-09-11 10:46:16 +03:00
d7f7df49e7 Fix permissions on download_32bit_libs.sh 2025-09-11 10:15:38 +03:00
9c42b38c4b Fix 32-bit bcrypt ELFCLASS64 error and enhance installation
- Enhanced install_32bit.sh with clean virtual environment creation
- Added --force-reinstall and --clear flags for proper isolation
- Improved download_32bit_libs.sh with explicit armv7l targeting
- Added troubleshoot_32bit.sh for diagnosing 32-bit issues
- Better architecture verification and error messages
- Comprehensive package testing after installation

Fixes ELFCLASS64 error on 32-bit systems by ensuring proper
architecture isolation and clean package installation.
2025-09-11 10:15:25 +03:00
0aa1bb7069 Clean up project: Remove test files and add dedicated 32-bit installer
- Remove temporary test files (test_*.py)
- Remove unused installation scripts
- Add install_32bit.sh for dedicated 32-bit Raspberry Pi OS installation
- Clean up repository structure for production deployment
2025-09-11 09:00:15 +03:00
35db99eb3d installer offline 2025-09-10 16:19:43 +03:00
b6e6190d6c create installer 2025-09-10 15:43:14 +03:00
26a9db889f deleted unnecesary files 2025-09-10 14:30:51 +03:00
26fc946a65 updated to show when is not online 2025-09-09 17:11:25 +03:00
a91b07ede4 updated 2025-09-09 16:16:23 +03:00
185f3099ad updated version 2025-09-08 15:46:59 +03:00
5063b47a56 updated feedback 2025-09-08 15:19:47 +03:00
e2eecb9cf9 updated to send feedback 2025-09-08 13:19:50 +03:00
bd4f101fcc updating player startup 2025-09-05 12:33:47 +03:00
cb861d0ffa Complete auto-startup installation system for Raspberry Pi Zero
- Enhanced install_minimal_xorg.sh with full automatic startup configuration
- Added systemd service for robust signage player auto-start
- Configured autologin and X server auto-launch on boot
- Added multiple startup methods (systemd, bashrc, rc.local) for reliability
- Disabled screen blanking and power management for kiosk operation
- Updated requirements.txt with proper version specifications
- Fixed run_tkinter_debug.sh for correct directory structure
- Added comprehensive logging and error handling
- Optimized for headless Raspberry Pi Zero deployment

Features:
 Plug-and-play operation - boots directly to signage player
 Automatic restart on crashes
 Multiple fallback startup methods
 Complete dependency installation
 Service management commands
 Hardware optimizations for digital signage
2025-09-04 16:44:00 +03:00
66 changed files with 22241 additions and 3523 deletions

View File

@@ -0,0 +1,59 @@
# Multi-Architecture Offline Installation Guide
## Overview
The enhanced `install_offline.sh` script automatically detects your system architecture and uses the appropriate libraries.
## Architecture Support
### 64-bit ARM (aarch64) - Raspberry Pi 4/5 with 64-bit OS
- **Folder used**: `req_libraries/`
- **Files**: 18 wheel files
- **Key packages**:
- bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl
- pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.whl
- psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.whl
### 32-bit ARM (armv7l) - Raspberry Pi with 32-bit OS
- **Folder used**: `req_libraries_32bit/`
- **Files**: 18 wheel files
- **Key packages**:
- bcrypt-4.3.0-cp311-cp311-linux_armv7l.whl
- pillow-11.3.0-cp311-cp311-linux_armv7l.whl
- psutil-6.0.0-cp311-abi3-linux_armv7l.whl
## Usage
### Simple Installation (Auto-Detect)
```bash
./install_offline.sh
```
The script will:
1. Detect your architecture automatically
2. Choose the correct library folder
3. Install the appropriate packages
4. Create a virtual environment
5. Verify installation
### Manual Architecture Check
```bash
uname -m
```
- `aarch64` = 64-bit (uses req_libraries/)
- `armv7l` = 32-bit (uses req_libraries_32bit/)
## Folder Structure
```
tkinter_player/
├── req_libraries/ # 64-bit ARM wheels
├── req_libraries_32bit/ # 32-bit ARM wheels
├── install_offline.sh # Auto-detecting installer
└── requirements.txt # Package list
```
## Benefits
- ✅ Single installer for both architectures
- ✅ Automatic architecture detection
- ✅ No manual intervention needed
- ✅ Proper error messages for missing libraries
- ✅ Complete offline installation support

View File

@@ -1,84 +0,0 @@
Here is the complete content for the `How to use.txt` file, starting from **point 3** and including all the missing information:
```plaintext
### How to Install, Start, and Use the Signage Player Application
This guide provides step-by-step instructions for installing, starting, and using the signage player application.
---
## 3. Using the Application
### MediaPlayer Screen
- **Play/Pause Button**:
- Toggles between playing and pausing the media.
- Automatically resumes playback after 30 seconds if not toggled again.
- **Next/Previous Buttons**:
- Navigate to the next or previous media in the playlist.
- **Exit App Button**:
- Opens a password-protected popup. Enter the `quickconnect_key` from the configuration file to exit the app.
### Settings Screen
- **Configuration Fields**:
- Update fields like `screen_orientation`, `screen_name`, `quickconnect_key`, `server_ip`, and `port`.
- **Save Button**:
- Saves the updated configuration and returns to the MediaPlayer screen.
- **Exit App Button**:
- Opens a password-protected popup. Enter the `quickconnect_key` to exit the app.
---
## 4. Configuration
### Configuration File
The configuration file is located at:
```bash
app_config.txt
```
### Example Configuration
```json
{
"screen_orientation": "Landscape",
"screen_name": "MyScreen",
"quickconnect_key": "12345",
"server_ip": "192.168.1.1",
"port": "8080"
}
```
### Updating Configuration
1. Navigate to the **Settings Screen** in the app.
2. Update the fields and click **Save**.
3. The configuration will be saved to `app_config.txt`.
---
## 5. Troubleshooting
### Permission Denied for `run_app.sh`
- Ensure the script is executable:
```bash
chmod +x /home/pi/Desktop/signage-player/run_app.sh
```
### Application Does Not Start on Boot
- Verify the `~/.bashrc` or `crontab` entry is correct.
- Check the script's permissions and paths.
### Media Files Not Playing
- Ensure media files are downloaded to the correct directory:
```bash
/home/pi/Desktop/signage-player/src/static/resurse
```
- Check the playlist configuration and server connection.
### Password for Exit App
- The password is the `quickconnect_key` from the configuration file (`app_config.txt`).
---
Let me know if you need further clarification or additional details!
```

44
OFFLINE_INSTALL_README.md Normal file
View File

@@ -0,0 +1,44 @@
# Offline Installation Guide
## Architecture Compatibility
### Current Libraries (req_libraries/)
- Compatible with: 64-bit Raspberry Pi OS (aarch64)
- NOT compatible with: 32-bit Raspberry Pi OS (armv7l)
### For 32-bit Raspberry Pi OS
The current wheels contain aarch64 packages that won't work on 32-bit systems.
## Installation Options
### 64-bit Raspberry Pi OS:
```bash
./install_offline.sh
```
### 32-bit Raspberry Pi OS:
1. Run on internet-connected system: `./download_32bit_libs.sh`
2. Copy req_libraries_32bit/ to offline system
3. Rename: `mv req_libraries_32bit req_libraries`
4. Run: `./install_offline.sh`
## System Requirements
### 32-bit systems need build tools:
```bash
sudo apt update
sudo apt install build-essential python3-dev python3-pip
```
### All systems need VLC:
```bash
sudo apt install vlc
```
## Quick Check
```bash
uname -m
```
- aarch64 = 64-bit (current wheels work)
- armv7l = 32-bit (need different wheels)

150
README.md
View File

@@ -1,150 +0,0 @@
# 🎥 Kivy Media Player
A media player application built using **Kivy** that allows users to play video files, display images, and manage settings for quick connect codes and server configurations. The application checks for updates to the playlist every five minutes while running in full-screen mode.
---
## 📂 Project Structure
```
signage-player
├── src
│ ├── media_player.py # Handles media playback
│ ├── python_functions.py # Utility functions for downloading files and updating playlists
│ ├── kv
│ │ └── media_player.kv # Layout for the media player screen
│ ├── Resurse
│ │ ├── app_config.txt # Configuration file
│ │ ├── log.txt # Log file for media events
│ │ ├── play.png # Play button icon
│ │ ├── pause.png # Pause button icon
│ │ └── other icons... # Additional icons for the UI
│ └── static
│ └── resurse # Directory for media files (images/videos)
├── requirements.txt # Project dependencies
├── run_app.sh # Script to run the application
├── install.sh # Installation script
├── How to use.txt # Detailed usage instructions
└── README.md # Documentation for the project
```
---
## 🚀 Setup Instructions
### Prerequisites
Before installing the application, ensure the following are installed on your Raspberry Pi:
- **Python 3.7 or higher**
- **pip3** (Python package manager)
- **ffmpeg** (for video conversion)
- **Internet connection** for downloading dependencies
### Installation Steps
1. **Clone the Repository**
```bash
git clone https://gitea.moto-adv.com/ske087/signage-player.git
cd signage-player
```
2. **Run the Installation Script**
```bash
./install.sh
```
The installation script will:
- Update the system.
- Install required Python and system dependencies.
- Clone the repository to `/home/pi/Desktop/ds-player`.
- Add the run_app.sh script to `~/.bashrc` for autostart.
- Make the run_app.sh script executable.
3. **Reboot the Device**
After installation, reboot the Raspberry Pi to start the application automatically:
```bash
sudo reboot
```
---
## 🎮 How to Use
### MediaPlayer Screen
- **▶️ Play/Pause Button**:
- Toggles between playing and pausing the media.
- Automatically resumes playback after 30 seconds if not toggled again.
- **⏩ Next/⏪ Previous Buttons**:
- Navigate to the next or previous media in the playlist.
- **❌ Exit App Button**:
- Opens a password-protected popup. Enter the `quickconnect_key` from the configuration file to exit the app.
### Settings Screen
- **Configuration Fields**:
- Update fields like `screen_orientation`, `screen_name`, `quickconnect_key`, `server_ip`, and `port`.
- **💾 Save Button**:
- Saves the updated configuration and returns to the MediaPlayer screen.
- **❌ Exit App Button**:
- Opens a password-protected popup. Enter the `quickconnect_key` to exit the app.
---
## ⚙️ Configuration
### Configuration File
The configuration file is located at:
```bash
/home/pi/Desktop/signage-player/src/Resurse/app_config.txt
```
### Example Configuration
```json
{
"screen_orientation": "Landscape",
"screen_name": "tv-panou1",
"quickconnect_key": "8887779",
"server_ip": "172.20.10.9",
"port": "80"
}
```
### Updating Configuration
1. Navigate to the **Settings Screen** in the app.
2. Update the fields and click **Save**.
3. The configuration will be saved to app_config.txt.
---
## 🛠️ Troubleshooting
### Permission Denied for run_app.sh
- Ensure the script is executable:
```bash
chmod +x /home/pi/Desktop/signage-player/run_app.sh
```
### Application Does Not Start on Boot
- Verify the `~/.bashrc` entry is correct.
- Check the script's permissions and paths.
### Media Files Not Playing
- Ensure media files are downloaded to the correct directory:
```bash
/home/pi/Desktop/signage-player/src/static/resurse
```
- Check the playlist configuration and server connection.
### Password for Exit App
- The password is the `quickconnect_key` from the configuration file (`app_config.txt`).
---
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a pull request or open an issue for any suggestions or improvements.
---
## 📜 License
This project is licensed under the **MIT License**. See the `LICENSE` file for more details.
````

29
build_pillow_local.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# Build Pillow from source and place wheel in req_libraries_32bit
set -e
echo "======================================="
echo " BUILD PILLOW FROM SOURCE (32-bit) "
echo "======================================="
# Ensure virtual environment is activated
echo "Activating .venv..."
source .venv/bin/activate
# Find Pillow source tar.gz
PILLOW_SRC=$(ls req_libraries_32bit/Pillow-*.tar.gz 2>/dev/null | head -1)
if [ -z "$PILLOW_SRC" ]; then
echo "❌ Pillow source distribution not found in req_libraries_32bit."
exit 1
fi
echo "Building Pillow from source: $PILLOW_SRC"
pip wheel "$PILLOW_SRC" -w req_libraries_32bit
if ls req_libraries_32bit/pillow*.whl >/dev/null 2>&1; then
echo "✅ Pillow wheel built successfully!"
echo "You can now install it offline with ./install_32bit.sh."
else
echo "❌ Pillow wheel build failed. Check build dependencies."
fi

89
download_32bit_libs.sh Executable file
View File

@@ -0,0 +1,89 @@
#!/bin/bash
# Download 32-bit ARM compatible libraries with forced architecture
set -e
echo "======================================="
echo " 32-BIT ARM LIBRARY DOWNLOADER"
echo "======================================="
echo ""
# Remove existing 32-bit folder to ensure clean download
if [ -d "req_libraries_32bit" ]; then
echo "🗑️ Removing existing req_libraries_32bit for clean download..."
rm -rf req_libraries_32bit
fi
# Create fresh directory
mkdir -p req_libraries_32bit
echo "📦 Downloading 32-bit ARM compatible packages..."
echo " Target architecture: linux_armv7l"
echo ""
# Download with explicit 32-bit ARM platform targeting
echo "🔄 Downloading with forced 32-bit architecture (armv7l and armhf)..."
# Download for armv7l (wheels only)
pip download -r requirements.txt -d req_libraries_32bit/ \
--platform linux_armv7l \
--only-binary=:all: \
--python-version 311 \
--abi cp311 || true
# Download for armhf (wheels only)
pip download -r requirements.txt -d req_libraries_32bit/ \
--platform linux_armhf \
--only-binary=:all: \
--python-version 311 \
--abi cp311 || true
# Download Pillow as source if wheel is not available
echo "🔄 Downloading Pillow source distribution for local build..."
pip download Pillow -d req_libraries_32bit/ --no-binary=:all: || true
echo ""
echo "📊 Download results:"
WHEEL_COUNT=$(ls req_libraries_32bit/*.whl 2>/dev/null | wc -l || echo "0")
echo " Wheel files: $WHEEL_COUNT"
if [ "$WHEEL_COUNT" -gt 0 ]; then
echo ""
echo "🔍 Key 32-bit packages:"
if ls req_libraries_32bit/bcrypt*.whl >/dev/null 2>&1; then
BCRYPT_FILE=$(basename $(ls req_libraries_32bit/bcrypt*.whl | head -1))
echo " 🔐 BCRYPT: $BCRYPT_FILE"
if [[ "$BCRYPT_FILE" == *"armv7l"* ]]; then
echo " ✅ Correct 32-bit architecture"
else
echo " ⚠️ Architecture unclear"
fi
fi
if ls req_libraries_32bit/pillow*.whl >/dev/null 2>&1; then
PILLOW_FILE=$(basename $(ls req_libraries_32bit/pillow*.whl | head -1))
echo " 🖼️ PILLOW: $PILLOW_FILE"
if [[ "$PILLOW_FILE" == *"armv7l"* ]]; then
echo " ✅ Correct 32-bit architecture"
fi
elif ls req_libraries_32bit/Pillow-*.tar.gz >/dev/null 2>&1; then
PILLOW_SRC=$(basename $(ls req_libraries_32bit/Pillow-*.tar.gz | head -1))
echo " 🖼️ PILLOW source: $PILLOW_SRC"
echo " ⚠️ No wheel available for 32-bit ARM. Will need to build locally."
fi
echo ""
echo "✅ 32-bit libraries download completed!"
echo ""
echo "📋 Next steps:"
echo " 1. Copy this folder to your 32-bit Raspberry Pi"
echo " 2. Run: ./install_32bit.sh"
echo " 3. If Pillow wheel is missing, run: ./build_pillow_local.sh (see instructions below)"
else
echo "❌ No wheel files downloaded"
echo ""
echo "💡 Troubleshooting:"
echo " • Check internet connection"
echo " • Verify requirements.txt exists"
echo " • Some packages may not have 32-bit wheels available"
fi

107
install_32bit.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/bin/bash
# Dedicated Offline Installer for 32-bit Raspberry Pi OS (armv7l)
# Ensures clean installation with proper architecture isolation
set -e
echo "================================="
echo " TKINTER PLAYER 32-BIT INSTALL "
echo "================================="
# 1. Check architecture
ARCH=$(uname -m)
echo "Architecture: $ARCH"
if [ "$ARCH" != "armv7l" ]; then
echo "ERROR: This script is for 32-bit Raspberry Pi OS (armv7l) only!"
echo "Current architecture: $ARCH"
exit 1
fi
# 2. Check library folder
LIBS_FOLDER="req_libraries_32bit"
if [ ! -d "$LIBS_FOLDER" ]; then
echo "ERROR: $LIBS_FOLDER not found!"
echo "Please download 32-bit libraries first."
exit 1
fi
echo "Library folder: $LIBS_FOLDER"
WHEEL_COUNT=$(ls $LIBS_FOLDER/*.whl | wc -l)
echo "Wheel files: $WHEEL_COUNT"
# 3. Show key 32-bit packages
echo ""
echo "32-bit packages to install:"
if ls $LIBS_FOLDER/bcrypt*.whl >/dev/null 2>&1; then
BCRYPT_FILE=$(basename $(ls $LIBS_FOLDER/bcrypt*.whl | head -1))
echo " BCRYPT: $BCRYPT_FILE"
fi
echo ""
# 4. Clean existing virtual environment
if [ -d ".venv" ]; then
echo "Removing existing .venv to ensure clean installation..."
rm -rf .venv
fi
# 5. Create fresh virtual environment
echo "Creating fresh virtual environment..."
python3 -m venv .venv --clear
# 6. Activate environment
echo "Activating virtual environment..."
source .venv/bin/activate
# 7. Ensure we're using the virtual environment
echo "Python location: $(which python)"
echo "Pip location: $(which pip)"
# 8. Upgrade pip
echo "Upgrading pip..."
pip install --upgrade pip
# 9. Install packages with forced 32-bit isolation
echo ""
echo "Installing 32-bit packages (completely isolated)..."
pip install --no-index --no-deps --force-reinstall --find-links $LIBS_FOLDER/ $LIBS_FOLDER/*.whl
# 10. Verify 32-bit installation
echo ""
echo "Verifying 32-bit installation..."
python3 -c "
import sys
print(f'Python executable: {sys.executable}')
print(f'Architecture: $(uname -m)')
try:
import bcrypt
print('✅ bcrypt imported successfully')
# Test bcrypt functionality
password = b'test'
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
if bcrypt.checkpw(password, hashed):
print('✅ bcrypt functionality test passed')
else:
print('❌ bcrypt functionality test failed')
except Exception as e:
print(f'❌ bcrypt import/test failed: {e}')
try:
import requests
print('✅ requests imported successfully')
except Exception as e:
print(f'❌ requests failed: {e}')
try:
import vlc
print('✅ python-vlc imported successfully')
except Exception as e:
print(f'❌ python-vlc failed: {e}')
"
echo ""
echo "✅ 32-bit installation completed!"
echo ""
echo "Next steps:"
echo "1. source .venv/bin/activate"
echo "2. ./run_app.sh"

View File

@@ -1,34 +0,0 @@
#!/bin/bash
# Minimal installer for Raspberry Pi OS (no desktop environment)
# Installs Xorg, Openbox, disables power saving, and configures auto-launch of the signage player
set -e
USER_HOME="/home/pi"
PROJECT_DIR="$USER_HOME/Desktop/signage-player"
APP_LAUNCH_SCRIPT="$PROJECT_DIR/run_tkinter_app.sh"
# Update system
sudo apt update
sudo apt upgrade -y
# Install minimal X server and Openbox
sudo apt install -y xorg openbox
# Install VLC for video playback
sudo apt install -y vlc
# Install Python and dependencies
sudo apt install -y python3 python3-pip python3-venv python3-tk ffmpeg libopencv-dev python3-opencv \
libsdl2-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-ttf-dev \
libjpeg-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev
# Create virtual environment and install Python requirements
cd "$PROJECT_DIR"
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r tkinter_requirements.txt
pip install python-vlc
chmod +x "$APP_LAUNCH_SCRIPT"
deactivate

68
install_offline.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
# Simple Offline Installation Script for Tkinter Player
set -e
echo "================================="
echo " TKINTER PLAYER OFFLINE INSTALL"
echo "================================="
# 1. Check architecture
# Detect architecture, treating armhf and armv7l as 32-bit
ARCH=$(uname -m)
if lscpu | grep -qi 'armhf'; then
ARCH_TYPE="armhf"
else
ARCH_TYPE="$ARCH"
fi
echo "Architecture: $ARCH_TYPE"
# 2. Select library folder
# Use 32-bit libraries for armv7l or armhf
if [ "$ARCH_TYPE" = "armv7l" ] || [ "$ARCH_TYPE" = "armhf" ]; then
LIBS_FOLDER="req_libraries_32bit"
echo "Using: 32-bit libraries (armv7l/armhf)"
elif [ "$ARCH_TYPE" = "aarch64" ]; then
LIBS_FOLDER="req_libraries"
echo "Using: 64-bit libraries (aarch64)"
else
LIBS_FOLDER="req_libraries"
echo "Using: default libraries"
fi
# 3. Check folder exists
if [ ! -d "$LIBS_FOLDER" ]; then
echo "ERROR: $LIBS_FOLDER not found!"
exit 1
fi
echo "Library folder: $LIBS_FOLDER"
WHEEL_COUNT=$(ls $LIBS_FOLDER/*.whl | wc -l)
echo "Wheel files: $WHEEL_COUNT"
# 4. Create .venv
echo ""
echo "Creating .venv..."
python3 -m venv .venv
# 5. Activate environment
echo "Activating environment..."
source .venv/bin/activate
# 6. Upgrade pip
#echo "Upgrading pip..."
#pip install --upgrade pip
# 7. Install packages offline
echo ""
echo "Installing from $LIBS_FOLDER..."
pip install --no-index --no-deps --find-links $LIBS_FOLDER/ $LIBS_FOLDER/*.whl
echo ""
echo "✅ Installation completed!"
echo ""
echo "Next steps:"
echo "1. source .venv/bin/activate"
echo "2. ./run_app.sh"

View File

@@ -1,68 +0,0 @@
#!/bin/bash
# Tkinter Media Player Installation Script
echo "Installing Tkinter Media Player..."
# Update system packages
echo "Updating system packages..."
sudo apt update
sudo apt upgrade -y
# Install system dependencies
echo "Installing system dependencies..."
sudo apt install -y python3 python3-pip python3-venv python3-tk
sudo apt install -y ffmpeg libopencv-dev python3-opencv
sudo apt install -y libsdl2-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-ttf-dev
sudo apt install -y libjpeg-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev
# Create project directory if it doesn't exist
PROJECT_DIR="/home/pi/Desktop/tkinter_player"
if [ ! -d "$PROJECT_DIR" ]; then
echo "Project directory not found. Please ensure the tkinter_player directory exists."
exit 1
fi
cd "$PROJECT_DIR"
# Create virtual environment
echo "Creating Python virtual environment..."
python3 -m venv venv
# Activate virtual environment and install requirements
echo "Installing Python dependencies..."
source venv/bin/activate
pip install --upgrade pip
pip install -r tkinter_requirements.txt
deactivate
# Make launcher script executable
chmod +x run_tkinter_app.sh
# Create systemd service for auto-start
echo "Creating systemd service..."
sudo tee /etc/systemd/system/tkinter-signage-player.service > /dev/null <<EOF
[Unit]
Description=Tkinter Signage Player
After=graphical-session.target
[Service]
Type=simple
User=pi
Environment=DISPLAY=:0
ExecStart=/home/pi/Desktop/signage-player/run_tkinter_app.sh
Restart=always
RestartSec=10
[Install]
WantedBy=graphical-session.target
EOF
# Enable the service
sudo systemctl daemon-reload
sudo systemctl enable tkinter-signage-player.service
echo "Installation completed!"
echo "The tkinter media player will start automatically on boot."
echo "To start manually, run: ./run_tkinter_app.sh"
echo "To stop the service: sudo systemctl stop tkinter-signage-player.service"
echo "To view logs: sudo journalctl -u tkinter-signage-player.service -f"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,7 @@
# Core requirements for tkinter_player signage app
python-vlc
Pillow
pyautogui
requests
bcrypt
python-vlc
pyautogui
setuptools
wheel

35
run_app.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Launch script for the tkinter signage player
# Get script directory and navigate there
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
echo "=== Tkinter Signage Player Launcher ==="
echo "Working directory: $SCRIPT_DIR"
# Activate virtual environment
if [ -d ".venv" ]; then
echo "Activating virtual environment..."
source .venv/bin/activate
echo "Virtual environment activated"
else
echo "Warning: No virtual environment found"
fi
# Set display environment
export DISPLAY=${DISPLAY:-:0.0}
# Create directories if needed
mkdir -p signage_player/main_data
# Change to signage_player directory
cd signage_player
echo "Starting application..."
python main.py
# Deactivate venv when done
if [ -d "../.venv" ]; then
deactivate
fi

View File

@@ -1,14 +1,40 @@
#!/bin/bash
# Debugging launch script for the tkinter player application
# Activate the virtual environment
source venv/bin/activate
# Set working directory to script location
cd "$(dirname "$0")"
# Change to the tkinter app src directory
cd signage_player/src
# Activate the virtual environment if it exists
if [ -d "venv" ]; then
source venv/bin/activate
echo "Virtual environment activated"
else
echo "Warning: No virtual environment found, using system Python"
fi
# Set environment variables for better display on Raspberry Pi
export DISPLAY=${DISPLAY:-:0.0}
export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/tmp/runtime-pi}
# Change to the signage_player directory
cd signage_player
# Run the main application with full error output
python main.py
echo "Starting tkinter player..."
echo "Working directory: $(pwd)"
echo "Python path: $(which python3)"
echo "Display: $DISPLAY"
# Run with error logging
python3 main.py 2>&1 | tee ../main_data/app_debug.log
# Capture exit code
EXIT_CODE=$?
# Deactivate virtual environment when done
deactivate
if [ -d "../venv" ]; then
deactivate
fi
echo "Application exited with code: $EXIT_CODE"
exit $EXIT_CODE

View File

@@ -3,8 +3,204 @@ import json
import requests
import bcrypt
import re
import datetime
from logging_config import Logger
# Global variable to track server connectivity status
SERVER_CONNECTION_STATUS = {
'is_online': True,
'last_successful_connection': None,
'last_playlist_update': None,
'error_message': None
}
def get_server_status():
"""Get current server connection status"""
return SERVER_CONNECTION_STATUS.copy()
def get_last_playlist_update_time():
"""Get the timestamp of the last playlist update from filesystem"""
try:
playlist_dir = os.path.join(os.path.dirname(__file__), 'static_data', 'playlist')
if os.path.exists(playlist_dir):
playlist_files = [f for f in os.listdir(playlist_dir) if f.startswith('server_playlist_v') and f.endswith('.json')]
if playlist_files:
# Get the most recent playlist file
latest_file = max([os.path.join(playlist_dir, f) for f in playlist_files], key=os.path.getmtime)
mod_time = os.path.getmtime(latest_file)
return datetime.datetime.fromtimestamp(mod_time)
return None
except Exception as e:
Logger.error(f"Error getting last playlist update time: {e}")
return None
def set_server_offline(error_message=None):
"""Mark server as offline with optional error message"""
global SERVER_CONNECTION_STATUS
SERVER_CONNECTION_STATUS['is_online'] = False
SERVER_CONNECTION_STATUS['error_message'] = error_message
Logger.warning(f"Server marked as offline: {error_message}")
def set_server_online():
"""Mark server as online and update connection time"""
global SERVER_CONNECTION_STATUS
SERVER_CONNECTION_STATUS['is_online'] = True
SERVER_CONNECTION_STATUS['last_successful_connection'] = datetime.datetime.now()
SERVER_CONNECTION_STATUS['error_message'] = None
Logger.info("Server connection restored")
def send_player_feedback(config, message, status="active", playlist_version=None, error_details=None):
"""
Send feedback to the server about player status.
Args:
config (dict): Configuration containing server details
message (str): Main feedback message
status (str): Player status - "active", "playing", "error", "restarting"
playlist_version (int, optional): Current playlist version being played
error_details (str, optional): Error details if status is "error"
Returns:
bool: True if feedback sent successfully, False otherwise
"""
try:
server = config.get("server_ip", "")
host = config.get("screen_name", "")
quick = config.get("quickconnect_key", "")
port = config.get("port", "")
# Construct server URL
ip_pattern = r'^\d+\.\d+\.\d+\.\d+$'
if re.match(ip_pattern, server):
feedback_url = f'http://{server}:{port}/api/player-feedback'
else:
feedback_url = f'http://{server}/api/player-feedback'
# Prepare feedback data
feedback_data = {
'player_name': host,
'quickconnect_code': quick,
'message': message,
'status': status,
'timestamp': datetime.datetime.now().isoformat(),
'playlist_version': playlist_version,
'error_details': error_details
}
Logger.info(f"Sending feedback to {feedback_url}: {feedback_data}")
# Send POST request
response = requests.post(feedback_url, json=feedback_data, timeout=10)
if response.status_code == 200:
Logger.info(f"Feedback sent successfully: {message}")
# Mark server as online on successful feedback
set_server_online()
return True
else:
Logger.warning(f"Feedback failed with status {response.status_code}: {response.text}")
set_server_offline(f"Feedback failed with status {response.status_code}")
return False
except requests.exceptions.RequestException as e:
Logger.error(f"Failed to send feedback: {e}")
set_server_offline(f"Network error during feedback: {e}")
return False
except Exception as e:
Logger.error(f"Unexpected error sending feedback: {e}")
set_server_offline(f"Unexpected error during feedback: {e}")
return False
def send_playlist_check_feedback(config, playlist_version=None):
"""
Send feedback when server is interrogated for playlist updates.
Args:
config (dict): Configuration containing server details
playlist_version (int, optional): Current playlist version
Returns:
bool: True if feedback sent successfully, False otherwise
"""
player_name = config.get("screen_name", "unknown")
version_info = f"playlist v{playlist_version}" if playlist_version else "unknown"
message = f"player {player_name}, server interrogation, checking for updates : {version_info}"
return send_player_feedback(
config=config,
message=message,
status="active",
playlist_version=playlist_version
)
def send_playlist_restart_feedback(config, playlist_version=None):
"""
Send feedback when playlist loop ends and restarts.
Args:
config (dict): Configuration containing server details
playlist_version (int, optional): Current playlist version
Returns:
bool: True if feedback sent successfully, False otherwise
"""
player_name = config.get("screen_name", "unknown")
version_info = f"playlist v{playlist_version}" if playlist_version else "unknown"
message = f"player {player_name}, playlist working in loop, cycle completed : {version_info}"
return send_player_feedback(
config=config,
message=message,
status="restarting",
playlist_version=playlist_version
)
def send_player_error_feedback(config, error_message, playlist_version=None):
"""
Send feedback when an error occurs in the player.
Args:
config (dict): Configuration containing server details
error_message (str): Description of the error
playlist_version (int, optional): Current playlist version
Returns:
bool: True if feedback sent successfully, False otherwise
"""
player_name = config.get("screen_name", "unknown")
message = f"player {player_name}, error occurred"
return send_player_feedback(
config=config,
message=message,
status="error",
playlist_version=playlist_version,
error_details=error_message
)
def send_playing_status_feedback(config, playlist_version=None, current_media=None):
"""
Send feedback about playlist starting (first media).
Args:
config (dict): Configuration containing server details
playlist_version (int, optional): Current playlist version
current_media (str, optional): First media file in playlist
Returns:
bool: True if feedback sent successfully, False otherwise
"""
player_name = config.get("screen_name", "unknown")
version_info = f"playlist v{playlist_version}" if playlist_version else "unknown"
message = f"player {player_name}, playlist started : {version_info}"
return send_player_feedback(
config=config,
message=message,
status="playing",
playlist_version=playlist_version
)
def is_playlist_up_to_date(local_playlist_path, config):
"""
Compare the version of the local playlist with the server playlist.
@@ -39,25 +235,44 @@ def fetch_server_playlist(config):
'quickconnect_code': quick
}
Logger.info(f"Fetching playlist from URL: {server_url} with params: {params}")
response = requests.get(server_url, params=params)
response = requests.get(server_url, params=params, timeout=15)
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.")
# Mark server as online on successful connection
set_server_online()
return {'playlist': playlist, 'version': version}
else:
Logger.error("Quickconnect code validation failed.")
set_server_offline("Authentication failed - invalid quickconnect code")
else:
Logger.error("Failed to retrieve playlist or hashed quickconnect from the response.")
set_server_offline("Invalid server response - missing playlist data")
else:
Logger.error(f"Failed to fetch playlist. Status Code: {response.status_code}")
set_server_offline(f"Server returned error code: {response.status_code}")
except requests.exceptions.ConnectTimeout as e:
Logger.error(f"Connection timeout while fetching playlist: {e}")
set_server_offline("Connection timeout - server unreachable")
except requests.exceptions.ConnectionError as e:
Logger.error(f"Connection error while fetching playlist: {e}")
set_server_offline("Connection failed - server unreachable")
except requests.exceptions.RequestException as e:
Logger.error(f"Failed to fetch playlist: {e}")
Logger.error(f"Request error while fetching playlist: {e}")
set_server_offline(f"Network error: {str(e)}")
except Exception as e:
Logger.error(f"Unexpected error while fetching playlist: {e}")
set_server_offline(f"Unexpected error: {str(e)}")
return {'playlist': [], 'version': 0}
def save_playlist_with_version(playlist_data, playlist_dir):
@@ -141,8 +356,11 @@ def update_playlist_if_needed(local_playlist_path, config, media_dir, playlist_d
"""
Fetch the server playlist once, compare versions, and update if needed.
Returns True if updated, False if already up to date.
Also sends feedback to server about playlist check.
"""
import json
global SERVER_CONNECTION_STATUS
server_data = fetch_server_playlist(config)
server_version = server_data.get('version', 0)
if not os.path.exists(local_playlist_path):
@@ -151,20 +369,30 @@ def update_playlist_if_needed(local_playlist_path, config, media_dir, playlist_d
with open(local_playlist_path, 'r') as f:
local_data = json.load(f)
local_version = local_data.get('version', 0)
Logger.info(f"Local playlist version: {local_version}, Server playlist version: {server_version}")
if local_version != server_version:
# Only send feedback if server is online
if SERVER_CONNECTION_STATUS['is_online']:
send_playlist_check_feedback(config, server_version if server_version > 0 else local_version)
if local_version != server_version and server_version > 0:
if server_data and server_data.get('playlist'):
updated_playlist = download_media_files(server_data['playlist'], media_dir)
server_data['playlist'] = updated_playlist
save_playlist_with_version(server_data, playlist_dir)
# Delete old playlists and unreferenced media
delete_old_playlists_and_media(server_version, playlist_dir, media_dir)
# Update last playlist update time
SERVER_CONNECTION_STATUS['last_playlist_update'] = datetime.datetime.now()
return True
else:
Logger.warning("No playlist data fetched from server or playlist is empty.")
return False
else:
Logger.info("Local playlist is already up to date.")
Logger.info("Local playlist is already up to date or server is offline.")
return False

View File

@@ -2,7 +2,7 @@
"screen_orientation": "Landscape",
"screen_name": "tv-terasa",
"quickconnect_key": "8887779",
"server_ip": "10.232.7.231",
"server_ip": "192.168.1.22",
"port": "80",
"screen_w": "1920",
"screen_h": "1080",

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import tkinter as tk
import vlc
import subprocess
import sys
from get_playlists import send_playlist_restart_feedback, send_player_error_feedback, send_playing_status_feedback, send_playlist_check_feedback, get_server_status, get_last_playlist_update_time
CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'main_data', 'app_config.txt')
PLAYLIST_DIR = os.path.join(os.path.dirname(__file__), 'static_data', 'playlist')
@@ -19,6 +20,14 @@ class SimpleTkPlayer:
self.is_transitioning = False # Flag to prevent rapid cycling
self.is_exiting = False # Flag to prevent operations during exit
# Load configuration for feedback
self.config = self.load_config()
self.playlist_version = self.get_current_playlist_version()
# Offline notification tracking
self.offline_notification = None
self.last_server_status = True
# Initialize all timer variables to None
self.hide_controls_timer = None
self.video_watchdog = None
@@ -36,6 +45,133 @@ class SimpleTkPlayer:
self.root.after(300, self.move_mouse_to_corner)
self.root.protocol('WM_DELETE_WINDOW', self.exit_app)
def load_config(self):
"""Load configuration for feedback functionality"""
try:
with open(CONFIG_PATH, 'r') as f:
config = json.load(f)
return config
except Exception as e:
print(f"[CONFIG] Error loading config: {e}")
return {}
def get_current_playlist_version(self):
"""Get the current playlist version from the loaded playlist data"""
try:
# Try to find the latest playlist file and extract version
if os.path.exists(PLAYLIST_DIR):
playlist_files = [f for f in os.listdir(PLAYLIST_DIR) if f.startswith('server_playlist_v') and f.endswith('.json')]
if playlist_files:
# Get the highest version number
versions = [int(f.split('_v')[-1].split('.json')[0]) for f in playlist_files]
return max(versions)
return None
except Exception as e:
print(f"[CONFIG] Error getting playlist version: {e}")
return None
def update_playlist_version(self):
"""Update the current playlist version - call when playlist changes"""
self.playlist_version = self.get_current_playlist_version()
print(f"[CONFIG] Updated playlist version to: {self.playlist_version}")
return self.playlist_version
def send_error_feedback(self, error_message):
"""Send error feedback to server"""
try:
if self.config:
send_player_error_feedback(self.config, error_message, self.playlist_version)
except Exception as e:
print(f"[FEEDBACK] Error sending error feedback: {e}")
def send_playing_feedback(self, current_media=None):
"""Send playing status feedback to server"""
try:
if self.config:
send_playing_status_feedback(self.config, self.playlist_version, current_media)
except Exception as e:
print(f"[FEEDBACK] Error sending playing feedback: {e}")
def send_restart_feedback(self):
"""Send playlist restart feedback to server"""
try:
if self.config:
send_playlist_restart_feedback(self.config, self.playlist_version)
except Exception as e:
print(f"[FEEDBACK] Error sending restart feedback: {e}")
def send_server_check_feedback(self):
"""Send server interrogation feedback"""
try:
if self.config:
send_playlist_check_feedback(self.config, self.playlist_version)
except Exception as e:
print(f"[FEEDBACK] Error sending server check feedback: {e}")
def check_server_status(self):
"""Check server connectivity status and show/hide offline notification"""
try:
server_status = get_server_status()
is_online = server_status['is_online']
# Only update notification if status changed
if is_online != self.last_server_status:
if not is_online:
# Server went offline
self.show_offline_notification(server_status)
else:
# Server came back online
self.hide_offline_notification()
self.last_server_status = is_online
except Exception as e:
print(f"[OFFLINE] Error checking server status: {e}")
def show_offline_notification(self, server_status):
"""Show offline notification at bottom of screen"""
try:
if self.offline_notification:
return # Already showing
# Get last playlist update time from filesystem
last_update = get_last_playlist_update_time()
if last_update:
update_time = last_update.strftime("%Y-%m-%d %H:%M:%S")
message = f"OFFLINE MODE: Playing last available playlist updated at: {update_time}"
else:
message = "OFFLINE MODE: Playing last available playlist"
# Create notification label at bottom of screen
self.offline_notification = tk.Label(
self.root,
text=message,
bg='orange',
fg='black',
font=('Arial', 14, 'bold'),
relief='raised',
bd=2
)
# Position at bottom of screen
self.offline_notification.pack(side='bottom', fill='x', padx=5, pady=5)
self.offline_notification.lift() # Bring to front
print(f"[OFFLINE] Showing offline notification: {message}")
except Exception as e:
print(f"[OFFLINE] Error showing offline notification: {e}")
def hide_offline_notification(self):
"""Hide offline notification when server comes back online"""
try:
if self.offline_notification:
self.offline_notification.destroy()
self.offline_notification = None
print("[OFFLINE] Server back online - hiding offline notification")
except Exception as e:
print(f"[OFFLINE] Error hiding offline notification: {e}")
def ensure_fullscreen(self):
self.root.attributes('-fullscreen', True)
self.root.update_idletasks()
@@ -150,11 +286,49 @@ class SimpleTkPlayer:
self.show_current_media()
def update_playlist_from_server(self):
# Dummy implementation: replace with your actual update logic
# For example, call a function to fetch and reload the playlist
print("[INFO] Updating playlist from server...")
# You can import and call your real update function here
# Example: self.playlist = get_latest_playlist()
"""Update playlist from server and reload if needed"""
try:
from get_playlists import update_playlist_if_needed
import json
print("[INFO] Checking for playlist updates from server...")
# Get config
config_path = os.path.join(os.path.dirname(__file__), 'main_data', 'app_config.txt')
with open(config_path, 'r') as config_file:
config = json.load(config_file)
# Define paths
playlist_dir = os.path.join(os.path.dirname(__file__), 'static_data', 'playlist')
media_dir = os.path.join(os.path.dirname(__file__), 'static_data', 'media')
# Find current playlist file
playlist_files = [f for f in os.listdir(playlist_dir) if f.startswith('server_playlist_v') and f.endswith('.json')]
if playlist_files:
current_playlist_path = os.path.join(playlist_dir, max(playlist_files))
else:
current_playlist_path = os.path.join(playlist_dir, 'server_playlist_v0.json')
# Check and update if needed
updated = update_playlist_if_needed(current_playlist_path, config, media_dir, playlist_dir)
if updated:
print("[INFO] Playlist updated! Reloading...")
# Reload the playlist
self.update_playlist_version()
# Restart playback with new playlist
self.current_index = 0
self.load_playlist()
else:
print("[INFO] Playlist is already up to date.")
except Exception as e:
print(f"[ERROR] Failed to update playlist: {e}")
from get_playlists import send_player_error_feedback
try:
send_player_error_feedback(config, f"Playlist update failed: {str(e)}")
except:
pass
def toggle_pause(self):
if not self.paused:
@@ -247,34 +421,55 @@ class SimpleTkPlayer:
check_end()
except Exception as e:
print(f"[VLC] Error playing video {file_path}: {e}")
self.send_error_feedback(f"Video playback error: {str(e)} - {os.path.basename(file_path)}")
if on_end:
on_end()
def show_current_media(self):
self.root.attributes('-fullscreen', True)
self.root.update_idletasks()
if not self.playlist:
print("[PLAYER] Playlist is empty. No media to show.")
self.label.config(text="No media available", fg='white', font=('Arial', 32))
# Try to reload playlist after 10 seconds
self.root.after(10000, self.reload_playlist_and_continue)
return
media = self.playlist[self.current_index]
file_path = os.path.join(MEDIA_DATA_PATH, media['file_name'])
ext = file_path.lower()
duration = media.get('duration', None)
if not os.path.isfile(file_path):
print(f"[PLAYER] File missing: {file_path}. Skipping to next.")
self.next_media()
return
if ext.endswith(('.mp4', '.avi', '.mov', '.mkv')):
self.show_video(file_path, on_end=self.next_media, duration=duration)
elif ext.endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')):
# Use PIL for images instead of VLC to avoid display issues
self.show_image_via_pil(file_path, duration if duration is not None else 10, on_end=self.next_media)
else:
print(f"[PLAYER] Unsupported file type: {media['file_name']}")
self.label.config(text=f"Unsupported: {media['file_name']}", fg='yellow')
try:
# Check server status and update offline notification
self.check_server_status()
self.root.attributes('-fullscreen', True)
self.root.update_idletasks()
if not self.playlist:
print("[PLAYER] Playlist is empty. No media to show.")
self.label.config(text="No media available", fg='white', font=('Arial', 32))
# Send error feedback
self.send_error_feedback("Playlist is empty, no media to show")
# Try to reload playlist after 10 seconds
self.root.after(10000, self.reload_playlist_and_continue)
return
media = self.playlist[self.current_index]
file_path = os.path.join(MEDIA_DATA_PATH, media['file_name'])
ext = file_path.lower()
duration = media.get('duration', None)
# Only send playing status feedback when starting the first media (playlist start)
if self.current_index == 0:
self.send_playing_feedback(media['file_name'])
if not os.path.isfile(file_path):
print(f"[PLAYER] File missing: {file_path}. Skipping to next.")
self.send_error_feedback(f"Media file missing: {media['file_name']}")
self.next_media()
return
if ext.endswith(('.mp4', '.avi', '.mov', '.mkv')):
self.show_video(file_path, on_end=self.next_media, duration=duration)
elif ext.endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')):
# Use PIL for images instead of VLC to avoid display issues
self.show_image_via_pil(file_path, duration if duration is not None else 10, on_end=self.next_media)
else:
print(f"[PLAYER] Unsupported file type: {media['file_name']}")
self.label.config(text=f"Unsupported: {media['file_name']}", fg='yellow')
self.send_error_feedback(f"Unsupported file type: {media['file_name']}")
self.root.after(2000, self.next_media)
except Exception as e:
print(f"[PLAYER] Error in show_current_media: {e}")
self.send_error_feedback(f"Error showing media: {str(e)}")
# Try to continue with next media after error
self.root.after(2000, self.next_media)
def show_image_via_pil(self, file_path, duration, on_end=None):
@@ -331,6 +526,7 @@ class SimpleTkPlayer:
except Exception as e:
print(f"[PLAYER] PIL image display failed: {e}. Skipping.")
self.send_error_feedback(f"Image display error: {str(e)} - {os.path.basename(file_path)}")
self.label.config(text=f"Image Error: {os.path.basename(file_path)}", fg='red', font=('Arial', 24))
self.root.after(2000, lambda: on_end() if on_end else None)
@@ -340,6 +536,8 @@ class SimpleTkPlayer:
if new_playlist:
self.playlist = new_playlist
self.current_index = 0
# Update playlist version after reloading
self.update_playlist_version()
print("[PLAYER] Playlist reloaded. Continuing playback.")
self.show_current_media()
else:
@@ -440,9 +638,18 @@ class SimpleTkPlayer:
return
self.is_transitioning = True
# Check if we're about to restart the playlist (loop back to beginning)
was_at_end = self.current_index == len(self.playlist) - 1
self.current_index = (self.current_index + 1) % len(self.playlist)
print(f"[PLAYER] Moving to next media: index {self.current_index}")
# Send feedback if playlist restarted (loop completed)
if was_at_end and self.current_index == 0:
print("[FEEDBACK] Playlist loop completed, sending restart feedback")
self.send_restart_feedback()
# Clear any existing timers safely
timer_names = ['video_watchdog', 'image_watchdog', 'image_timer']
for timer_name in timer_names:
@@ -761,6 +968,9 @@ class SimpleTkPlayer:
try:
print("[EXIT] Destroying controls...")
# Hide offline notification if showing
self.hide_offline_notification()
# First, try to hide and destroy the main controls window
if hasattr(self, 'controls_win') and self.controls_win:
try:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,20 +1,20 @@
{
"playlist": [
{
"file_name": "HARTING_Safety_day_informare_2_page_003.jpg",
"url": "media/HARTING_Safety_day_informare_2_page_003.jpg",
"file_name": "one-piece-season-2-5120x2880-23673.jpg",
"url": "media/one-piece-season-2-5120x2880-23673.jpg",
"duration": 30
},
{
"file_name": "call-of-duty-black-3840x2160-23674.jpg",
"url": "media/call-of-duty-black-3840x2160-23674.jpg",
"duration": 30
},
{
"file_name": "big-buck-bunny-1080p-60fps-30sec.mp4",
"url": "media/big-buck-bunny-1080p-60fps-30sec.mp4",
"duration": 30
},
{
"file_name": "one-piece-season-2-5120x2880-23673.jpg",
"url": "media/one-piece-season-2-5120x2880-23673.jpg",
"duration": 30
}
],
"version": 6
"version": 8
}

81
troubleshoot_32bit.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
# Troubleshooting script for 32-bit bcrypt issues
echo "🔍 32-BIT SYSTEM TROUBLESHOOTING"
echo "================================"
# System info
echo "1. System Information:"
echo " Architecture: $(uname -m)"
echo " OS: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
echo " Python: $(python3 --version)"
echo ""
# Check if we're in virtual environment
echo "2. Environment Check:"
if [ -n "$VIRTUAL_ENV" ]; then
echo " ✅ In virtual environment: $VIRTUAL_ENV"
echo " Python location: $(which python)"
echo " Pip location: $(which pip)"
else
echo " ❌ Not in virtual environment"
echo " System Python: $(which python3)"
fi
echo ""
# Check bcrypt installation
echo "3. Bcrypt Investigation:"
if [ -n "$VIRTUAL_ENV" ]; then
echo " Checking installed bcrypt..."
pip list | grep bcrypt || echo " ❌ bcrypt not installed"
echo " Testing bcrypt import..."
python3 -c "
try:
import bcrypt
print(' ✅ bcrypt imports successfully')
# Check bcrypt module location
print(f' Module location: {bcrypt.__file__}')
# Test basic functionality
test_pw = b'test123'
hashed = bcrypt.hashpw(test_pw, bcrypt.gensalt())
if bcrypt.checkpw(test_pw, hashed):
print(' ✅ bcrypt functionality works')
else:
print(' ❌ bcrypt functionality failed')
except ImportError as e:
print(f' ❌ bcrypt import failed: {e}')
except Exception as e:
print(f' ❌ bcrypt error: {e}')
"
else
echo " ⚠️ Please activate virtual environment first"
fi
echo ""
echo "4. Library Files Check:"
if [ -d "req_libraries_32bit" ]; then
echo " ✅ req_libraries_32bit exists"
if ls req_libraries_32bit/bcrypt*.whl >/dev/null 2>&1; then
BCRYPT_FILE=$(ls req_libraries_32bit/bcrypt*.whl | head -1)
echo " 32-bit bcrypt: $(basename $BCRYPT_FILE)"
# Quick check of wheel architecture
echo " Checking wheel architecture..."
unzip -l "$BCRYPT_FILE" | grep "\.so" | head -1 || echo " No .so files found"
else
echo " ❌ No bcrypt wheel found in 32-bit libraries"
fi
else
echo " ❌ req_libraries_32bit folder missing"
fi
echo ""
echo "5. Recommendations:"
echo " • Ensure you're on 32-bit Raspberry Pi OS (armv7l)"
echo " • Use: source .venv/bin/activate"
echo " • Run: ./install_32bit.sh for clean installation"
echo " • If still failing, remove .venv and reinstall"

View File

@@ -1,11 +0,0 @@
Video Profile for Player Compatibility (based on intro1.mp4)
Codec: H.264
Profile: Main
Resolution: 1920x1080
Bitrate: ~14,700 kbps
Framerate: 29.97 fps
Recommended ffmpeg conversion command:
ffmpeg -i input.mp4 -c:v libx264 -profile:v main -b:v 14700k -vf "scale=1920:1080,fps=29.97" -c:a copy output_normalized.mp4