Spaces:
Runtime error
Runtime error
#!/usr/bin/env python3 | |
""" | |
Create a User-Friendly Mac App Bundle for SafetyMaster Pro | |
with proper GUI interface and clear user guidance | |
""" | |
import os | |
import shutil | |
import stat | |
import plistlib | |
from pathlib import Path | |
def create_user_friendly_mac_app(): | |
"""Create a Mac app bundle with proper user interface.""" | |
app_name = "SafetyMaster Pro" | |
app_dir = f"{app_name}.app" | |
# Remove existing app if it exists | |
if os.path.exists(app_dir): | |
shutil.rmtree(app_dir) | |
# Create app bundle structure | |
contents_dir = os.path.join(app_dir, "Contents") | |
macos_dir = os.path.join(contents_dir, "MacOS") | |
resources_dir = os.path.join(contents_dir, "Resources") | |
os.makedirs(macos_dir, exist_ok=True) | |
os.makedirs(resources_dir, exist_ok=True) | |
# Copy all necessary files to Resources | |
files_to_copy = [ | |
'web_interface.py', | |
'safety_detector.py', | |
'camera_manager.py', | |
'config.py', | |
'requirements.txt', | |
'ppe_yolov8_model_0.pt', | |
'ppe_model.pt', | |
'yolov8n.pt' | |
] | |
for file in files_to_copy: | |
if os.path.exists(file): | |
shutil.copy2(file, resources_dir) | |
print(f"Copied {file}") | |
# Copy templates directory | |
if os.path.exists('templates'): | |
shutil.copytree('templates', os.path.join(resources_dir, 'templates')) | |
print("Copied templates directory") | |
# Create a Python GUI launcher script | |
gui_launcher_script = '''#!/usr/bin/env python3 | |
""" | |
SafetyMaster Pro - Mac GUI Launcher | |
Provides a proper user interface with status window and controls | |
""" | |
import tkinter as tk | |
from tkinter import ttk, messagebox | |
import subprocess | |
import threading | |
import time | |
import webbrowser | |
import sys | |
import os | |
import signal | |
from pathlib import Path | |
class SafetyMasterGUI: | |
def __init__(self): | |
self.root = tk.Tk() | |
self.root.title("SafetyMaster Pro") | |
self.root.geometry("500x400") | |
self.root.resizable(False, False) | |
# Set app icon and styling | |
self.setup_styling() | |
# Variables | |
self.server_process = None | |
self.is_running = False | |
self.status_var = tk.StringVar(value="Ready to start") | |
# Create GUI | |
self.create_widgets() | |
# Handle window closing | |
self.root.protocol("WM_DELETE_WINDOW", self.on_closing) | |
def setup_styling(self): | |
"""Setup the GUI styling.""" | |
self.root.configure(bg='#2c3e50') | |
# Configure styles | |
style = ttk.Style() | |
style.theme_use('clam') | |
# Configure custom styles | |
style.configure('Title.TLabel', | |
background='#2c3e50', | |
foreground='#ecf0f1', | |
font=('Arial', 16, 'bold')) | |
style.configure('Status.TLabel', | |
background='#2c3e50', | |
foreground='#3498db', | |
font=('Arial', 10)) | |
style.configure('Action.TButton', | |
font=('Arial', 12, 'bold')) | |
def create_widgets(self): | |
"""Create the GUI widgets.""" | |
# Title | |
title_label = ttk.Label(self.root, | |
text="SafetyMaster Pro", | |
style='Title.TLabel') | |
title_label.pack(pady=20) | |
# Subtitle | |
subtitle_label = ttk.Label(self.root, | |
text="Real-time AI Safety Equipment Detection", | |
background='#2c3e50', | |
foreground='#95a5a6', | |
font=('Arial', 10)) | |
subtitle_label.pack(pady=(0, 20)) | |
# Status frame | |
status_frame = tk.Frame(self.root, bg='#34495e', relief='sunken', bd=2) | |
status_frame.pack(fill='x', padx=20, pady=10) | |
ttk.Label(status_frame, | |
text="Status:", | |
background='#34495e', | |
foreground='#ecf0f1', | |
font=('Arial', 10, 'bold')).pack(side='left', padx=10, pady=5) | |
ttk.Label(status_frame, | |
textvariable=self.status_var, | |
style='Status.TLabel').pack(side='left', padx=10, pady=5) | |
# Main buttons frame | |
buttons_frame = tk.Frame(self.root, bg='#2c3e50') | |
buttons_frame.pack(pady=20) | |
# Start/Stop button | |
self.start_button = ttk.Button(buttons_frame, | |
text="π Start Safety Monitoring", | |
command=self.toggle_monitoring, | |
style='Action.TButton') | |
self.start_button.pack(pady=10) | |
# Open Dashboard button | |
self.dashboard_button = ttk.Button(buttons_frame, | |
text="π Open Dashboard", | |
command=self.open_dashboard, | |
state='disabled') | |
self.dashboard_button.pack(pady=5) | |
# Info frame | |
info_frame = tk.Frame(self.root, bg='#2c3e50') | |
info_frame.pack(fill='both', expand=True, padx=20, pady=10) | |
# Instructions | |
instructions = """ | |
π How to use SafetyMaster Pro: | |
1. Click "Start Safety Monitoring" to begin | |
2. Grant camera permissions when prompted | |
3. Click "Open Dashboard" to view the web interface | |
4. The system will detect: | |
β’ Hard hats and helmets | |
β’ Safety vests and high-vis clothing | |
β’ Face masks and protective equipment | |
β’ Safety violations in real-time | |
π― Features: | |
β’ Real-time AI detection (30+ FPS) | |
β’ Web-based dashboard with statistics | |
β’ Violation tracking and alerts | |
β’ Cross-platform compatibility | |
β οΈ Requirements: | |
β’ Camera/webcam connected | |
β’ Python 3.8+ (auto-installed if needed) | |
β’ macOS 10.14+ (Mojave or later) | |
""" | |
info_text = tk.Text(info_frame, | |
wrap='word', | |
bg='#34495e', | |
fg='#ecf0f1', | |
font=('Arial', 9), | |
relief='flat', | |
state='disabled') | |
info_text.pack(fill='both', expand=True) | |
info_text.config(state='normal') | |
info_text.insert('1.0', instructions) | |
info_text.config(state='disabled') | |
# Footer | |
footer_label = ttk.Label(self.root, | |
text="SafetyMaster Pro v1.1 - Professional Safety Monitoring", | |
background='#2c3e50', | |
foreground='#7f8c8d', | |
font=('Arial', 8)) | |
footer_label.pack(side='bottom', pady=10) | |
def toggle_monitoring(self): | |
"""Start or stop the monitoring system.""" | |
if not self.is_running: | |
self.start_monitoring() | |
else: | |
self.stop_monitoring() | |
def start_monitoring(self): | |
"""Start the SafetyMaster Pro monitoring system.""" | |
self.status_var.set("Starting system...") | |
self.start_button.config(text="β³ Starting...", state='disabled') | |
# Start in a separate thread | |
threading.Thread(target=self._start_monitoring_thread, daemon=True).start() | |
def _start_monitoring_thread(self): | |
"""Thread function to start monitoring.""" | |
try: | |
# Check Python and dependencies | |
self.root.after(0, lambda: self.status_var.set("Checking Python installation...")) | |
# Start the web interface | |
self.root.after(0, lambda: self.status_var.set("Starting web server...")) | |
# Run the web interface | |
self.server_process = subprocess.Popen([ | |
sys.executable, 'web_interface.py' | |
], stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
# Wait a moment for server to start | |
time.sleep(3) | |
# Check if process is still running | |
if self.server_process.poll() is None: | |
self.is_running = True | |
self.root.after(0, self._monitoring_started) | |
else: | |
self.root.after(0, self._monitoring_failed) | |
except Exception as e: | |
self.root.after(0, lambda: self._monitoring_failed(str(e))) | |
def _monitoring_started(self): | |
"""Called when monitoring starts successfully.""" | |
self.status_var.set("β SafetyMaster Pro is running!") | |
self.start_button.config(text="π Stop Monitoring", state='normal') | |
self.dashboard_button.config(state='normal') | |
# Auto-open dashboard | |
self.open_dashboard() | |
def _monitoring_failed(self, error=None): | |
"""Called when monitoring fails to start.""" | |
error_msg = f"Failed to start: {error}" if error else "Failed to start monitoring" | |
self.status_var.set(f"β {error_msg}") | |
self.start_button.config(text="π Start Safety Monitoring", state='normal') | |
messagebox.showerror("Error", | |
f"Failed to start SafetyMaster Pro.\\n\\n" | |
f"This might be due to:\\n" | |
f"β’ Missing Python dependencies\\n" | |
f"β’ Camera access denied\\n" | |
f"β’ Port 8080 already in use\\n\\n" | |
f"Error: {error if error else 'Unknown error'}") | |
def stop_monitoring(self): | |
"""Stop the monitoring system.""" | |
self.status_var.set("Stopping system...") | |
self.start_button.config(text="β³ Stopping...", state='disabled') | |
if self.server_process: | |
self.server_process.terminate() | |
self.server_process.wait() | |
self.server_process = None | |
self.is_running = False | |
self.status_var.set("Stopped") | |
self.start_button.config(text="π Start Safety Monitoring", state='normal') | |
self.dashboard_button.config(state='disabled') | |
def open_dashboard(self): | |
"""Open the web dashboard in the default browser.""" | |
if self.is_running: | |
webbrowser.open('http://localhost:8080') | |
else: | |
messagebox.showwarning("Warning", | |
"Please start the monitoring system first.") | |
def on_closing(self): | |
"""Handle window closing.""" | |
if self.is_running: | |
if messagebox.askokcancel("Quit", | |
"SafetyMaster Pro is still running. Do you want to stop it and quit?"): | |
self.stop_monitoring() | |
self.root.destroy() | |
else: | |
self.root.destroy() | |
def run(self): | |
"""Run the GUI application.""" | |
self.root.mainloop() | |
if __name__ == "__main__": | |
# Change to the Resources directory | |
script_dir = os.path.dirname(os.path.abspath(__file__)) | |
os.chdir(script_dir) | |
# Create and run the GUI | |
app = SafetyMasterGUI() | |
app.run() | |
''' | |
# Write the GUI launcher script | |
gui_launcher_path = os.path.join(resources_dir, "gui_launcher.py") | |
with open(gui_launcher_path, 'w') as f: | |
f.write(gui_launcher_script) | |
# Create the main executable script | |
executable_script = '''#!/bin/bash | |
# SafetyMaster Pro - User-Friendly Mac App Launcher | |
# Provides proper GUI interface instead of floating in background | |
# Get the app bundle directory - Fixed path resolution | |
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" | |
APP_DIR="$( cd "$SCRIPT_DIR/../.." && pwd )" | |
RESOURCES_DIR="$APP_DIR/Contents/Resources" | |
# Function to show error dialog | |
show_error() { | |
osascript -e "display dialog \\"$1\\" with title \\"SafetyMaster Pro - Error\\" buttons {\\"OK\\"} default button \\"OK\\" with icon caution" | |
} | |
# Check if resources directory exists | |
if [[ ! -d "$RESOURCES_DIR" ]]; then | |
show_error "Resources directory not found at: $RESOURCES_DIR | |
This might be due to: | |
- Incomplete app bundle | |
- Incorrect installation | |
- File permissions | |
Please re-download SafetyMaster Pro." | |
exit 1 | |
fi | |
# Change to resources directory | |
cd "$RESOURCES_DIR" || { | |
show_error "Failed to access application resources at: $RESOURCES_DIR | |
Please check file permissions and try again." | |
exit 1 | |
} | |
# Detect Python installation with multiple fallbacks | |
PYTHON_CMD="" | |
# Check for various Python installations in order of preference | |
for cmd in python3.11 python3.10 python3.9 python3.8 python3 python; do | |
if command -v "$cmd" &> /dev/null; then | |
# Verify it's Python 3.8+ | |
version=$("$cmd" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "0.0") | |
if command -v bc &> /dev/null; then | |
# Use bc if available | |
if [[ $(echo "$version >= 3.8" | bc -l 2>/dev/null || echo "0") == "1" ]]; then | |
PYTHON_CMD="$cmd" | |
break | |
fi | |
else | |
# Fallback comparison without bc | |
major=$(echo "$version" | cut -d. -f1) | |
minor=$(echo "$version" | cut -d. -f2) | |
if [[ "$major" -gt 3 ]] || [[ "$major" -eq 3 && "$minor" -ge 8 ]]; then | |
PYTHON_CMD="$cmd" | |
break | |
fi | |
fi | |
fi | |
done | |
# If no suitable Python found, provide installation guidance | |
if [[ -z "$PYTHON_CMD" ]]; then | |
show_error "Python 3.8+ is required but not found. | |
Installation options: | |
1. Official Python (Recommended): | |
Download from: https://www.python.org/downloads/macos/ | |
2. Homebrew (if installed): | |
brew install python3 | |
3. Xcode Command Line Tools: | |
xcode-select --install | |
After installation, restart this application." | |
exit 1 | |
fi | |
# Check if we're in a virtual environment, if not try to create one | |
if [[ -z "$VIRTUAL_ENV" ]]; then | |
VENV_DIR="$HOME/.safetymaster_venv" | |
if [[ ! -d "$VENV_DIR" ]]; then | |
# Show progress dialog | |
osascript -e 'display dialog "Setting up SafetyMaster Pro for first use...\\n\\nThis may take a few minutes." with title "SafetyMaster Pro - Setup" buttons {"OK"} default button "OK" with icon note giving up after 3' | |
"$PYTHON_CMD" -m venv "$VENV_DIR" || { | |
echo "Warning: Could not create virtual environment, using system Python" | |
} | |
fi | |
if [[ -d "$VENV_DIR" ]]; then | |
source "$VENV_DIR/bin/activate" | |
PYTHON_CMD="python" | |
fi | |
fi | |
# Install/upgrade dependencies with progress indication | |
if [[ ! -f "$HOME/.safetymaster_deps_installed" ]]; then | |
osascript -e 'display dialog "Installing required dependencies...\\n\\nThis is a one-time setup." with title "SafetyMaster Pro - Installing" buttons {"OK"} default button "OK" with icon note giving up after 3' | |
"$PYTHON_CMD" -m pip install --upgrade pip setuptools wheel > /dev/null 2>&1 || true | |
# Install requirements with fallback options | |
if "$PYTHON_CMD" -m pip install -r requirements.txt > /dev/null 2>&1; then | |
touch "$HOME/.safetymaster_deps_installed" | |
elif "$PYTHON_CMD" -m pip install --user -r requirements.txt > /dev/null 2>&1; then | |
touch "$HOME/.safetymaster_deps_installed" | |
else | |
show_error "Failed to install required dependencies. | |
Please try installing manually: | |
1. Open Terminal | |
2. Run: pip3 install opencv-python ultralytics flask flask-socketio torch torchvision | |
Then restart SafetyMaster Pro." | |
exit 1 | |
fi | |
fi | |
# Install tkinter if not available (for GUI) | |
"$PYTHON_CMD" -c "import tkinter" 2>/dev/null || { | |
show_error "GUI components not available. | |
Please install tkinter: | |
1. If using Homebrew Python: brew install python-tk | |
2. If using system Python: Install from python.org | |
Then restart SafetyMaster Pro." | |
exit 1 | |
} | |
# Launch the GUI application | |
"$PYTHON_CMD" gui_launcher.py | |
''' | |
# Write the executable script | |
executable_path = os.path.join(macos_dir, "SafetyMasterPro") | |
with open(executable_path, 'w') as f: | |
f.write(executable_script) | |
# Make executable | |
os.chmod(executable_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) | |
# Create improved Info.plist | |
plist_data = { | |
'CFBundleExecutable': 'SafetyMasterPro', | |
'CFBundleIdentifier': 'com.safetymaster.pro', | |
'CFBundleName': 'SafetyMaster Pro', | |
'CFBundleDisplayName': 'SafetyMaster Pro', | |
'CFBundleVersion': '1.1.0', | |
'CFBundleShortVersionString': '1.1.0', | |
'CFBundlePackageType': 'APPL', | |
'CFBundleSignature': 'SMPR', | |
'LSMinimumSystemVersion': '10.14', | |
'LSRequiresNativeExecution': True, | |
'NSCameraUsageDescription': 'SafetyMaster Pro needs camera access to detect safety equipment and monitor workplace compliance in real-time.', | |
'NSHighResolutionCapable': True, | |
'LSApplicationCategoryType': 'public.app-category.business', | |
'NSRequiresAquaSystemAppearance': False, | |
'LSMultipleInstancesProhibited': True, | |
'NSSupportsAutomaticGraphicsSwitching': True, | |
'LSArchitecturePriority': ['arm64', 'x86_64'], | |
'NSAppTransportSecurity': { | |
'NSAllowsLocalNetworking': True, | |
'NSExceptionDomains': { | |
'localhost': { | |
'NSExceptionAllowsInsecureHTTPLoads': True | |
} | |
} | |
}, | |
'LSUIElement': False, # Show in Dock and App Switcher | |
'NSPrincipalClass': 'NSApplication' | |
} | |
# Write Info.plist | |
plist_path = os.path.join(contents_dir, "Info.plist") | |
with open(plist_path, 'wb') as f: | |
plistlib.dump(plist_data, f) | |
print(f"\nβ User-Friendly Mac app bundle created: {app_dir}") | |
print(f"π Size: {get_directory_size(app_dir):.1f} MB") | |
print(f"π― New Features:") | |
print(f" - Proper GUI interface with status window") | |
print(f" - Clear start/stop controls") | |
print(f" - Built-in dashboard launcher") | |
print(f" - User-friendly instructions and guidance") | |
print(f" - No more 'floating' background processes") | |
print(f" - Professional appearance in Dock and App Switcher") | |
print(f"\nπ User Experience:") | |
print(f" 1. Double-click app β GUI window opens") | |
print(f" 2. Click 'Start Monitoring' β System starts") | |
print(f" 3. Click 'Open Dashboard' β Browser opens") | |
print(f" 4. Clear status updates and controls") | |
print(f" 5. Proper app lifecycle management") | |
def get_directory_size(path): | |
"""Calculate directory size in MB.""" | |
total_size = 0 | |
for dirpath, dirnames, filenames in os.walk(path): | |
for filename in filenames: | |
filepath = os.path.join(dirpath, filename) | |
if os.path.exists(filepath): | |
total_size += os.path.getsize(filepath) | |
return total_size / (1024 * 1024) | |
if __name__ == "__main__": | |
create_user_friendly_mac_app() |