mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-21 04:39:06 +00:00
* llm instructions * vibe dailyco * vibe dailyco * doc update (vibe) * dont show recording ui on call * stub processor (vibe) * stub processor (vibe) self-review * stub processor (vibe) self-review * chore(main): release 0.14.0 (#670) * Add multitrack pipeline * Mixdown audio tracks * Mixdown with pyav filter graph * Trigger multitrack processing for daily recordings * apply platform from envs in priority: non-dry * Use explicit track keys for processing * Align tracks of a multitrack recording * Generate waveforms for the mixed audio * Emit multriack pipeline events * Fix multitrack pipeline track alignment * dailico docs * Enable multitrack reprocessing * modal temp files uniform names, cleanup. remove llm temporary docs * docs cleanup * dont proceed with raw recordings if any of the downloads fail * dry transcription pipelines * remove is_miltitrack * comments * explicit dailyco room name * docs * remove stub data/method * frontend daily/whereby code self-review (no-mistake) * frontend daily/whereby code self-review (no-mistakes) * frontend daily/whereby code self-review (no-mistakes) * consent cleanup for multitrack (no-mistakes) * llm fun * remove extra comments * fix tests * merge migrations * Store participant names * Get participants by meeting session id * pop back main branch migration * s3 paddington (no-mistakes) * comment * pr comments * pr comments * pr comments * platform / meeting cleanup * Use participant names in summary generation * platform assignment to meeting at controller level * pr comment * room playform properly default none * room playform properly default none * restore migration lost * streaming WIP * extract storage / use common storage / proper env vars for storage * fix mocks tests * remove fall back * streaming for multifile * cenrtal storage abstraction (no-mistakes) * remove dead code / vars * Set participant user id for authenticated users * whereby recording name parsing fix * whereby recording name parsing fix * more file stream * storage dry + tests * remove homemade boto3 streaming and use proper boto * update migration guide * webhook creation script - print uuid --------- Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com> Co-authored-by: Mathieu Virbel <mat@meltingrocks.com> Co-authored-by: Sergey Mankovsky <sergey@monadical.com>
162 lines
5.1 KiB
Python
162 lines
5.1 KiB
Python
import importlib
|
|
from typing import BinaryIO, Union
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from reflector.settings import settings
|
|
|
|
|
|
class StorageError(Exception):
|
|
"""Base exception for storage operations."""
|
|
|
|
pass
|
|
|
|
|
|
class StoragePermissionError(StorageError):
|
|
"""Exception raised when storage operation fails due to permission issues."""
|
|
|
|
pass
|
|
|
|
|
|
class FileResult(BaseModel):
|
|
filename: str
|
|
url: str
|
|
|
|
|
|
class Storage:
|
|
_registry = {}
|
|
CONFIG_SETTINGS = []
|
|
|
|
@classmethod
|
|
def register(cls, name, kclass):
|
|
cls._registry[name] = kclass
|
|
|
|
@classmethod
|
|
def get_instance(cls, name: str, settings_prefix: str = ""):
|
|
if name not in cls._registry:
|
|
module_name = f"reflector.storage.storage_{name}"
|
|
importlib.import_module(module_name)
|
|
|
|
# gather specific configuration for the processor
|
|
# search `TRANSCRIPT_BACKEND_XXX_YYY`, push to constructor as `backend_xxx_yyy`
|
|
config = {}
|
|
name_upper = name.upper()
|
|
config_prefix = f"{settings_prefix}{name_upper}_"
|
|
for key, value in settings:
|
|
if key.startswith(config_prefix):
|
|
config_name = key[len(settings_prefix) :].lower()
|
|
config[config_name] = value
|
|
|
|
return cls._registry[name](**config)
|
|
|
|
# Credential properties for API passthrough
|
|
@property
|
|
def bucket_name(self) -> str:
|
|
"""Default bucket name for this storage instance."""
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def region(self) -> str:
|
|
"""AWS region for this storage instance."""
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def access_key_id(self) -> str | None:
|
|
"""AWS access key ID (None for role-based auth). Prefer key_credentials property."""
|
|
return None
|
|
|
|
@property
|
|
def secret_access_key(self) -> str | None:
|
|
"""AWS secret access key (None for role-based auth). Prefer key_credentials property."""
|
|
return None
|
|
|
|
@property
|
|
def role_arn(self) -> str | None:
|
|
"""AWS IAM role ARN for role-based auth (None for key-based auth). Prefer role_credential property."""
|
|
return None
|
|
|
|
@property
|
|
def key_credentials(self) -> tuple[str, str]:
|
|
"""
|
|
Get (access_key_id, secret_access_key) for key-based auth.
|
|
Raises ValueError if storage uses IAM role instead.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def role_credential(self) -> str:
|
|
"""
|
|
Get IAM role ARN for role-based auth.
|
|
Raises ValueError if storage uses access keys instead.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
async def put_file(
|
|
self, filename: str, data: Union[bytes, BinaryIO], *, bucket: str | None = None
|
|
) -> FileResult:
|
|
"""Upload data. bucket: override instance default if provided."""
|
|
return await self._put_file(filename, data, bucket=bucket)
|
|
|
|
async def _put_file(
|
|
self, filename: str, data: Union[bytes, BinaryIO], *, bucket: str | None = None
|
|
) -> FileResult:
|
|
raise NotImplementedError
|
|
|
|
async def delete_file(self, filename: str, *, bucket: str | None = None):
|
|
"""Delete file. bucket: override instance default if provided."""
|
|
return await self._delete_file(filename, bucket=bucket)
|
|
|
|
async def _delete_file(self, filename: str, *, bucket: str | None = None):
|
|
raise NotImplementedError
|
|
|
|
async def get_file_url(
|
|
self,
|
|
filename: str,
|
|
operation: str = "get_object",
|
|
expires_in: int = 3600,
|
|
*,
|
|
bucket: str | None = None,
|
|
) -> str:
|
|
"""Generate presigned URL. bucket: override instance default if provided."""
|
|
return await self._get_file_url(filename, operation, expires_in, bucket=bucket)
|
|
|
|
async def _get_file_url(
|
|
self,
|
|
filename: str,
|
|
operation: str = "get_object",
|
|
expires_in: int = 3600,
|
|
*,
|
|
bucket: str | None = None,
|
|
) -> str:
|
|
raise NotImplementedError
|
|
|
|
async def get_file(self, filename: str, *, bucket: str | None = None):
|
|
"""Download file. bucket: override instance default if provided."""
|
|
return await self._get_file(filename, bucket=bucket)
|
|
|
|
async def _get_file(self, filename: str, *, bucket: str | None = None):
|
|
raise NotImplementedError
|
|
|
|
async def list_objects(
|
|
self, prefix: str = "", *, bucket: str | None = None
|
|
) -> list[str]:
|
|
"""List object keys. bucket: override instance default if provided."""
|
|
return await self._list_objects(prefix, bucket=bucket)
|
|
|
|
async def _list_objects(
|
|
self, prefix: str = "", *, bucket: str | None = None
|
|
) -> list[str]:
|
|
raise NotImplementedError
|
|
|
|
async def stream_to_fileobj(
|
|
self, filename: str, fileobj: BinaryIO, *, bucket: str | None = None
|
|
):
|
|
"""Stream file directly to file object without loading into memory.
|
|
bucket: override instance default if provided."""
|
|
return await self._stream_to_fileobj(filename, fileobj, bucket=bucket)
|
|
|
|
async def _stream_to_fileobj(
|
|
self, filename: str, fileobj: BinaryIO, *, bucket: str | None = None
|
|
):
|
|
raise NotImplementedError
|