Files
folkugat-web/folkugat_web/model/temes.py

166 lines
4.2 KiB
Python

from __future__ import annotations
import dataclasses
import datetime
import enum
import itertools
from typing import TYPE_CHECKING, Self
from folkugat_web.model.lilypond.processing import RenderError
from folkugat_web.model.search import NGrams
from folkugat_web.services import ngrams
if TYPE_CHECKING:
from folkugat_web.model.sessions import Session
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
preview_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,
preview_url=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
played_with: list[CommonlyPlayedTema] | 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)
@dataclasses.dataclass
class CommonlyPlayedTema:
tema: Tema
count: int
class TemaCols(enum.Enum):
NOM = "nom"
COPS_TOCAT = "cops_tocat"