55 lines
2.1 KiB
Python
55 lines
2.1 KiB
Python
"""Explicit local-file input adapter with safe error reporting."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
|
|
from yovision_brain.config import BrainInputConfig
|
|
|
|
from .models import CancellationToken, InputError, InputPacket
|
|
|
|
|
|
class LocalFileInput:
|
|
def __init__(self, config: BrainInputConfig) -> None:
|
|
if config.source.kind != "local_file" or config.source.path is None:
|
|
raise ValueError("LocalFileInput requires a local_file source config")
|
|
self._config = config
|
|
self._path = config.source.path
|
|
|
|
@property
|
|
def source_label(self) -> str:
|
|
"""Return a safe label rather than exposing the internal absolute path."""
|
|
return self._path.name
|
|
|
|
def packets(self, cancellation: CancellationToken | None = None) -> Iterator[InputPacket]:
|
|
profile = self._config.profile
|
|
source = self._config.source
|
|
try:
|
|
stream = self._path.open("rb")
|
|
except FileNotFoundError as exc:
|
|
raise InputError(f"local video source not found: {self.source_label}") from exc
|
|
except OSError as exc:
|
|
raise InputError(f"local video source cannot be opened: {self.source_label}: {exc.strerror}") from exc
|
|
|
|
with stream:
|
|
sequence = 0
|
|
while cancellation is None or not cancellation.cancelled:
|
|
try:
|
|
payload = stream.read(source.chunk_size)
|
|
except OSError as exc:
|
|
raise InputError(f"local video source read failed: {self.source_label}: {exc.strerror}") from exc
|
|
if not payload:
|
|
return
|
|
yield InputPacket(
|
|
sequence=sequence,
|
|
timestamp_ns=None,
|
|
logical_device_id=self._config.logical_device_id,
|
|
profile_id=profile.profile_id,
|
|
width=profile.width,
|
|
height=profile.height,
|
|
media_format="container-bytes",
|
|
payload=payload,
|
|
)
|
|
sequence += 1
|