Set scores
This commit is contained in:
@@ -1,15 +1,9 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import Form, HTTPException, Request
|
from fastapi import Form, Request
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from folkugat_web.api import router
|
from folkugat_web.api import router
|
||||||
from folkugat_web.fragments import live, sessio
|
from folkugat_web.fragments import live, sessio
|
||||||
from folkugat_web.services import auth, files
|
from folkugat_web.services import auth
|
||||||
from folkugat_web.services import playlists as playlists_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 scores as scores_service
|
|
||||||
from folkugat_web.services.temes import write as temes_service
|
from folkugat_web.services.temes import write as temes_service
|
||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
@@ -205,25 +199,26 @@ def set_tema_new(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/sessio/{session_id}/set/{set_id}/score")
|
# @router.get("/api/sessio/{session_id}/set/{set_id}/score")
|
||||||
async def render(
|
# async def render(
|
||||||
request: Request,
|
# request: Request,
|
||||||
_: auth.RequireLogin,
|
# _: auth.RequireLogin,
|
||||||
session_id: int,
|
# session_id: int,
|
||||||
set_id: int,
|
# set_id: int,
|
||||||
):
|
# ):
|
||||||
set_entry = playlists_service.get_set(session_id=session_id, set_id=set_id)
|
# set_entry = playlists_service.get_set(session_id=session_id, set_id=set_id)
|
||||||
if not set_entry:
|
# if not set_entry:
|
||||||
raise HTTPException(status_code=404, detail="Could not find set!")
|
# raise HTTPException(status_code=404, detail="Could not find set!")
|
||||||
tune_set = lilypond_build.set_from_set(set_entry=set_entry)
|
# set_entry = playlists_service.add_temes_to_set(set_entry)
|
||||||
set_source = lilypond_source.set_source(tune_set=tune_set)
|
# tune_set = lilypond_build.set_from_set(set_entry=set_entry)
|
||||||
pdf_result = await lilypond_render.render(
|
# set_source = lilypond_source.set_source(tune_set=tune_set)
|
||||||
source=set_source,
|
# pdf_result = await lilypond_render.render(
|
||||||
output=lilypond_render.RenderOutput.PDF,
|
# source=set_source,
|
||||||
)
|
# output=lilypond_render.RenderOutput.PDF,
|
||||||
if output_filename := pdf_result.result:
|
# )
|
||||||
score_render_url = files.get_db_file_path(output_filename)
|
# if output_filename := pdf_result.result:
|
||||||
# return temes.score_render(request=request, score_id=score_id, score_render_url=score_render_url)
|
# score_render_url = files.get_db_file_path(output_filename)
|
||||||
return HTMLResponse(content=score_render_url)
|
# # return temes.score_render(request=request, score_id=score_id, score_render_url=score_render_url)
|
||||||
else:
|
# return HTMLResponse(content=score_render_url)
|
||||||
return HTMLResponse()
|
# else:
|
||||||
|
# return HTMLResponse()
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ def page(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/api/content/sessio/{session_id}/set/{set_id}")
|
@router.get("/api/content/sessio/{session_id}/set/{set_id}")
|
||||||
def contingut(
|
async def contingut(
|
||||||
request: Request,
|
request: Request,
|
||||||
logged_in: auth.LoggedIn,
|
logged_in: auth.LoggedIn,
|
||||||
session_id: int,
|
session_id: int,
|
||||||
set_id: int,
|
set_id: int,
|
||||||
):
|
):
|
||||||
return set_page.pagina(request, session_id, set_id, logged_in)
|
return await set_page.pagina(request, session_id, set_id, logged_in)
|
||||||
|
|||||||
@@ -9,6 +9,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if set.score %}
|
||||||
|
{% for tema_in_set in set.temes %}
|
||||||
|
{% if tema_in_set.tema is not none %}
|
||||||
|
{% set tema = tema_in_set.tema %}
|
||||||
|
{% include "fragments/sessio/set/tema_title.html" %}
|
||||||
|
{% else %}
|
||||||
|
<h3 class="text-center text-3xl p-4"> <i>Desconegut</i> </h3>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="mx-12">
|
||||||
|
<hr class="h-px mt-1 mb-3 bg-beige border-0">
|
||||||
|
{% include "fragments/sessio/set/set_score.html"%}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
{% for tema_in_set in set.temes %}
|
{% for tema_in_set in set.temes %}
|
||||||
{% if loop.index != 1 %}
|
{% if loop.index != 1 %}
|
||||||
<div class="mx-12">
|
<div class="mx-12">
|
||||||
@@ -28,3 +42,4 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="m-8"> </div>
|
<div class="m-8"> </div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div class="flex flex-col items-center
|
||||||
|
bg-white rounded-lg ">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<a href="{{ set.score.pdf_url or score.img_url }}" target="_blank">
|
||||||
|
<img class="py-4 px-2 max-w-full h-auto" src="{{ set.score.img_url }}" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% if set.score.pdf_url is not none %}
|
||||||
|
<div class="bg-beige border rounded border-beige m-2 p-1">
|
||||||
|
<a href="{{ set.score.pdf_url }}" target="_blank">Obre el PDF</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
<h3 class="text-center text-3xl p-4">
|
{% include "fragments/sessio/set/tema_title.html" %}
|
||||||
{{ tema.title }}
|
|
||||||
<a class="text-beige"
|
|
||||||
º href="/tema/{{ tema.id }}">
|
|
||||||
<i class="fa fa-chevron-right px-2" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</h3>
|
|
||||||
<div class="mx-12 text-left">
|
<div class="mx-12 text-left">
|
||||||
|
|
||||||
{% if tema.main_score() is not none %}
|
{% if tema.main_score() is not none %}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<h3 class="text-center text-3xl p-4">
|
||||||
|
{{ tema.title }}
|
||||||
|
<a class="text-beige"
|
||||||
|
º href="/tema/{{ tema.id }}">
|
||||||
|
<i class="fa fa-chevron-right px-2" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
@@ -11,7 +11,17 @@ logger.info(f"Using DB_DIR: {DB_DIR}")
|
|||||||
# Files db
|
# Files db
|
||||||
|
|
||||||
DB_FILES_DIR = DB_DIR / "fitxer"
|
DB_FILES_DIR = DB_DIR / "fitxer"
|
||||||
DB_FILES_DIR.mkdir(exist_ok=True)
|
DB_FILES_TEMA_DIR = DB_FILES_DIR / "tema"
|
||||||
|
DB_FILES_SET_DIR = DB_FILES_DIR / "set"
|
||||||
|
DB_FILES_TMP_DIR = DB_FILES_DIR / "tmp"
|
||||||
|
|
||||||
|
for path in [
|
||||||
|
DB_FILES_DIR,
|
||||||
|
DB_FILES_TEMA_DIR,
|
||||||
|
DB_FILES_SET_DIR,
|
||||||
|
DB_FILES_SET_DIR,
|
||||||
|
]:
|
||||||
|
path.mkdir(exist_ok=True)
|
||||||
|
|
||||||
DB_FILES_URL = "/db/fitxer"
|
DB_FILES_URL = "/db/fitxer"
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ from folkugat_web.services import sessions as sessions_service
|
|||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
|
|
||||||
def pagina(request: Request, session_id: int, set_id: int, logged_in: bool):
|
async def pagina(request: Request, session_id: int, set_id: int, logged_in: bool):
|
||||||
session = sessions_service.get_session(session_id=session_id)
|
session = sessions_service.get_session(session_id=session_id)
|
||||||
set_ = playlists_service.get_set(session_id=session_id, set_id=set_id)
|
set_ = playlists_service.get_set(session_id=session_id, set_id=set_id)
|
||||||
if not set_:
|
if not set_:
|
||||||
raise HTTPException(status_code=404, detail="Set not found")
|
raise HTTPException(status_code=404, detail="Set not found")
|
||||||
set_ = playlists_service.add_temes_to_set(set_)
|
set_ = playlists_service.add_temes_to_set(set_)
|
||||||
|
set_ = await playlists_service.add_set_score_to_set(set_)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"fragments/sessio/set/pagina.html",
|
"fragments/sessio/set/pagina.html",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,10 +33,17 @@ class TemaInSet:
|
|||||||
return cls(id=entry.id, tema_id=entry.tema_id, tema=None)
|
return cls(id=entry.id, tema_id=entry.tema_id, tema=None)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class SetScore:
|
||||||
|
img_url: str | None
|
||||||
|
pdf_url: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Set:
|
class Set:
|
||||||
id: int
|
id: int
|
||||||
temes: list[TemaInSet]
|
temes: list[TemaInSet]
|
||||||
|
score: SetScore | None
|
||||||
|
|
||||||
def to_playlist_entries(self, session_id: int) -> Iterator[PlaylistEntry]:
|
def to_playlist_entries(self, session_id: int) -> Iterator[PlaylistEntry]:
|
||||||
for tema_in_set in self.temes:
|
for tema_in_set in self.temes:
|
||||||
@@ -52,6 +59,7 @@ class Set:
|
|||||||
return cls(
|
return cls(
|
||||||
id=set_id,
|
id=set_id,
|
||||||
temes=[TemaInSet.from_playlist_entry(entry) for entry in sorted(entries, key=lambda e: e.id or 0)],
|
temes=[TemaInSet.from_playlist_entry(entry) for entry in sorted(entries, key=lambda e: e.id or 0)],
|
||||||
|
score=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import itertools
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
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:
|
def create_tema_filename(tema_id: int, extension: str = "") -> Path:
|
||||||
filename = str(uuid.uuid4().hex) + extension
|
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)
|
filedir.mkdir(parents=True, exist_ok=True)
|
||||||
filepath = filedir / filename
|
filepath = filedir / filename
|
||||||
return filepath
|
return filepath
|
||||||
@@ -67,12 +68,14 @@ def create_tema_filename(tema_id: int, extension: str = "") -> Path:
|
|||||||
|
|
||||||
def create_tmp_filename(extension: str = "") -> Path:
|
def create_tmp_filename(extension: str = "") -> Path:
|
||||||
filename = str(uuid.uuid4().hex) + extension
|
filename = str(uuid.uuid4().hex) + extension
|
||||||
filedir = db.DB_FILES_DIR / "tmp"
|
filepath = db.DB_FILES_TMP_DIR / filename
|
||||||
filedir.mkdir(exist_ok=True)
|
|
||||||
filepath = filedir / filename
|
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
def get_set_filename(filename: str) -> Path:
|
||||||
|
return db.DB_FILES_SET_DIR / filename
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def tmp_file(content: str):
|
async def tmp_file(content: str):
|
||||||
input_filename = create_tmp_filename(extension=".ly")
|
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]:
|
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()]
|
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
|
alive_urls = link_urls | score_pdf_urls | score_img_urls
|
||||||
return filter(
|
return filter(
|
||||||
lambda p: p.is_file() and get_db_file_path(p) not in alive_urls,
|
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 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 playlists as playlists_model
|
||||||
from folkugat_web.model import temes as model
|
from folkugat_web.model import temes as model
|
||||||
from folkugat_web.model.lilypond import score as lilypond_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 lyrics as lyrics_service
|
||||||
from folkugat_web.services.temes import properties as properties_service
|
from folkugat_web.services.temes import properties as properties_service
|
||||||
from folkugat_web.services.temes import query as temes_q
|
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 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]
|
tema_ids = [tema_in_set.tema_id for tema_in_set in set_entry.temes]
|
||||||
temes_by_id = (
|
temes_by_id = {
|
||||||
FnChain.transform([tema_id for tema_id in tema_ids if tema_id is not None]) |
|
tema_in_set.tema_id: tema_in_set.tema
|
||||||
temes_q.get_temes_by_ids |
|
for tema_in_set in set_entry.temes
|
||||||
properties_service.add_properties_to_temes |
|
if tema_in_set.id is not None and tema_in_set.tema
|
||||||
lyrics_service.add_lyrics_to_temes |
|
}
|
||||||
scores_service.add_scores_to_temes |
|
|
||||||
_build_temes_by_id
|
|
||||||
).result()
|
|
||||||
temes = [temes_by_id[tema_id] if tema_id is not None else None for tema_id in tema_ids]
|
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)
|
set_title = build_set_title(temes=temes)
|
||||||
tunes = tunes_from_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()
|
stderr_lines = stderr.decode("utf-8").splitlines()
|
||||||
logger.warning("Lilypond errors:")
|
logger.warning("Lilypond errors:")
|
||||||
for line in stderr_lines:
|
for line in stderr_lines:
|
||||||
logger.warning(line)
|
logger.warning(f"[LILYPOND] {line}")
|
||||||
async with aiofiles.open(input_file, "r") as f:
|
async with aiofiles.open(input_file, "r") as f:
|
||||||
lines = await f.readlines()
|
lines = await f.readlines()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
|
import dataclasses
|
||||||
|
|
||||||
from folkugat_web.dal.sql import Connection
|
from folkugat_web.dal.sql import Connection
|
||||||
from folkugat_web.dal.sql._connection import get_connection
|
from folkugat_web.dal.sql._connection import get_connection
|
||||||
from folkugat_web.dal.sql.playlists import query, write
|
from folkugat_web.dal.sql.playlists import query, write
|
||||||
from folkugat_web.log import logger
|
from folkugat_web.log import logger
|
||||||
from folkugat_web.model import playlists
|
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 links as links_service
|
||||||
from folkugat_web.services.temes import lyrics as lyrics_service
|
from folkugat_web.services.temes import lyrics as lyrics_service
|
||||||
from folkugat_web.services.temes import query as temes_query
|
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:
|
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)
|
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)
|
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