Add multi-user server sync (PHP server + client)
Phase 2 implementation: Multiple streamers can now merge their captions into a single stream using a PHP server. PHP Server (server/php/): - server.php: API endpoint for sending/streaming transcriptions - display.php: Web page for viewing merged captions in OBS - config.php: Server configuration - .htaccess: Security settings - README.md: Comprehensive deployment guide Features: - Room-based isolation (multiple groups on same server) - Passphrase authentication per room - Real-time streaming via Server-Sent Events (SSE) - Different colors for each user - File-based storage (no database required) - Auto-cleanup of old rooms - Works on standard PHP hosting Client-Side: - client/server_sync.py: HTTP client for sending to PHP server - Settings dialog updated with server sync options - Config updated with server_sync section Server Configuration: - URL: Server endpoint (e.g., http://example.com/transcription/server.php) - Room: Unique room name for your group - Passphrase: Shared secret for authentication OBS Integration: Display URL format: http://example.com/transcription/display.php?room=ROOM&passphrase=PASS&fade=10×tamps=true NOTE: Main window integration pending (client sends transcriptions) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
163
client/server_sync.py
Normal file
163
client/server_sync.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""Server sync client for multi-user transcription."""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import threading
|
||||
import queue
|
||||
|
||||
|
||||
class ServerSyncClient:
|
||||
"""Client for syncing transcriptions to a PHP server."""
|
||||
|
||||
def __init__(self, url: str, room: str, passphrase: str, user_name: str):
|
||||
"""
|
||||
Initialize server sync client.
|
||||
|
||||
Args:
|
||||
url: Server URL (e.g., http://example.com/transcription/server.php)
|
||||
room: Room name
|
||||
passphrase: Room passphrase
|
||||
user_name: User's display name
|
||||
"""
|
||||
self.url = url
|
||||
self.room = room
|
||||
self.passphrase = passphrase
|
||||
self.user_name = user_name
|
||||
|
||||
# Queue for sending transcriptions asynchronously
|
||||
self.send_queue = queue.Queue()
|
||||
self.is_running = False
|
||||
self.send_thread: Optional[threading.Thread] = None
|
||||
|
||||
# Statistics
|
||||
self.sent_count = 0
|
||||
self.error_count = 0
|
||||
self.last_error: Optional[str] = None
|
||||
|
||||
def start(self):
|
||||
"""Start the sync client."""
|
||||
if self.is_running:
|
||||
return
|
||||
|
||||
self.is_running = True
|
||||
self.send_thread = threading.Thread(target=self._send_loop, daemon=True)
|
||||
self.send_thread.start()
|
||||
print(f"Server sync started: room={self.room}")
|
||||
|
||||
def stop(self):
|
||||
"""Stop the sync client."""
|
||||
self.is_running = False
|
||||
if self.send_thread:
|
||||
self.send_thread.join(timeout=2.0)
|
||||
print("Server sync stopped")
|
||||
|
||||
def send_transcription(self, text: str, timestamp: Optional[datetime] = None):
|
||||
"""
|
||||
Send a transcription to the server (non-blocking).
|
||||
|
||||
Args:
|
||||
text: Transcription text
|
||||
timestamp: Timestamp (defaults to now)
|
||||
"""
|
||||
if timestamp is None:
|
||||
timestamp = datetime.now()
|
||||
|
||||
# Add to queue
|
||||
self.send_queue.put({
|
||||
'text': text,
|
||||
'timestamp': timestamp.strftime("%H:%M:%S")
|
||||
})
|
||||
|
||||
def _send_loop(self):
|
||||
"""Background thread for sending transcriptions."""
|
||||
while self.is_running:
|
||||
try:
|
||||
# Get transcription from queue (with timeout)
|
||||
try:
|
||||
trans_data = self.send_queue.get(timeout=1.0)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
# Send to server
|
||||
self._send_to_server(trans_data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in server sync send loop: {e}")
|
||||
self.error_count += 1
|
||||
self.last_error = str(e)
|
||||
|
||||
def _send_to_server(self, trans_data: dict):
|
||||
"""
|
||||
Send a transcription to the PHP server.
|
||||
|
||||
Args:
|
||||
trans_data: Dictionary with 'text' and 'timestamp'
|
||||
"""
|
||||
try:
|
||||
# Prepare payload
|
||||
payload = {
|
||||
'room': self.room,
|
||||
'passphrase': self.passphrase,
|
||||
'user_name': self.user_name,
|
||||
'text': trans_data['text'],
|
||||
'timestamp': trans_data['timestamp']
|
||||
}
|
||||
|
||||
# Send POST request
|
||||
response = requests.post(
|
||||
self.url,
|
||||
params={'action': 'send'},
|
||||
json=payload,
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
# Check response
|
||||
if response.status_code == 200:
|
||||
self.sent_count += 1
|
||||
self.last_error = None
|
||||
else:
|
||||
error_msg = f"Server returned {response.status_code}"
|
||||
try:
|
||||
error_data = response.json()
|
||||
if 'error' in error_data:
|
||||
error_msg = error_data['error']
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"Server sync error: {error_msg}")
|
||||
self.error_count += 1
|
||||
self.last_error = error_msg
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
print("Server sync timeout")
|
||||
self.error_count += 1
|
||||
self.last_error = "Request timeout"
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("Server sync connection error")
|
||||
self.error_count += 1
|
||||
self.last_error = "Connection error"
|
||||
|
||||
except Exception as e:
|
||||
print(f"Server sync error: {e}")
|
||||
self.error_count += 1
|
||||
self.last_error = str(e)
|
||||
|
||||
def get_stats(self) -> dict:
|
||||
"""Get sync statistics."""
|
||||
return {
|
||||
'sent': self.sent_count,
|
||||
'errors': self.error_count,
|
||||
'last_error': self.last_error,
|
||||
'queue_size': self.send_queue.qsize()
|
||||
}
|
||||
|
||||
def is_healthy(self) -> bool:
|
||||
"""Check if sync is working (no recent errors)."""
|
||||
# Consider healthy if less than 10% error rate
|
||||
total = self.sent_count + self.error_count
|
||||
if total == 0:
|
||||
return True
|
||||
return (self.error_count / total) < 0.1
|
||||
Reference in New Issue
Block a user