feat: add save frame if there is sessionId
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build Worker Base and Application Images / check-base-changes (push) Successful in 8s
				
			
		
			
				
	
				Build Worker Base and Application Images / build-base (push) Successful in 5m23s
				
			
		
			
				
	
				Build Worker Base and Application Images / build-docker (push) Successful in 3m11s
				
			
		
			
				
	
				Build Worker Base and Application Images / deploy-stack (push) Successful in 23s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build Worker Base and Application Images / check-base-changes (push) Successful in 8s
				
			Build Worker Base and Application Images / build-base (push) Successful in 5m23s
				
			Build Worker Base and Application Images / build-docker (push) Successful in 3m11s
				
			Build Worker Base and Application Images / deploy-stack (push) Successful in 23s
				
			This commit is contained in:
		
							parent
							
								
									965a0d0a72
								
							
						
					
					
						commit
						2eba1f94ea
					
				
					 2 changed files with 95 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -20,5 +20,9 @@ RUN pip install --no-cache-dir -r requirements.base.txt
 | 
			
		|||
# Set working directory
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# Create images directory for bind mount
 | 
			
		||||
RUN mkdir -p /app/images && \
 | 
			
		||||
    chmod 755 /app/images
 | 
			
		||||
 | 
			
		||||
# This base image will be reused for all worker builds
 | 
			
		||||
CMD ["python3", "-m", "fastapi", "run", "--host", "0.0.0.0", "--port", "8000"]
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,10 @@ WebSocket message handling and protocol implementation.
 | 
			
		|||
import asyncio
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import cv2
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Optional
 | 
			
		||||
from fastapi import WebSocket, WebSocketDisconnect
 | 
			
		||||
from websockets.exceptions import ConnectionClosedError
 | 
			
		||||
| 
						 | 
				
			
			@ -447,6 +451,89 @@ class WebSocketHandler:
 | 
			
		|||
            logger.error(f"[Model Management] Exception ensuring model {model_id}: {str(e)}", exc_info=True)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    async def _save_snapshot(self, display_identifier: str, session_id: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Save snapshot image to images folder after receiving sessionId.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            display_identifier: Display identifier to match with subscriptionIdentifier
 | 
			
		||||
            session_id: Session ID to include in filename
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # Find subscription that matches the displayIdentifier
 | 
			
		||||
            matching_subscription = None
 | 
			
		||||
            for subscription in worker_state.get_all_subscriptions():
 | 
			
		||||
                # Extract display ID from subscriptionIdentifier (format: displayId;cameraId)
 | 
			
		||||
                from .messages import extract_display_identifier
 | 
			
		||||
                sub_display_id = extract_display_identifier(subscription.subscriptionIdentifier)
 | 
			
		||||
                if sub_display_id == display_identifier:
 | 
			
		||||
                    matching_subscription = subscription
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            if not matching_subscription:
 | 
			
		||||
                logger.error(f"[Snapshot Save] No subscription found for display {display_identifier}")
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            if not matching_subscription.snapshotUrl:
 | 
			
		||||
                logger.error(f"[Snapshot Save] No snapshotUrl found for display {display_identifier}")
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            # Ensure images directory exists (relative path for Docker bind mount)
 | 
			
		||||
            images_dir = Path("images")
 | 
			
		||||
            images_dir.mkdir(exist_ok=True)
 | 
			
		||||
 | 
			
		||||
            # Generate filename with timestamp and session ID
 | 
			
		||||
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
 | 
			
		||||
            filename = f"{display_identifier}_{session_id}_{timestamp}.jpg"
 | 
			
		||||
            filepath = images_dir / filename
 | 
			
		||||
 | 
			
		||||
            # Use existing HTTPSnapshotReader to fetch snapshot
 | 
			
		||||
            logger.info(f"[Snapshot Save] Fetching snapshot from {matching_subscription.snapshotUrl}")
 | 
			
		||||
 | 
			
		||||
            # Run snapshot fetch in thread pool to avoid blocking async loop
 | 
			
		||||
            loop = asyncio.get_event_loop()
 | 
			
		||||
            frame = await loop.run_in_executor(None, self._fetch_snapshot_sync, matching_subscription.snapshotUrl)
 | 
			
		||||
 | 
			
		||||
            if frame is not None:
 | 
			
		||||
                # Save the image using OpenCV
 | 
			
		||||
                success = cv2.imwrite(str(filepath), frame)
 | 
			
		||||
                if success:
 | 
			
		||||
                    logger.info(f"[Snapshot Save] Successfully saved snapshot to {filepath}")
 | 
			
		||||
                else:
 | 
			
		||||
                    logger.error(f"[Snapshot Save] Failed to save image file {filepath}")
 | 
			
		||||
            else:
 | 
			
		||||
                logger.error(f"[Snapshot Save] Failed to fetch snapshot from {matching_subscription.snapshotUrl}")
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"[Snapshot Save] Error saving snapshot for display {display_identifier}: {e}", exc_info=True)
 | 
			
		||||
 | 
			
		||||
    def _fetch_snapshot_sync(self, snapshot_url: str):
 | 
			
		||||
        """
 | 
			
		||||
        Synchronous snapshot fetching using existing HTTPSnapshotReader infrastructure.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            snapshot_url: URL to fetch snapshot from
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            np.ndarray or None: Fetched frame or None on error
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            from ..streaming.readers import HTTPSnapshotReader
 | 
			
		||||
 | 
			
		||||
            # Create temporary snapshot reader for single fetch
 | 
			
		||||
            snapshot_reader = HTTPSnapshotReader(
 | 
			
		||||
                camera_id="temp_snapshot",
 | 
			
		||||
                snapshot_url=snapshot_url,
 | 
			
		||||
                interval_ms=5000  # Not used for single fetch
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Use existing fetch_single_snapshot method
 | 
			
		||||
            return snapshot_reader.fetch_single_snapshot()
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(f"Error in sync snapshot fetch: {e}")
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    async def _handle_set_session_id(self, message: SetSessionIdMessage) -> None:
 | 
			
		||||
        """Handle setSessionId message."""
 | 
			
		||||
        display_identifier = message.payload.displayIdentifier
 | 
			
		||||
| 
						 | 
				
			
			@ -460,6 +547,10 @@ class WebSocketHandler:
 | 
			
		|||
        # Update tracking integrations with session ID
 | 
			
		||||
        shared_stream_manager.set_session_id(display_identifier, session_id)
 | 
			
		||||
 | 
			
		||||
        # Save snapshot image after getting sessionId
 | 
			
		||||
        if session_id:
 | 
			
		||||
            await self._save_snapshot(display_identifier, session_id)
 | 
			
		||||
 | 
			
		||||
    async def _handle_set_progression_stage(self, message: SetProgressionStageMessage) -> None:
 | 
			
		||||
        """Handle setProgressionStage message."""
 | 
			
		||||
        display_identifier = message.payload.displayIdentifier
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue