Improved lyrics rendering

This commit is contained in:
marc
2025-04-05 23:09:20 +02:00
parent 211a1fbb05
commit 7a823a98ab
6 changed files with 44 additions and 25 deletions

View File

@@ -35,8 +35,7 @@ def set_lyric(
lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id) lyric = lyrics_service.get_lyric_by_id(lyric_id=lyric_id, tema_id=tema_id)
if not lyric: if not lyric:
raise HTTPException(status_code=404, detail="Could not find lyric!") raise HTTPException(status_code=404, detail="Could not find lyric!")
new_lyric = dataclasses.replace(lyric, title=title, content=content.strip()) new_lyric = lyrics_service.update_lyric(lyric=lyric, title=title, content=content)
lyrics_service.update_lyric(lyric=new_lyric)
return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=new_lyric) return lyrics_fragments.lyric(request=request, logged_in=logged_in, lyric=new_lyric)

View File

@@ -27,20 +27,29 @@
>> >>
} }
{% if lyrics_text is not none %} {% if lyrics_text is not none %}
{% for line in lyrics_text.lines %}
\markup { \markup {
\vspace #2
\fill-line { \fill-line {
{% for paragraph in line.paragraphs %}
\hspace #1 \hspace #1
{% for column in lyrics_text.columns %}
\center-column { \center-column {
{% for paragraph in column.paragraphs %}
{% for line in paragraph.lines %} {% for line in paragraph.lines %}
\line { {{ line | safe }} } \line { {{ line | safe }} }
{% endfor %} {% endfor %}
\vspace #1
{% endfor %}
} }
\hspace #1
{% endfor %} {% endfor %}
\hspace #1
}
}
{% if loop.index < lyrics_text.lines|length %}
\markup {
\vspace #1
\fill-line {
\override #'(span-factor . 4/9)
\draw-hline
} }
} }
{% endif %} {% endif %}
{% endfor %}
{% endif %}

View File

@@ -30,10 +30,10 @@ class LyricsParagraph:
@dataclasses.dataclass @dataclasses.dataclass
class LyricsColumn: class LyricsLine:
paragraphs: list[LyricsParagraph] paragraphs: list[LyricsParagraph]
@dataclasses.dataclass @dataclasses.dataclass
class LyricsText: class LyricsText:
columns: list[LyricsColumn] lines: list[LyricsLine]

View File

@@ -1,17 +1,17 @@
import asyncio import asyncio
import dataclasses import dataclasses
import itertools
import os import os
import re import re
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
import aiofiles import aiofiles
from folkugat_web.model.lilypond import (LyricsColumn, LyricsParagraph, from folkugat_web.model.lilypond import (LyricsLine, LyricsParagraph,
LyricsText, RenderError) LyricsText, RenderError)
from folkugat_web.model.temes import Tema from folkugat_web.model.temes import Tema
from folkugat_web.services import files as files_service from folkugat_web.services import files as files_service
from folkugat_web.templates import templates from folkugat_web.templates import templates
from folkugat_web.utils import batched
SCORE_BEGINNING = "% --- SCORE BEGINNING --- %" SCORE_BEGINNING = "% --- SCORE BEGINNING --- %"
@@ -23,6 +23,9 @@ def source_to_single_score(source: str, tema: Tema) -> str:
lyrics_text = build_lyrics(tema.lyrics[0].content) lyrics_text = build_lyrics(tema.lyrics[0].content)
else: else:
lyrics_text = None lyrics_text = None
print("AAAAAA")
print(lyrics_text)
print("AAAAAA")
return templates.get_template("lilypond/single_score.ly").render( return templates.get_template("lilypond/single_score.ly").render(
score_beginning=SCORE_BEGINNING, score_beginning=SCORE_BEGINNING,
score_source=source, score_source=source,
@@ -77,13 +80,10 @@ async def render(source: str, fmt: RenderFormat, output_filename: Path | None =
return output_filename, [] return output_filename, []
def build_lyrics(text: str, max_cols: int = 2, min_pars: int = 2) -> LyricsText: def build_lyrics(text: str, max_cols: int = 3) -> LyricsText:
paragraphs = [LyricsParagraph(lines=par_str.splitlines()) for par_str in text.split("\n\n")] paragraphs = [LyricsParagraph(lines=par_str.splitlines()) for par_str in text.split("\n\n") if par_str]
n_cols = next(filter(lambda nc: len(paragraphs) // nc >= min_pars, range(max_cols, 0, -1)), 1) print(list(batched(paragraphs, max_cols)))
n_long_cols = len(paragraphs) % n_cols return LyricsText(lines=[
pars_per_col = [len(paragraphs) // n_cols + 1] * n_long_cols + [len(paragraphs) // n_cols] * (n_cols - n_long_cols) LyricsLine(paragraphs=list(line_pars))
acc_pars_per_col = [0] + list(itertools.accumulate(pars_per_col)) for line_pars in batched(paragraphs, max_cols)
return LyricsText(columns=[
LyricsColumn(paragraphs=paragraphs[acc_pars_per_col[i]:acc_pars_per_col[i+1]])
for i in range(n_cols)
]) ])

View File

@@ -17,9 +17,11 @@ def _sub(pattern: str, sub: str) -> Callable[[str], str]:
def _clean_string(lyrics_str: str) -> str: def _clean_string(lyrics_str: str) -> str:
return ( return (
FnChain.transform(lyrics_str) | FnChain.transform(lyrics_str) |
_sub(r" *", " ") | _sub(r"\t", " ") |
_sub(r"\s*\n\s*", "\n") | _sub(r" +", " ") |
_sub(r"\n*", "\n") _sub(r" \n", "\n") |
_sub(r"\n ", "\n") |
_sub(r"\n\n+", "\n\n")
).result() ).result()
@@ -37,9 +39,10 @@ def get_lyric_by_id(lyric_id: int, tema_id: int | None = None) -> model.Lyrics |
return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None) return next(iter(lyrics_dal.get_lyrics(lyric_id=lyric_id, tema_id=tema_id)), None)
def update_lyric(lyric: model.Lyrics): def update_lyric(lyric: model.Lyrics, title: str, content: str):
lyric = dataclasses.replace(lyric, content=_clean_string(lyric.content)) lyric = dataclasses.replace(lyric, title=title.strip(), content=_clean_string(content))
lyrics_dal.update_lyric(lyric=lyric) lyrics_dal.update_lyric(lyric=lyric)
return lyric
def delete_lyric(lyric_id: int, tema_id: int | None = None): def delete_lyric(lyric_id: int, tema_id: int | None = None):

View File

@@ -47,3 +47,11 @@ class FnChain(Generic[U]):
@classmethod @classmethod
def transform(cls, x: V, /) -> FnChain[V]: def transform(cls, x: V, /) -> FnChain[V]:
return FnChain(lambda: x) return FnChain(lambda: x)
def batched(iterable: Iterable[T], n: int) -> Iterator[tuple[T, ...]]:
if n < 1:
raise ValueError('n must be at least one')
iterator = iter(iterable)
while batch := tuple(itertools.islice(iterator, n)):
yield batch