import dataclasses import datetime import enum import itertools from typing import Self from folkugat_web.model.lilypond.processing import RenderError from folkugat_web.model.search import NGrams from folkugat_web.model.sessions import Session from folkugat_web.services import ngrams class ContentType(enum.Enum): PARTITURA = "partitura" AUDIO = "àudio" OTHER = "enllaç" class LinkType(enum.Enum): # Score PDF = "pdf" IMAGE = "image" # Audio SPOTIFY = "spotify" YOUTUBE = "youtube" @dataclasses.dataclass class Link: id: int | None tema_id: int content_type: ContentType link_type: LinkType | None url: str title: str = "" class PropertyField(enum.Enum): AUTOR = "autor" TIPUS = "tipus" COMPAS = "compàs" ORIGEN = "orígen" @dataclasses.dataclass class Property: id: int | None tema_id: int field: PropertyField value: str @dataclasses.dataclass class Lyrics: id: int | None tema_id: int title: str content: str @dataclasses.dataclass class Score: id: int | None tema_id: int title: str errors: list[RenderError] source: str img_url: str | None pdf_url: str | None hidden: bool @classmethod def from_link(cls, link: Link) -> Self: if link.link_type not in {LinkType.IMAGE, LinkType.PDF}: raise ValueError("Link not supported to build a score!") return cls( id=None, tema_id=link.tema_id, title=link.title, source="", errors=[], img_url=link.url if link.link_type is LinkType.IMAGE else None, pdf_url=link.url if link.link_type is LinkType.PDF else None, hidden=False, ) @dataclasses.dataclass class Stats: times_played: int sessions_played: list[Session] @dataclasses.dataclass class Tema: id: int | None = None # Info title: str = "" properties: list[Property] = dataclasses.field(default_factory=list) links: list[Link] = dataclasses.field(default_factory=list) lyrics: list[Lyrics] = dataclasses.field(default_factory=list) scores: list[Score] = dataclasses.field(default_factory=list) # Search related alternatives: list[str] = dataclasses.field(default_factory=list) hidden: bool = True # Other info modification_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now) creation_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now) # Stats stats: Stats | None = None def ngrams(self) -> NGrams: return ngrams.get_text_ngrams(self.title, *self.alternatives) @staticmethod def _is_score(link: Link) -> bool: if link.content_type is not ContentType.PARTITURA: return False if link.link_type is LinkType.PDF: return link.url.startswith("/") return link.link_type is LinkType.IMAGE def main_score(self) -> Score | None: candidate_scores = filter(lambda s: s.hidden is False, self.scores) candidate_links = map(Score.from_link, filter(self._is_score, self.links)) return next(itertools.chain(candidate_scores, candidate_links), None) def composer(self) -> str | None: result = next(filter(lambda prop: prop.field is PropertyField.AUTOR, self.properties), None) return result.value if result else None def origin(self) -> str | None: result = next(filter(lambda prop: prop.field is PropertyField.ORIGEN, self.properties), None) return result.value if result else None @property def has_score(self) -> bool: return bool(self.main_score()) @property def has_audio(self) -> bool: audio_links = filter(lambda l: l.content_type is ContentType.AUDIO, self.links) return any(audio_links) @property def has_lyrics(self) -> bool: return bool(self.lyrics) class TemaCols(enum.Enum): NOM = "nom" COPS_TOCAT = "cops_tocat"