e2e1107df7
Lint & Test / test (push) Has been cancelled
- Replace URL-based image_source/url fields with image_asset_id/video_asset_id on StaticImagePictureSource and VideoCaptureSource (clean break, no migration) - Resolve asset IDs to file paths at runtime via AssetStore.get_file_path() - Add EntitySelect asset pickers for image/video in stream editor modal - Add notification sound configuration (global sound + per-app overrides) - Unify per-app color and sound overrides into single "Per-App Overrides" section - Persist notification history between server restarts - Add asset management system (upload, edit, delete, soft-delete) - Replace emoji buttons with SVG icons throughout UI - Various backend improvements: SQLite stores, auth, backup, MQTT, webhooks
81 lines
2.7 KiB
Python
81 lines
2.7 KiB
Python
"""Asset data model.
|
|
|
|
An Asset represents an uploaded file (sound, image, video, or other)
|
|
stored on the server. Assets are referenced by ID from other entities
|
|
(e.g. NotificationColorStripSource uses sound assets for alert sounds).
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
|
|
# Map MIME type prefixes to asset_type categories
|
|
_MIME_TO_ASSET_TYPE = {
|
|
"audio/": "sound",
|
|
"image/": "image",
|
|
"video/": "video",
|
|
}
|
|
|
|
|
|
def asset_type_from_mime(mime_type: str) -> str:
|
|
"""Derive asset_type from a MIME type string."""
|
|
for prefix, asset_type in _MIME_TO_ASSET_TYPE.items():
|
|
if mime_type.startswith(prefix):
|
|
return asset_type
|
|
return "other"
|
|
|
|
|
|
@dataclass
|
|
class Asset:
|
|
"""Persistent metadata for an uploaded file asset."""
|
|
|
|
id: str
|
|
name: str
|
|
filename: str # original upload filename
|
|
stored_filename: str # on-disk filename (uuid-based)
|
|
mime_type: str # e.g. "audio/wav", "image/png"
|
|
asset_type: str # "sound" | "image" | "video" | "other"
|
|
size_bytes: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
description: Optional[str] = None
|
|
tags: List[str] = field(default_factory=list)
|
|
prebuilt: bool = False # True for shipped assets
|
|
deleted: bool = False # soft-delete for prebuilt assets
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"filename": self.filename,
|
|
"stored_filename": self.stored_filename,
|
|
"mime_type": self.mime_type,
|
|
"asset_type": self.asset_type,
|
|
"size_bytes": self.size_bytes,
|
|
"description": self.description,
|
|
"tags": self.tags,
|
|
"prebuilt": self.prebuilt,
|
|
"deleted": self.deleted,
|
|
"created_at": self.created_at.isoformat(),
|
|
"updated_at": self.updated_at.isoformat(),
|
|
}
|
|
|
|
@staticmethod
|
|
def from_dict(data: dict) -> "Asset":
|
|
return Asset(
|
|
id=data["id"],
|
|
name=data["name"],
|
|
filename=data.get("filename", ""),
|
|
stored_filename=data.get("stored_filename", ""),
|
|
mime_type=data.get("mime_type", "application/octet-stream"),
|
|
asset_type=data.get("asset_type", "other"),
|
|
size_bytes=int(data.get("size_bytes", 0)),
|
|
description=data.get("description"),
|
|
tags=data.get("tags", []),
|
|
prebuilt=bool(data.get("prebuilt", False)),
|
|
deleted=bool(data.get("deleted", False)),
|
|
created_at=datetime.fromisoformat(data["created_at"]),
|
|
updated_at=datetime.fromisoformat(data["updated_at"]),
|
|
)
|