"""Source-backend value objects — framework-free. A *source* is a place tracks come from (a mounted folder, YouTube, an upload). Backends are driven adapters (``app.infrastructure.sources``); these are the shapes they speak in, and the ports they satisfy live in ``app.domain.ports``. The first backend, ``local``, is *indexable*: it enumerates files already on disk. Concrete metadata (artist/album/tags) is intentionally **not** resolved here — a source yields a file plus a minimal title; enrichment (plan §6.2) fills the rest later, so this stays a thin discovery layer (CLAUDE.md: no duplicated business logic).""" from dataclasses import dataclass, field from pathlib import Path from typing import Any # A source's ``kind`` describes which ports it satisfies, so the UI/admin can # tell an indexed folder from a searchable fetch-source. A backend may be both. KIND_INDEXABLE = "indexable" # enumerates files already on disk (local folder) KIND_FETCH = "fetch" # searches + downloads from an external service (YTM, …) @dataclass(frozen=True, slots=True) class SourceInfo: """Describes a registered source for enumeration / health (UI, admin).""" name: str label: str kind: str # KIND_INDEXABLE | KIND_FETCH available: bool @dataclass(frozen=True, slots=True) class SourceFile: """A single importable file discovered by an indexable source. ``source_id`` is stable per source (the local backend uses the path relative to its root) so re-scans are idempotent — already-imported files are skipped. """ source_id: str path: Path suggested_title: str file_format: str file_size: int @dataclass(frozen=True, slots=True) class SearchResult: """One hit from a searchable source (plan §5), shown on the discover screen. ``source_id`` is the stable handle the same backend later resolves in ``fetch`` — it must round-trip a download request without re-searching. ``raw`` carries the backend's untouched payload for debugging / future use. """ source: str source_id: str title: str artist: str | None album: str | None duration_seconds: int | None thumbnail_url: str | None raw: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True, slots=True) class RawMetadata: """Metadata a fetch-source can offer about an item *before* enrichment. Best-effort and source-shaped — the canonical metadata still comes from the enrichment pipeline (plan §6.2). Used to seed a more useful provisional title than a bare id while a download is queued.""" title: str | None artist: str | None album: str | None year: int | None extra: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True, slots=True) class DownloadResult: """A file a fetch-source produced on local disk (plan §5). ``path`` is a temp file the caller owns: it is stored into managed storage and then removed (same lifecycle as an upload). ``source_id`` is echoed back because some backends only learn the canonical id during the download.""" source_id: str path: Path file_format: str file_size: int bitrate: int | None suggested_title: str