Set scores
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
@@ -59,7 +60,7 @@ async def store_file(tema_id: int, upload_file: UploadFile) -> str:
|
||||
|
||||
def create_tema_filename(tema_id: int, extension: str = "") -> Path:
|
||||
filename = str(uuid.uuid4().hex) + extension
|
||||
filedir = db.DB_FILES_DIR / "tema" / str(tema_id)
|
||||
filedir = db.DB_FILES_TEMA_DIR / str(tema_id)
|
||||
filedir.mkdir(parents=True, exist_ok=True)
|
||||
filepath = filedir / filename
|
||||
return filepath
|
||||
@@ -67,12 +68,14 @@ def create_tema_filename(tema_id: int, extension: str = "") -> Path:
|
||||
|
||||
def create_tmp_filename(extension: str = "") -> Path:
|
||||
filename = str(uuid.uuid4().hex) + extension
|
||||
filedir = db.DB_FILES_DIR / "tmp"
|
||||
filedir.mkdir(exist_ok=True)
|
||||
filepath = filedir / filename
|
||||
filepath = db.DB_FILES_TMP_DIR / filename
|
||||
return filepath
|
||||
|
||||
|
||||
def get_set_filename(filename: str) -> Path:
|
||||
return db.DB_FILES_SET_DIR / filename
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def tmp_file(content: str):
|
||||
input_filename = create_tmp_filename(extension=".ly")
|
||||
@@ -86,7 +89,7 @@ async def tmp_file(content: str):
|
||||
|
||||
|
||||
def list_files(tema_id: str) -> list[str]:
|
||||
filedir = db.DB_FILES_DIR / str(tema_id)
|
||||
filedir = db.DB_FILES_TEMA_DIR / str(tema_id)
|
||||
return [get_db_file_path(f) for f in filedir.iterdir()]
|
||||
|
||||
|
||||
@@ -97,7 +100,10 @@ def get_orphan_files() -> Iterator[Path]:
|
||||
alive_urls = link_urls | score_pdf_urls | score_img_urls
|
||||
return filter(
|
||||
lambda p: p.is_file() and get_db_file_path(p) not in alive_urls,
|
||||
db.DB_FILES_DIR.rglob("*"),
|
||||
itertools.chain(
|
||||
db.DB_FILES_TEMA_DIR.rglob("*"),
|
||||
db.DB_FILES_TMP_DIR.rglob("*"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
from collections.abc import Iterable, Iterator
|
||||
from collections.abc import Iterable
|
||||
|
||||
from fastapi import HTTPException
|
||||
from folkugat_web.dal.sql.temes import scores as scores_dal
|
||||
from folkugat_web.model import playlists as playlists_model
|
||||
from folkugat_web.model import temes as model
|
||||
from folkugat_web.model.lilypond import score as lilypond_model
|
||||
from folkugat_web.services import lilypond
|
||||
from folkugat_web.services.temes import lyrics as lyrics_service
|
||||
from folkugat_web.services.temes import properties as properties_service
|
||||
from folkugat_web.services.temes import query as temes_q
|
||||
@@ -82,17 +80,15 @@ def build_set_title(temes: list[model.Tema | None]) -> str:
|
||||
|
||||
|
||||
def set_from_set(set_entry: playlists_model.Set) -> lilypond_model.LilypondSet:
|
||||
def _build_temes_by_id(temes: Iterable[model.Tema]) -> dict[int, model.Tema]:
|
||||
return {tema.id: tema for tema in temes if tema.id is not None}
|
||||
"""
|
||||
The tune_set is assumed to be enriched with tunes
|
||||
"""
|
||||
tema_ids = [tema_in_set.tema_id for tema_in_set in set_entry.temes]
|
||||
temes_by_id = (
|
||||
FnChain.transform([tema_id for tema_id in tema_ids if tema_id is not None]) |
|
||||
temes_q.get_temes_by_ids |
|
||||
properties_service.add_properties_to_temes |
|
||||
lyrics_service.add_lyrics_to_temes |
|
||||
scores_service.add_scores_to_temes |
|
||||
_build_temes_by_id
|
||||
).result()
|
||||
temes_by_id = {
|
||||
tema_in_set.tema_id: tema_in_set.tema
|
||||
for tema_in_set in set_entry.temes
|
||||
if tema_in_set.id is not None and tema_in_set.tema
|
||||
}
|
||||
temes = [temes_by_id[tema_id] if tema_id is not None else None for tema_id in tema_ids]
|
||||
set_title = build_set_title(temes=temes)
|
||||
tunes = tunes_from_temes(temes)
|
||||
|
||||
@@ -91,7 +91,7 @@ async def _build_errors(input_file: Path, stderr: bytes) -> list[RenderError]:
|
||||
stderr_lines = stderr.decode("utf-8").splitlines()
|
||||
logger.warning("Lilypond errors:")
|
||||
for line in stderr_lines:
|
||||
logger.warning(line)
|
||||
logger.warning(f"[LILYPOND] {line}")
|
||||
async with aiofiles.open(input_file, "r") as f:
|
||||
lines = await f.readlines()
|
||||
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import dataclasses
|
||||
|
||||
from folkugat_web.dal.sql import Connection
|
||||
from folkugat_web.dal.sql._connection import get_connection
|
||||
from folkugat_web.dal.sql.playlists import query, write
|
||||
from folkugat_web.log import logger
|
||||
from folkugat_web.model import playlists
|
||||
from folkugat_web.model.lilypond import score as lilypond_model
|
||||
from folkugat_web.services import files as files_service
|
||||
from folkugat_web.services.lilypond import build as lilypond_build
|
||||
from folkugat_web.services.lilypond import render as lilypond_render
|
||||
from folkugat_web.services.lilypond import source as lilypond_source
|
||||
from folkugat_web.services.temes import links as links_service
|
||||
from folkugat_web.services.temes import lyrics as lyrics_service
|
||||
from folkugat_web.services.temes import query as temes_query
|
||||
@@ -84,3 +91,64 @@ def set_tema(session_id: int, set_id: int, entry_id: int, tema_id: int | None,
|
||||
with get_connection(con) as con:
|
||||
new_entry = playlists.PlaylistEntry(id=entry_id, session_id=session_id, set_id=set_id, tema_id=tema_id)
|
||||
write.update_playlist_entry(entry=new_entry, con=con)
|
||||
|
||||
|
||||
def _elegible_for_set_score(tune: lilypond_model.LilypondTune) -> bool:
|
||||
# A tune will be eligible for a set score if it has a score, lyrics or is unknown
|
||||
return tune.is_unknown or bool(tune.score_source) or bool(tune.lyrics)
|
||||
|
||||
|
||||
async def get_or_create_set_score(tune_set: playlists.Set) -> playlists.SetScore | None:
|
||||
# The tune_set is assumed to be enriched with tunes
|
||||
lilypond_set = lilypond_build.set_from_set(set_entry=tune_set)
|
||||
if not all(map(_elegible_for_set_score, lilypond_set.tunes)):
|
||||
return None
|
||||
set_score_hash = lilypond_set.hash().hex()
|
||||
|
||||
pdf_filepath = files_service.get_set_filename(f"{set_score_hash}.pdf")
|
||||
png_filepath = files_service.get_set_filename(f"{set_score_hash}.cropped.png")
|
||||
|
||||
if not pdf_filepath.exists() or not png_filepath.exists():
|
||||
# No score exists, so we need to create it
|
||||
set_source = lilypond_source.set_source(tune_set=lilypond_set)
|
||||
out_filepath = files_service.get_set_filename(set_score_hash)
|
||||
print("Out filepath: ", str(out_filepath))
|
||||
async with files_service.tmp_file(content=set_source) as source_filepath:
|
||||
if not pdf_filepath.exists():
|
||||
pdf_result = await lilypond_render.render_file(
|
||||
input_file=source_filepath,
|
||||
output=lilypond_render.RenderOutput.PDF,
|
||||
output_file=out_filepath,
|
||||
)
|
||||
if pdf_result.error is not None:
|
||||
return None
|
||||
if pdf_result.result is None:
|
||||
raise RuntimeError("This shouldn't happen")
|
||||
pdf_filepath = pdf_result.result
|
||||
|
||||
if not png_filepath.exists():
|
||||
png_result = await lilypond_render.render_file(
|
||||
input_file=source_filepath,
|
||||
output=lilypond_render.RenderOutput.PNG_CROPPED,
|
||||
output_file=out_filepath,
|
||||
)
|
||||
if png_result.error is not None:
|
||||
return None
|
||||
if png_result.result is None:
|
||||
raise RuntimeError("This shouldn't happen")
|
||||
png_filepath = png_result.result
|
||||
|
||||
return playlists.SetScore(
|
||||
img_url=files_service.get_db_file_path(png_filepath),
|
||||
pdf_url=files_service.get_db_file_path(pdf_filepath),
|
||||
)
|
||||
|
||||
|
||||
async def add_set_score_to_set(tune_set: playlists.Set) -> playlists.Set:
|
||||
if score := await get_or_create_set_score(tune_set=tune_set):
|
||||
return dataclasses.replace(
|
||||
tune_set,
|
||||
score=score,
|
||||
)
|
||||
else:
|
||||
return tune_set
|
||||
|
||||
Reference in New Issue
Block a user