Chore: rename api->camera-management-api and web->management-dashboard-web-app; update compose, ignore, README references
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
"""
|
||||
Video Repository Implementations.
|
||||
|
||||
File system-based implementation of video repository interface.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import List, Optional, BinaryIO
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import aiofiles
|
||||
|
||||
from ..domain.interfaces import VideoRepository
|
||||
from ..domain.models import VideoFile, VideoFormat, VideoStatus, StreamRange
|
||||
from ...core.config import Config
|
||||
from ...storage.manager import StorageManager
|
||||
|
||||
|
||||
class FileSystemVideoRepository(VideoRepository):
|
||||
"""File system implementation of video repository"""
|
||||
|
||||
def __init__(self, config: Config, storage_manager: StorageManager):
|
||||
self.config = config
|
||||
self.storage_manager = storage_manager
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
async def get_by_id(self, file_id: str) -> Optional[VideoFile]:
|
||||
"""Get video file by ID"""
|
||||
try:
|
||||
# Get file info from storage manager
|
||||
file_info = self.storage_manager.get_file_info(file_id)
|
||||
if not file_info:
|
||||
return None
|
||||
|
||||
return self._convert_to_video_file(file_info)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting video by ID {file_id}: {e}")
|
||||
return None
|
||||
|
||||
async def get_by_camera(
|
||||
self,
|
||||
camera_name: str,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
limit: Optional[int] = None
|
||||
) -> List[VideoFile]:
|
||||
"""Get video files for a camera with optional filters"""
|
||||
try:
|
||||
# Use storage manager to get files
|
||||
files = self.storage_manager.get_recording_files(
|
||||
camera_name=camera_name,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return [self._convert_to_video_file(file_info) for file_info in files]
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting videos for camera {camera_name}: {e}")
|
||||
return []
|
||||
|
||||
async def get_all(
|
||||
self,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
limit: Optional[int] = None
|
||||
) -> List[VideoFile]:
|
||||
"""Get all video files with optional filters"""
|
||||
try:
|
||||
# Get files from all cameras
|
||||
files = self.storage_manager.get_recording_files(
|
||||
camera_name=None, # All cameras
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return [self._convert_to_video_file(file_info) for file_info in files]
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting all videos: {e}")
|
||||
return []
|
||||
|
||||
async def exists(self, file_id: str) -> bool:
|
||||
"""Check if video file exists"""
|
||||
try:
|
||||
video_file = await self.get_by_id(file_id)
|
||||
return video_file is not None and video_file.file_path.exists()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking if video exists {file_id}: {e}")
|
||||
return False
|
||||
|
||||
async def get_file_stream(self, video_file: VideoFile) -> BinaryIO:
|
||||
"""Get file stream for reading video data"""
|
||||
try:
|
||||
# Use aiofiles for async file operations
|
||||
return await aiofiles.open(video_file.file_path, 'rb')
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error opening file stream for {video_file.file_id}: {e}")
|
||||
raise
|
||||
|
||||
async def get_file_range(
|
||||
self,
|
||||
video_file: VideoFile,
|
||||
range_request: StreamRange
|
||||
) -> bytes:
|
||||
"""Get specific byte range from video file"""
|
||||
try:
|
||||
async with aiofiles.open(video_file.file_path, 'rb') as f:
|
||||
# Seek to start position
|
||||
await f.seek(range_request.start)
|
||||
|
||||
# Calculate how many bytes to read
|
||||
if range_request.end is not None:
|
||||
bytes_to_read = range_request.end - range_request.start + 1
|
||||
data = await f.read(bytes_to_read)
|
||||
else:
|
||||
# Read to end of file
|
||||
data = await f.read()
|
||||
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error reading file range for {video_file.file_id}: {e}")
|
||||
raise
|
||||
|
||||
def _convert_to_video_file(self, file_info: dict) -> VideoFile:
|
||||
"""Convert storage manager file info to VideoFile domain model"""
|
||||
try:
|
||||
file_path = Path(file_info["filename"])
|
||||
|
||||
# Determine video format from extension
|
||||
extension = file_path.suffix.lower().lstrip('.')
|
||||
if extension == 'avi':
|
||||
format = VideoFormat.AVI
|
||||
elif extension == 'mp4':
|
||||
format = VideoFormat.MP4
|
||||
elif extension == 'webm':
|
||||
format = VideoFormat.WEBM
|
||||
else:
|
||||
format = VideoFormat.AVI # Default fallback
|
||||
|
||||
# Parse status
|
||||
status_str = file_info.get("status", "unknown")
|
||||
try:
|
||||
status = VideoStatus(status_str)
|
||||
except ValueError:
|
||||
status = VideoStatus.UNKNOWN
|
||||
|
||||
# Parse timestamps
|
||||
start_time = None
|
||||
if file_info.get("start_time"):
|
||||
start_time = datetime.fromisoformat(file_info["start_time"])
|
||||
|
||||
end_time = None
|
||||
if file_info.get("end_time"):
|
||||
end_time = datetime.fromisoformat(file_info["end_time"])
|
||||
|
||||
created_at = start_time or datetime.now()
|
||||
|
||||
return VideoFile(
|
||||
file_id=file_info["file_id"],
|
||||
camera_name=file_info["camera_name"],
|
||||
filename=file_info["filename"],
|
||||
file_path=file_path,
|
||||
file_size_bytes=file_info.get("file_size_bytes", 0),
|
||||
created_at=created_at,
|
||||
status=status,
|
||||
format=format,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
machine_trigger=file_info.get("machine_trigger"),
|
||||
error_message=None # Could be added to storage manager later
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error converting file info to VideoFile: {e}")
|
||||
raise
|
||||
Reference in New Issue
Block a user