import dataclasses from typing import Annotated from fastapi import HTTPException, Request from fastapi.params import Form from fastapi.responses import HTMLResponse from folkugat_web.api import router from folkugat_web.fragments import temes from folkugat_web.fragments.tema import scores as scores_fragments from folkugat_web.services import auth, files 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 @router.get("/api/tema/{tema_id}/score/{score_id}") def score(request: Request, logged_in: auth.LoggedIn, tema_id: int, score_id: int): score = scores_service.get_score_by_id(score_id=score_id, tema_id=tema_id) if not score: raise HTTPException(status_code=404, detail="Could not find lyric!") return scores_fragments.score(request=request, logged_in=logged_in, score=score) @router.put("/api/tema/{tema_id}/score/{score_id}") async def set_score( request: Request, logged_in: auth.RequireLogin, tema_id: int, score_id: int, title: Annotated[str, Form()], source: Annotated[str, Form()], ): source = source.strip() score = scores_service.get_score_by_id(score_id=score_id, tema_id=tema_id) if not score: raise HTTPException(status_code=404, detail="Could not find lyric!") tune = lilypond_build.tune_from_tema_id(tema_id=tema_id, score_source=source) tune_source = lilypond_source.tune_source(tune=tune) pdf_result, png_result = await render_tune(tune_source=tune_source, tema_id=tema_id) if errors := pdf_result.error: new_score = dataclasses.replace( score, source=source, title=title, errors=errors, ) elif png_result is None: raise RuntimeError(f"Received empty png_result with pdf_result: {pdf_result}") elif errors := png_result.error: new_score = dataclasses.replace( score, source=source, title=title, errors=errors, ) else: pdf_file = pdf_result.result png_file = png_result.result new_score = dataclasses.replace( score, source=source, title=title, pdf_url=files.get_db_file_path(pdf_file) if pdf_file else None, img_url=files.get_db_file_path(png_file) if png_file else None, errors=[], ) scores_service.update_score(score=new_score) files.clean_orphan_files() return scores_fragments.score(request=request, logged_in=logged_in, score=new_score) async def render_tune( tune_source: str, tema_id: int, ) -> tuple[lilypond_render.RenderResult, lilypond_render.RenderResult | None]: async with files.tmp_file(content=tune_source) as source_file: pdf_file = files.create_tema_filename(tema_id=tema_id) pdf_result = await lilypond_render.render_file( input_file=source_file, output=lilypond_render.RenderOutput.PDF, output_file=pdf_file, ) if pdf_result.error: return pdf_result, None png_file = files.create_tema_filename(tema_id=tema_id) png_result = await lilypond_render.render_file( input_file=source_file, output=lilypond_render.RenderOutput.PNG_CROPPED, output_file=png_file, ) return pdf_result, png_result @router.post("/api/tema/{tema_id}/score") def add_score( request: Request, logged_in: auth.RequireLogin, tema_id: int, ): score = scores_service.create_score(tema_id=tema_id) return scores_fragments.score_editor( request=request, logged_in=logged_in, score=score, ) @router.delete("/api/tema/{tema_id}/score/{score_id}") def delete_score(request: Request, logged_in: auth.RequireLogin, tema_id: int, score_id: int): scores_service.delete_score(score_id=score_id, tema_id=tema_id) files.clean_orphan_files() return HTMLResponse() @router.get("/api/tema/{tema_id}/editor/score/{score_id}") def score_editor( request: Request, logged_in: auth.RequireLogin, tema_id: int, score_id: int, ): score = scores_service.get_score_by_id(score_id=score_id, tema_id=tema_id) if not score: raise HTTPException(status_code=404, detail="Could not find lyric!") return scores_fragments.score_editor( request=request, logged_in=logged_in, score=score, ) @router.post("/api/tema/{tema_id}/editor/score/{score_id}/render") async def render( request: Request, _: auth.RequireLogin, tema_id: int, score_id: int, source: Annotated[str, Form()], ): tune = lilypond_build.tune_from_tema_id(tema_id=tema_id, score_source=source) full_source = lilypond_source.tune_source(tune=tune) png_result = await lilypond_render.render( source=full_source, output=lilypond_render.RenderOutput.PNG_CROPPED, ) if output_filename := png_result.result: score_render_url = files.get_db_file_path(output_filename) return temes.score_render(request=request, score_id=score_id, score_render_url=score_render_url) else: return temes.score_render(request=request, score_id=score_id, errors=png_result.error) @router.put("/api/tema/{tema_id}/score/{score_id}/show") async def show_score( request: Request, logged_in: auth.RequireLogin, tema_id: int, score_id: int, ): score = scores_service.get_score_by_id(score_id=score_id, tema_id=tema_id) if not score: raise HTTPException(status_code=404, detail="Could not find lyric!") new_score = dataclasses.replace(score, hidden=False) scores_service.update_score(score=new_score) return scores_fragments.score(request=request, logged_in=logged_in, score=new_score) @router.put("/api/tema/{tema_id}/score/{score_id}/hide") async def hide_score( request: Request, logged_in: auth.RequireLogin, tema_id: int, score_id: int, ): score = scores_service.get_score_by_id(score_id=score_id, tema_id=tema_id) if not score: raise HTTPException(status_code=404, detail="Could not find lyric!") new_score = dataclasses.replace(score, hidden=True) scores_service.update_score(score=new_score) return scores_fragments.score(request=request, logged_in=logged_in, score=new_score)