diff --git a/squashkiwi-streaming/.gitignore b/squashkiwi-streaming/.gitignore deleted file mode 100644 index 58786aa..0000000 --- a/squashkiwi-streaming/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ - -# Playwright -node_modules/ -/test-results/ -/playwright-report/ -/blob-report/ -/playwright/.cache/ diff --git a/squashkiwi-streaming/_paths.sh b/squashkiwi-streaming/_paths.sh deleted file mode 100755 index ac173ae..0000000 --- a/squashkiwi-streaming/_paths.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# Path configuration for squashkiwi-streaming - -# get_squashkiwi_streaming_paths() { -# echo "${LOCAL_DATA_FOLDER}/config" -# echo "${LOCAL_DATA_FOLDER}/recordings" -# echo "${LOCAL_DATA_FOLDER}/config/overlay" -# echo "${LOCAL_DATA_FOLDER}/config/web" -# } - - -get_squashkiwi_streaming_paths() { - echo "path:localpath:${LOCAL_DATA_FOLDER}" -} \ No newline at end of file diff --git a/squashkiwi-streaming/backup.sh b/squashkiwi-streaming/backup.sh deleted file mode 100755 index f5dd984..0000000 --- a/squashkiwi-streaming/backup.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# shellcheck disable=SC1091 -source "${AGENT_PATH}/common.sh" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/_paths.sh" -_check_required_env_vars "PROJECT_NAME" "LOCAL_DATA_FOLDER" "BACKUP_FILE" "TEMP_DIR" - -# shellcheck disable=SC2046 -backup_items $(get_squashkiwi_streaming_paths) || _die "Failed to create backup" - -echo "Backup created successfully (recordings excluded)" diff --git a/squashkiwi-streaming/config/.template_info.env b/squashkiwi-streaming/config/.template_info.env deleted file mode 100644 index 68645a6..0000000 --- a/squashkiwi-streaming/config/.template_info.env +++ /dev/null @@ -1,16 +0,0 @@ -# DO NOT EDIT THIS FILE FOR YOUR SERVICE! -# This file is replaced from the template whenever there is an update. -# Edit the service.env file to make changes. - -# Template to use - always required! -TEMPLATE=squashkiwi-streaming -REQUIRES_HOST_ROOT=false -REQUIRES_DOCKER=true -REQUIRES_DOCKER_ROOT=false - -# Application settings -MEDIAMTX_PORT=8554 -HLS_PORT=8888 -WEBRTC_PORT=8889 -WEB_PORT=80 - diff --git a/squashkiwi-streaming/config/docker-compose.yml b/squashkiwi-streaming/config/docker-compose.yml deleted file mode 100644 index 5748fff..0000000 --- a/squashkiwi-streaming/config/docker-compose.yml +++ /dev/null @@ -1,150 +0,0 @@ -services: - # MediaMTX - RTSP/HLS/WebRTC streaming server - mediamtx: - image: bluenviron/mediamtx:latest - container_name: ${PROJECT_NAME}-mediamtx - restart: unless-stopped - network_mode: host - volumes: - - ./mediamtx.yml:/mediamtx.yml - - ${RECORDINGS_FOLDER}:/recordings - environment: - MTX_PROTOCOLS: tcp - # Main stream configuration - MTX_PATHS_COURT_MAIN_SOURCE: ${MTX_PATHS_COURT_MAIN_SOURCE} - MTX_PATHS_COURT_MAIN_SOURCEPROTOCOL: tcp - MTX_PATHS_COURT_MAIN_RECORD: "yes" - MTX_PATHS_COURT_MAIN_RECORDPATH: "/recordings/%path/%Y-%m-%d_%H-%M-%S-%f.mp4" - MTX_PATHS_COURT_MAIN_RECORDFORMAT: fmp4 - MTX_PATHS_COURT_MAIN_RECORDSEGMENTDURATION: 1h - MTX_PATHS_COURT_MAIN_RECORDDELETEAFTER: 24h - # Sub stream configuration (usually H264) - MTX_PATHS_COURT_SUB_SOURCE: ${MTX_PATHS_COURT_SUB_SOURCE} - MTX_PATHS_COURT_SUB_SOURCEPROTOCOL: tcp - # Legacy court path - original H265 stream - MTX_PATHS_COURT_SOURCE: ${MTX_PATHS_COURT_SOURCE} - MTX_PATHS_COURT_SOURCEPROTOCOL: tcp - # Force all paths to start immediately - MTX_PATHDEFAULTS_SOURCEONDEMAND: "no" - # Authentication disabled for testing - # MTX_PATHDEFAULTS_PUBLISHUSER: ${MEDIAMTX_USER} - # MTX_PATHDEFAULTS_PUBLISHPASS: ${MEDIAMTX_PASS} - # MTX_PATHDEFAULTS_READUSER: ${MEDIAMTX_USER} - # MTX_PATHDEFAULTS_READPASS: ${MEDIAMTX_PASS} - healthcheck: - test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9997/v2/paths/list"] - interval: 30s - timeout: 10s - retries: 3 - - # FFmpeg transcoder for H265 to H264 with score overlay - transcoder: - build: ./transcoder - image: ${PROJECT_NAME}-transcoder:latest - container_name: ${PROJECT_NAME}-transcoder - restart: unless-stopped - network_mode: host - volumes: - - score-data:/tmp:rw - depends_on: - - mediamtx - - # Score overlay and recording service - overlay-service: - build: ./overlay - image: ${PROJECT_NAME}-overlay:latest - container_name: ${PROJECT_NAME}-overlay - restart: unless-stopped - network_mode: host - depends_on: - - mediamtx - volumes: - - ${RECORDINGS_FOLDER}:/recordings - - score-data:/tmp:rw - environment: - - SQUASHKIWI_API=${SQUASHKIWI_API} - - CLUB_CODE=${CLUB_CODE} - - COURT_NUMBER=${COURT_NUMBER} - - COURT_NAME=${COURT_NAME} - - MEDIAMTX_API=http://localhost:9997 - - RECORDING_PATH=/recordings - - IDLE_TIMEOUT=${IDLE_TIMEOUT} - - RECORDING_RETENTION_DAYS=${RECORDING_RETENTION_DAYS} - healthcheck: - test: ["CMD", "pgrep", "-f", "overlay_service.py"] - interval: 30s - timeout: 10s - retries: 3 - - # Nginx web server - nginx: - image: nginx:alpine - container_name: ${PROJECT_NAME}-nginx - restart: unless-stopped - network_mode: host - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf:ro - - ./web:/usr/share/nginx/html:ro - - ${RECORDINGS_FOLDER}:/recordings:ro - - nginx-cache:/var/cache/nginx - depends_on: - - mediamtx - healthcheck: - test: ["CMD", "wget", "-q", "-O", "-", "http://localhost/health"] - interval: 30s - timeout: 10s - retries: 3 - - # Optional: Cloudflare tunnel - cloudflared: - image: cloudflare/cloudflared:latest - container_name: ${PROJECT_NAME}-tunnel - restart: unless-stopped - command: tunnel run - environment: - - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} - profiles: - - tunnel - - # Optional: Prometheus monitoring - prometheus: - image: prom/prometheus:latest - container_name: ${PROJECT_NAME}-prometheus - restart: unless-stopped - ports: - - "9090:9090" - volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - - prometheus-data:/prometheus - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - profiles: - - monitoring - - # Optional: Grafana - grafana: - image: grafana/grafana:latest - container_name: ${PROJECT_NAME}-grafana - restart: unless-stopped - ports: - - "3000:3000" - volumes: - - grafana-data:/var/lib/grafana - environment: - - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} - depends_on: - - prometheus - profiles: - - monitoring - -volumes: - nginx-cache: - prometheus-data: - grafana-data: - score-data: - -networks: - default: - name: ${PROJECT_NAME} - driver: bridge \ No newline at end of file diff --git a/squashkiwi-streaming/config/mediamtx.yml b/squashkiwi-streaming/config/mediamtx.yml deleted file mode 100644 index 645619c..0000000 --- a/squashkiwi-streaming/config/mediamtx.yml +++ /dev/null @@ -1,47 +0,0 @@ -# MediaMTX Configuration for SquashKiwi Streaming - -# General Settings -logLevel: info -logDestinations: [stdout] - -# API Configuration -api: yes -apiAddress: :9997 -apiAllowOrigin: '*' - -# Metrics -metrics: yes -metricsAddress: :9998 - -# RTSP Server -rtsp: yes -rtspAddress: :8554 -rtspTransports: [tcp, udp] - -# HLS Server -hls: yes -hlsAddress: :8888 -hlsAllowOrigin: '*' -hlsSegmentCount: 3 -hlsSegmentDuration: 1s -hlsPartDuration: 200ms -hlsAlwaysRemux: yes - -# WebRTC Server -webrtc: yes -webrtcAddress: :8889 -webrtcAllowOrigin: '*' - -# Path defaults -pathDefaults: - # Authentication disabled for testing - # readUser: stream - # readPass: squashkiwi - # publishUser: stream - # publishPass: squashkiwi - -# Path Configuration -paths: - # Transcoded H264 stream - court_h264: - # This path will receive the transcoded stream from ffmpeg \ No newline at end of file diff --git a/squashkiwi-streaming/config/nginx.conf b/squashkiwi-streaming/config/nginx.conf deleted file mode 100644 index 5b5d336..0000000 --- a/squashkiwi-streaming/config/nginx.conf +++ /dev/null @@ -1,120 +0,0 @@ -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - gzip on; - - # Cache settings for HLS - proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=hls_cache:10m max_size=1g inactive=60m use_temp_path=off; - - server { - listen 8880; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - # Health check - location /health { - access_log off; - return 200 "OK\n"; - add_header Content-Type text/plain; - } - - # Main web interface - location / { - try_files $uri $uri/ /index.html; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - } - - # HLS streams proxy - location /hls/ { - proxy_pass http://[::1]:8888/; - proxy_http_version 1.1; - - # Forward authentication headers - proxy_set_header Authorization $http_authorization; - - # CORS headers - add_header Access-Control-Allow-Origin *; - add_header Access-Control-Allow-Methods "GET, OPTIONS"; - add_header Access-Control-Allow-Headers "Range, Authorization"; - - # Cache HLS segments - proxy_cache hls_cache; - proxy_cache_valid 200 1m; - proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; - - proxy_buffering off; - proxy_request_buffering off; - } - - # WebRTC signaling (WHEP) - location /webrtc/ { - proxy_pass http://[::1]:8889/; - proxy_http_version 1.1; - - # CORS headers for WebRTC - add_header Access-Control-Allow-Origin * always; - add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PATCH, DELETE" always; - add_header Access-Control-Allow-Headers "Authorization, Content-Type, If-Match" always; - add_header Access-Control-Expose-Headers "Link" always; - - # Handle OPTIONS preflight - if ($request_method = OPTIONS) { - return 204; - } - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Authorization $http_authorization; - - proxy_buffering off; - proxy_request_buffering off; - proxy_read_timeout 86400; - proxy_send_timeout 86400; - } - - # MediaMTX API - location /api/mediamtx/ { - proxy_pass http://[::1]:9997/; - allow 127.0.0.1; - allow 172.16.0.0/12; - deny all; - } - - # Recordings - location /recordings/ { - alias /recordings/; - autoindex on; - autoindex_exact_size off; - autoindex_localtime on; - add_header Accept-Ranges bytes; - add_header Content-Disposition "inline"; - } - - # API endpoint for recordings list - location /api/recordings { - default_type application/json; - return 200 '{"recordings": []}'; - } - - # Metrics - location /metrics { - proxy_pass http://localhost:9998/metrics; - allow 127.0.0.1; - allow 172.16.0.0/12; - deny all; - } - } -} \ No newline at end of file diff --git a/squashkiwi-streaming/config/overlay/Dockerfile b/squashkiwi-streaming/config/overlay/Dockerfile deleted file mode 100644 index 4c3e0a9..0000000 --- a/squashkiwi-streaming/config/overlay/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM python:3.11-slim - -# Install FFmpeg and fonts -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - ffmpeg \ - fonts-dejavu-core \ - curl \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Copy requirements and install -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Copy application -COPY overlay_service.py . - -# Create recordings directory -RUN mkdir -p /recordings - -# Create user but don't switch to it - need root for shared volume -RUN useradd -m -s /bin/bash overlay && \ - chown -R overlay:overlay /app /recordings -# Running as root to access shared /tmp volume -# USER overlay - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD python -c "import sys; sys.exit(0)" - -CMD ["python", "-u", "overlay_service.py"] \ No newline at end of file diff --git a/squashkiwi-streaming/config/overlay/overlay_service.py b/squashkiwi-streaming/config/overlay/overlay_service.py deleted file mode 100644 index 919dff0..0000000 --- a/squashkiwi-streaming/config/overlay/overlay_service.py +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/env python3 -""" -SquashKiwi Score Overlay Service -Fetches scores from SquashKiwi API and manages recordings -""" - -import asyncio -import aiohttp -import json -import subprocess -import os -import time -import signal -import sys -from datetime import datetime, timedelta -from typing import Optional, Dict, Any -import logging - -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger(__name__) - -class ScoreOverlayService: - def __init__(self): - # Load configuration from environment - self.api_url = os.getenv('SQUASHKIWI_API', 'https://squash.kiwi/api') - self.club_code = os.getenv('CLUB_CODE', 'OTOG') - self.court_number = os.getenv('COURT_NUMBER', '1') - self.court_id = f"{self.club_code.lower()}{self.court_number}" - self.mediamtx_api = os.getenv('MEDIAMTX_API', 'http://localhost:9997') - self.recording_path = os.getenv('RECORDING_PATH', '/recordings') - - # State management - self.current_score = {"player1": 0, "player2": 0, "games": "0-0", "serving": None} - self.recording_process = None - self.recording_filename = None - self.last_score_change = time.time() - self.match_start_time = None - self.idle_timeout = int(os.getenv('IDLE_TIMEOUT', '300')) - - # Ensure recording directory exists - os.makedirs(self.recording_path, exist_ok=True) - - # Setup signal handlers - signal.signal(signal.SIGINT, self.signal_handler) - signal.signal(signal.SIGTERM, self.signal_handler) - - logger.info(f"Score overlay service initialized for court {self.court_id}") - - def signal_handler(self, signum, frame): - """Handle shutdown signals gracefully""" - logger.info(f"Received signal {signum}, shutting down...") - if self.recording_process: - self.stop_recording_sync() - sys.exit(0) - - async def fetch_score(self) -> Optional[Dict[str, Any]]: - """Fetch current score from SquashKiwi API""" - try: - timeout = aiohttp.ClientTimeout(total=5) - async with aiohttp.ClientSession(timeout=timeout) as session: - # Use format: https://squash.kiwi/api/otog2/score - url = f"{self.api_url}/{self.court_id}/score" - async with session.get(url) as resp: - if resp.status == 200: - data = await resp.json() - # Check if match is active - if data.get('match_active', False): - return data - else: - logger.debug(f"No active match on court {self.court_id}") - return None - else: - logger.warning(f"API returned status {resp.status}") - return None - except Exception as e: - logger.error(f"Error fetching score: {e}") - return None - - async def check_stream_health(self) -> bool: - """Check if the stream is healthy""" - try: - timeout = aiohttp.ClientTimeout(total=2) - async with aiohttp.ClientSession(timeout=timeout) as session: - url = f"{self.mediamtx_api}/v3/paths/list" - async with session.get(url) as resp: - if resp.status == 200: - data = await resp.json() - paths = data.get('items', []) - # Check if either court or court_h264 path exists (we're transcoding) - return any(p.get('name') in ['court', 'court_h264'] for p in paths) - return False - except Exception as e: - logger.error(f"Failed to check stream health: {e}") - return False - - def format_score_text(self) -> str: - """Format the score for overlay display""" - games = self.current_score.get('games', '0-0') - p1_score = self.current_score.get('player1', 0) - p2_score = self.current_score.get('player2', 0) - p1_name = self.current_score.get('player1_name', 'Player 1')[:15] - p2_name = self.current_score.get('player2_name', 'Player 2')[:15] - - serving = self.current_score.get('serving') - serve1 = ' •' if serving == 1 else '' - serve2 = ' •' if serving == 2 else '' - - return f"{games} | {p1_name}: {p1_score}{serve1} - {p2_name}: {p2_score}{serve2}" - - async def start_recording(self): - """Start recording the match""" - if self.recording_process and self.recording_process.poll() is None: - logger.debug("Recording already in progress") - return - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - court_folder = os.path.join(self.recording_path, self.court_id) - os.makedirs(court_folder, exist_ok=True) - - self.recording_filename = os.path.join(court_folder, f"match_{timestamp}.mp4") - self.match_start_time = datetime.now() - - logger.info(f"Starting recording: {self.recording_filename}") - - score_text = self.format_score_text() - - cmd = [ - 'ffmpeg', - '-y', - '-i', 'rtsp://localhost:8554/court', - '-vf', f"drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:" - f"text='{score_text}':fontcolor=white:fontsize=30:" - f"box=1:boxcolor=black@0.5:boxborderw=5:x=10:y=10:" - f"reload=1:textfile=/tmp/score.txt", - '-c:v', 'libx264', - '-preset', 'fast', - '-crf', '23', - '-c:a', 'aac', - '-b:a', '128k', - '-movflags', '+faststart', - self.recording_filename - ] - - with open('/tmp/score.txt', 'w') as f: - f.write(score_text) - - try: - self.recording_process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - logger.info(f"Recording process started with PID {self.recording_process.pid}") - except Exception as e: - logger.error(f"Failed to start recording: {e}") - self.recording_process = None - - async def stop_recording(self): - """Stop recording the match""" - if not self.recording_process or self.recording_process.poll() is not None: - logger.debug("No active recording to stop") - return - - logger.info("Stopping recording due to inactivity") - - try: - self.recording_process.send_signal(signal.SIGINT) - - try: - await asyncio.wait_for( - asyncio.create_subprocess_exec('wait', str(self.recording_process.pid)), - timeout=5.0 - ) - except asyncio.TimeoutError: - self.recording_process.kill() - logger.warning("Had to force kill recording process") - - if self.match_start_time: - duration = datetime.now() - self.match_start_time - logger.info(f"Recording duration: {duration}") - - metadata_file = self.recording_filename.replace('.mp4', '.json') - metadata = { - 'court_id': self.court_id, - 'start_time': self.match_start_time.isoformat(), - 'end_time': datetime.now().isoformat(), - 'duration': str(duration), - 'final_score': self.current_score - } - - try: - with open(metadata_file, 'w') as f: - json.dump(metadata, f, indent=2) - logger.info(f"Metadata saved to {metadata_file}") - except Exception as e: - logger.error(f"Failed to save metadata: {e}") - - except Exception as e: - logger.error(f"Error stopping recording: {e}") - finally: - self.recording_process = None - self.recording_filename = None - self.match_start_time = None - - def stop_recording_sync(self): - """Synchronous version of stop_recording for signal handler""" - if self.recording_process and self.recording_process.poll() is None: - logger.info("Stopping recording (sync)") - self.recording_process.terminate() - self.recording_process.wait(timeout=5) - - async def update_overlay_text(self): - """Update the overlay text file""" - score_text = self.format_score_text() - try: - with open('/tmp/score.txt', 'w') as f: - f.write(score_text) - except Exception as e: - logger.error(f"Failed to update overlay text: {e}") - - async def cleanup_old_recordings(self): - """Remove recordings older than retention period""" - retention_days = int(os.getenv('RECORDING_RETENTION_DAYS', '30')) - cutoff_date = datetime.now() - timedelta(days=retention_days) - - try: - for root, dirs, files in os.walk(self.recording_path): - for file in files: - if file.endswith('.mp4'): - file_path = os.path.join(root, file) - file_time = datetime.fromtimestamp(os.path.getmtime(file_path)) - if file_time < cutoff_date: - os.remove(file_path) - logger.info(f"Deleted old recording: {file}") - metadata_file = file_path.replace('.mp4', '.json') - if os.path.exists(metadata_file): - os.remove(metadata_file) - except Exception as e: - logger.error(f"Error during cleanup: {e}") - - async def main_loop(self): - """Main service loop""" - logger.info("Starting main service loop") - - # Initialize overlay text - court_name = os.getenv('COURT_NAME', f'Court {self.court_number}') - try: - # Create file with write permissions for all - with open('/tmp/score.txt', 'w') as f: - f.write(f"{court_name} - Waiting for match...") - os.chmod('/tmp/score.txt', 0o666) - except PermissionError: - # If file exists and we can't write, try to remove and recreate - try: - os.remove('/tmp/score.txt') - with open('/tmp/score.txt', 'w') as f: - f.write(f"{court_name} - Waiting for match...") - os.chmod('/tmp/score.txt', 0o666) - except: - logger.error("Cannot create score.txt file - check permissions") - - await self.cleanup_old_recordings() - last_cleanup = datetime.now() - - while True: - try: - stream_healthy = await self.check_stream_health() - if not stream_healthy: - logger.warning("Stream not healthy, waiting...") - await asyncio.sleep(10) - continue - - score = await self.fetch_score() - - if score: - if score != self.current_score: - self.current_score = score - self.last_score_change = time.time() - - logger.info(f"Score updated: {self.format_score_text()}") - await self.update_overlay_text() - - if not self.recording_process or self.recording_process.poll() is not None: - await self.start_recording() - - idle_time = time.time() - self.last_score_change - if self.recording_process and idle_time > self.idle_timeout: - logger.info(f"Match idle for {idle_time:.0f} seconds") - await self.stop_recording() - - else: - if self.recording_process: - logger.info("No active match detected") - await self.stop_recording() - - # Update overlay to show no active match - court_name = os.getenv('COURT_NAME', f'Court {self.court_number}') - with open('/tmp/score.txt', 'w') as f: - f.write(f"{court_name} - No active match") - - if datetime.now() - last_cleanup > timedelta(days=1): - await self.cleanup_old_recordings() - last_cleanup = datetime.now() - - await asyncio.sleep(1) - - except Exception as e: - logger.error(f"Error in main loop: {e}") - await asyncio.sleep(5) - - async def run(self): - """Run the service""" - logger.info("SquashKiwi Score Overlay Service starting...") - await self.main_loop() - -if __name__ == "__main__": - service = ScoreOverlayService() - try: - asyncio.run(service.run()) - except KeyboardInterrupt: - logger.info("Service stopped by user") - except Exception as e: - logger.error(f"Service crashed: {e}") - sys.exit(1) \ No newline at end of file diff --git a/squashkiwi-streaming/config/overlay/requirements.txt b/squashkiwi-streaming/config/overlay/requirements.txt deleted file mode 100644 index 5486541..0000000 --- a/squashkiwi-streaming/config/overlay/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -aiohttp==3.9.1 -python-dateutil==2.8.2 -tenacity==8.2.3 \ No newline at end of file diff --git a/squashkiwi-streaming/config/service.env b/squashkiwi-streaming/config/service.env deleted file mode 100644 index c7dd26f..0000000 --- a/squashkiwi-streaming/config/service.env +++ /dev/null @@ -1,49 +0,0 @@ -# SquashKiwi Streaming Configuration -# Edit this file to configure your streaming service - -# Data folders -LOCAL_DATA_FOLDER="/home/dropshell/squashkiwi-streaming-data" -RECORDINGS_FOLDER="/home/dropshell/squashkiwi-streaming-recordings" - -# Project Name used in docker-compose.yml, must be unique on the server. -PROJECT_NAME="sk-streaming" - -# Camera Configuration -# Note: If password contains special characters (! @ # $ & %), they will be automatically URL-encoded -# Or you can manually encode them: ! = %21, @ = %40, # = %23, $ = %24, & = %26 -CAMERA_IP=192.168.1.100 -CAMERA_USER=admin -CAMERA_PASSWORD=changeme -CAMERA_RTSP_PORT=554 - -# Court Configuration -# CLUB_CODE: Your club's code (e.g., OTOG for Otago, WELL for Wellington) -# COURT_NUMBER: The court number (1, 2, 3, etc.) -CLUB_CODE=OTOG -COURT_NUMBER=1 -COURT_NAME="Court 1" - -# SquashKiwi API -SQUASHKIWI_API=https://squash.kiwi/api - -# Recording Settings -RECORDING_RETENTION_DAYS=30 -RECORDING_QUALITY=high -IDLE_TIMEOUT=300 - -# Network Settings -HOST_PORT=8880 -PUBLIC_IP= - -# MediaMTX Authentication (optional - remove values to disable auth) -MEDIAMTX_USER= -MEDIAMTX_PASS= - -# SSH User automatically set on service creation. -SSH_USER="root" - -# Optional: Cloudflare Tunnel -CLOUDFLARE_TUNNEL_TOKEN= - -# Optional: Monitoring -GRAFANA_PASSWORD=admin diff --git a/squashkiwi-streaming/config/transcoder/Dockerfile b/squashkiwi-streaming/config/transcoder/Dockerfile deleted file mode 100644 index c877ee8..0000000 --- a/squashkiwi-streaming/config/transcoder/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM linuxserver/ffmpeg:latest - -# Install fonts for text overlay -RUN apt-get update && \ - apt-get install -y --no-install-recommends fonts-dejavu-core && \ - rm -rf /var/lib/apt/lists/* - -# Copy transcoder script -COPY transcoder.sh /transcoder.sh -RUN chmod +x /transcoder.sh - -# Create score file directory -RUN mkdir -p /tmp - -ENTRYPOINT ["/transcoder.sh"] \ No newline at end of file diff --git a/squashkiwi-streaming/config/transcoder/transcoder.sh b/squashkiwi-streaming/config/transcoder/transcoder.sh deleted file mode 100644 index 5da5433..0000000 --- a/squashkiwi-streaming/config/transcoder/transcoder.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# Transcoder with live score overlay for SquashKiwi streaming - -# Create initial score file if it doesn't exist -SCORE_FILE="/tmp/score.txt" -if [ ! -f "$SCORE_FILE" ]; then - echo "Waiting for match..." > "$SCORE_FILE" -fi - -# Start ffmpeg with drawtext filter for score overlay -exec ffmpeg \ - -rtsp_transport tcp \ - -i rtsp://localhost:8554/court \ - -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:\ -textfile='$SCORE_FILE':\ -reload=1:\ -fontcolor=white:\ -fontsize=24:\ -box=1:\ -boxcolor=black@0.7:\ -boxborderw=5:\ -x=10:\ -y=10" \ - -c:v libx264 \ - -preset ultrafast \ - -tune zerolatency \ - -g 30 \ - -keyint_min 30 \ - -b:v 2M \ - -maxrate 2M \ - -bufsize 1M \ - -f rtsp \ - -rtsp_transport tcp \ - rtsp://localhost:8554/court_h264 \ No newline at end of file diff --git a/squashkiwi-streaming/config/web/index.html b/squashkiwi-streaming/config/web/index.html deleted file mode 100644 index ec337a7..0000000 --- a/squashkiwi-streaming/config/web/index.html +++ /dev/null @@ -1,329 +0,0 @@ - - -
- - -This player uses WebRTC for lower latency streaming. It should work with H265/HEVC streams.
-