Afegir partitures de llistes (cançoners)
This commit is contained in:
@@ -34,12 +34,12 @@ def page(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/api/content/llista/{playlist_id}")
|
@router.get("/api/content/llista/{playlist_id}")
|
||||||
def contingut(
|
async def contingut(
|
||||||
request: Request,
|
request: Request,
|
||||||
logged_in: auth.LoggedIn,
|
logged_in: auth.LoggedIn,
|
||||||
playlist_id: int,
|
playlist_id: int,
|
||||||
):
|
):
|
||||||
return playlist.pagina(request, playlist_id, logged_in)
|
return await playlist.pagina(request, playlist_id, logged_in)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/llista/{playlist_id}/name")
|
@router.get("/api/llista/{playlist_id}/name")
|
||||||
|
|||||||
@@ -653,6 +653,10 @@ video {
|
|||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-6 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-2 {
|
.ml-2 {
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<div id="llista-name">
|
<div id="llista-name">
|
||||||
{% include "fragments/playlist/name.html" %}
|
{% include "fragments/playlist/name.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
{% include "fragments/playlist/score.html" %}
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
{% set playlist_id = playlist.id %}
|
{% set playlist_id = playlist.id %}
|
||||||
{% set playlist = playlist %}
|
{% set playlist = playlist %}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{% if playlist.playlist_score is not none and playlist.playlist_score.pdf_url is not none %}
|
||||||
|
<div class="flex flex-col items-center mt-4 mb-6">
|
||||||
|
<div class="bg-beige border rounded border-beige m-2 p-2">
|
||||||
|
<a href="{{ playlist.playlist_score.pdf_url }}" target="_blank" class="text-white">
|
||||||
|
Obre en PDF
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
49
folkugat_web/assets/templates/lilypond/playlist.ly
Normal file
49
folkugat_web/assets/templates/lilypond/playlist.ly
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{{ score_beginning }}
|
||||||
|
\version "2.24.4"
|
||||||
|
|
||||||
|
{% include "lilypond/lib.ly" %}
|
||||||
|
|
||||||
|
\book {
|
||||||
|
\paper {
|
||||||
|
top-margin = 10
|
||||||
|
left-margin = 15
|
||||||
|
right-margin = 15
|
||||||
|
ragged-bottom = ##f
|
||||||
|
|
||||||
|
scoreTitleMarkup = \markup {
|
||||||
|
\center-column {
|
||||||
|
\fontsize #3 \bold \fromproperty #'header:piece
|
||||||
|
\fill-line {
|
||||||
|
\null
|
||||||
|
\right-column {
|
||||||
|
\fromproperty #'header:composer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\header {
|
||||||
|
title = \markup { "{{ playlist.title | safe }}" }
|
||||||
|
tagline = "Partitura generada amb LilyPond"
|
||||||
|
copyright = "Folkugat"
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for set in playlist.sets %}
|
||||||
|
{% if set.tunes|length > 1 %}
|
||||||
|
\markup {
|
||||||
|
\vspace #2
|
||||||
|
\fill-line {
|
||||||
|
\center-column {
|
||||||
|
\fontsize #2 \bold "{{ set.title | safe }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\noPageBreak
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for tune in set.tunes %}
|
||||||
|
{% include "lilypond/tune_in_set.ly" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
\noPageBreak
|
||||||
{% if tune.score_source is not none %}
|
{% if tune.score_source is not none %}
|
||||||
\score {
|
\score {
|
||||||
\language "english"
|
\language "english"
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ DB_FILES_DIR = DB_DIR / "fitxer"
|
|||||||
DB_FILES_TEMA_DIR = DB_FILES_DIR / "tema"
|
DB_FILES_TEMA_DIR = DB_FILES_DIR / "tema"
|
||||||
DB_FILES_SESSION_DIR = DB_FILES_DIR / "sessio"
|
DB_FILES_SESSION_DIR = DB_FILES_DIR / "sessio"
|
||||||
DB_FILES_SET_DIR = DB_FILES_DIR / "set"
|
DB_FILES_SET_DIR = DB_FILES_DIR / "set"
|
||||||
|
DB_FILES_PLAYLIST_DIR = DB_FILES_DIR / "llistes"
|
||||||
DB_FILES_TMP_DIR = DB_FILES_DIR / "tmp"
|
DB_FILES_TMP_DIR = DB_FILES_DIR / "tmp"
|
||||||
|
|
||||||
for path in [
|
for path in [
|
||||||
DB_FILES_DIR,
|
DB_FILES_DIR,
|
||||||
DB_FILES_TEMA_DIR,
|
DB_FILES_TEMA_DIR,
|
||||||
DB_FILES_SET_DIR,
|
DB_FILES_SET_DIR,
|
||||||
|
DB_FILES_PLAYLIST_DIR,
|
||||||
DB_FILES_SET_DIR,
|
DB_FILES_SET_DIR,
|
||||||
]:
|
]:
|
||||||
path.mkdir(exist_ok=True)
|
path.mkdir(exist_ok=True)
|
||||||
|
|||||||
@@ -174,12 +174,13 @@ def set_tema(request: Request, logged_in: bool, playlist_id: int, set_id: int, e
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def pagina(request: Request, playlist_id: int, logged_in: bool):
|
async def pagina(request: Request, playlist_id: int, logged_in: bool):
|
||||||
playlist = playlists_service.get_playlist(playlist_id=playlist_id)
|
playlist = playlists_service.get_playlist(playlist_id=playlist_id)
|
||||||
if not playlist:
|
if not playlist:
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
raise HTTPException(status_code=404, detail="Could not find playlist")
|
raise HTTPException(status_code=404, detail="Could not find playlist")
|
||||||
playlist = playlists_service.add_temes_to_playlist(playlist)
|
playlist = playlists_service.add_temes_to_playlist(playlist)
|
||||||
|
playlist = await playlists_service.add_playlist_score_to_playlist(playlist)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"fragments/playlist/pagina.html",
|
"fragments/playlist/pagina.html",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,3 +89,15 @@ class LilypondSet:
|
|||||||
self.title.encode(),
|
self.title.encode(),
|
||||||
*(tune.hash() for tune in self.tunes),
|
*(tune.hash() for tune in self.tunes),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class LilypondPlaylist:
|
||||||
|
title: str
|
||||||
|
sets: list[LilypondSet]
|
||||||
|
|
||||||
|
def hash(self) -> bytes:
|
||||||
|
return get_hash(
|
||||||
|
self.title.encode(),
|
||||||
|
*(lilypond_set.hash() for lilypond_set in self.sets),
|
||||||
|
)
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ class SetScore:
|
|||||||
pdf_url: str | None
|
pdf_url: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class PlaylistScore:
|
||||||
|
img_url: str | None
|
||||||
|
pdf_url: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Set:
|
class Set:
|
||||||
id: int
|
id: int
|
||||||
@@ -81,6 +87,7 @@ class Playlist:
|
|||||||
id: int
|
id: int
|
||||||
name: str | None
|
name: str | None
|
||||||
sets: list[Set]
|
sets: list[Set]
|
||||||
|
playlist_score: PlaylistScore | None = None
|
||||||
|
|
||||||
def to_playlist_entries(self) -> Iterator[PlaylistEntry]:
|
def to_playlist_entries(self) -> Iterator[PlaylistEntry]:
|
||||||
for set_entry in self.sets:
|
for set_entry in self.sets:
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ def get_set_filename(filename: str) -> Path:
|
|||||||
return db.DB_FILES_SET_DIR / filename
|
return db.DB_FILES_SET_DIR / filename
|
||||||
|
|
||||||
|
|
||||||
|
def get_playlist_filename(filename: str) -> Path:
|
||||||
|
return db.DB_FILES_PLAYLIST_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")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from fastapi import HTTPException
|
|||||||
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 playlists as playlists_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 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
|
||||||
@@ -96,3 +97,20 @@ def set_from_set(set_entry: playlists_model.Set) -> lilypond_model.LilypondSet:
|
|||||||
title=set_title,
|
title=set_title,
|
||||||
tunes=tunes
|
tunes=tunes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def playlist_from_playlist(playlist: playlists_model.Playlist) -> lilypond_model.LilypondPlaylist:
|
||||||
|
"""
|
||||||
|
The playlist is assumed to be enriched with tunes
|
||||||
|
"""
|
||||||
|
lilypond_sets = []
|
||||||
|
for set_entry in playlist.sets:
|
||||||
|
lilypond_set = set_from_set(set_entry)
|
||||||
|
if lilypond_set.tunes and all(map(playlists_service._elegible_for_set_score, lilypond_set.tunes)):
|
||||||
|
lilypond_sets.append(lilypond_set)
|
||||||
|
|
||||||
|
playlist_title = playlist.name or "Llista"
|
||||||
|
return lilypond_model.LilypondPlaylist(
|
||||||
|
title=playlist_title,
|
||||||
|
sets=lilypond_sets
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from folkugat_web.model.lilypond.score import LilypondSet, LilypondTune
|
from folkugat_web.model.lilypond.score import LilypondPlaylist, LilypondSet, LilypondTune
|
||||||
from folkugat_web.templates import templates
|
from folkugat_web.templates import templates
|
||||||
|
|
||||||
SCORE_BEGINNING = "% --- SCORE BEGINNING --- %"
|
SCORE_BEGINNING = "% --- SCORE BEGINNING --- %"
|
||||||
@@ -23,3 +23,10 @@ def set_source(tune_set: LilypondSet) -> str:
|
|||||||
score_beginning=SCORE_BEGINNING,
|
score_beginning=SCORE_BEGINNING,
|
||||||
tune_set=tune_set,
|
tune_set=tune_set,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def playlist_source(playlist: LilypondPlaylist) -> str:
|
||||||
|
return templates.get_template("lilypond/playlist.ly").render(
|
||||||
|
score_beginning=SCORE_BEGINNING,
|
||||||
|
playlist=playlist,
|
||||||
|
)
|
||||||
|
|||||||
@@ -170,3 +170,49 @@ async def add_set_score_to_set(tune_set: playlists.Set) -> playlists.Set:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return tune_set
|
return tune_set
|
||||||
|
|
||||||
|
|
||||||
|
async def get_or_create_playlist_score(playlist: playlists.Playlist) -> playlists.PlaylistScore | None:
|
||||||
|
# The playlist is assumed to be enriched with tunes
|
||||||
|
if not playlist.sets:
|
||||||
|
return None
|
||||||
|
|
||||||
|
lilypond_playlist = lilypond_build.playlist_from_playlist(playlist)
|
||||||
|
if not lilypond_playlist.sets:
|
||||||
|
return None
|
||||||
|
|
||||||
|
playlist_score_hash = lilypond_playlist.hash().hex()
|
||||||
|
|
||||||
|
pdf_filepath = files_service.get_playlist_filename(f"{playlist_score_hash}.pdf")
|
||||||
|
|
||||||
|
if not pdf_filepath.exists():
|
||||||
|
# No score exists, so we need to create it
|
||||||
|
playlist_source = lilypond_source.playlist_source(playlist=lilypond_playlist)
|
||||||
|
out_filepath = files_service.get_playlist_filename(playlist_score_hash)
|
||||||
|
async with files_service.tmp_file(content=playlist_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
|
||||||
|
|
||||||
|
return playlists.PlaylistScore(
|
||||||
|
img_url=None, # Only PDF generation for now
|
||||||
|
pdf_url=files_service.get_db_file_path(pdf_filepath),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def add_playlist_score_to_playlist(playlist: playlists.Playlist) -> playlists.Playlist:
|
||||||
|
if score := await get_or_create_playlist_score(playlist=playlist):
|
||||||
|
return dataclasses.replace(
|
||||||
|
playlist,
|
||||||
|
playlist_score=score,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return playlist
|
||||||
|
|||||||
@@ -184,8 +184,8 @@ def _create_session_playlists(session_id: int):
|
|||||||
|
|
||||||
setlist_name, slowjam_name = _get_playlist_names(session=session)
|
setlist_name, slowjam_name = _get_playlist_names(session=session)
|
||||||
|
|
||||||
setlist_playlist_id = playlists_write.create_playlist(name=setlist_name, con=None)
|
setlist_playlist_id = playlists_write.create_playlist(name=setlist_name)
|
||||||
slowjam_playlist_id = playlists_write.create_playlist(name=slowjam_name, con=None)
|
slowjam_playlist_id = playlists_write.create_playlist(name=slowjam_name)
|
||||||
|
|
||||||
with get_connection() as con:
|
with get_connection() as con:
|
||||||
session_playlists.insert_playlist(
|
session_playlists.insert_playlist(
|
||||||
|
|||||||
71
scripts/06_migrate_session_playlist_names.py
Executable file
71
scripts/06_migrate_session_playlist_names.py
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
from folkugat_web.dal.sql import get_connection
|
||||||
|
from folkugat_web.dal.sql.sessions import playlists as session_playlists
|
||||||
|
from folkugat_web.model.playlists import PlaylistType
|
||||||
|
from folkugat_web.services import sessions as sessions_service
|
||||||
|
from folkugat_web.dal.sql.playlists import write as playlists_write
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main migration function."""
|
||||||
|
with get_connection() as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
query = """ SELECT
|
||||||
|
sp.playlist_id,
|
||||||
|
sp.session_id,
|
||||||
|
s.date,
|
||||||
|
sp.playlist_type,
|
||||||
|
p.name as current_name
|
||||||
|
FROM session_playlists sp
|
||||||
|
JOIN sessions s ON sp.session_id = s.id
|
||||||
|
JOIN playlists p ON sp.playlist_id = p.id
|
||||||
|
WHERE p.name IS NULL OR p.name = ''
|
||||||
|
ORDER BY sp.session_id, sp.playlist_type """
|
||||||
|
_ = cur.execute(query)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
print("No session playlists need renaming.")
|
||||||
|
return
|
||||||
|
|
||||||
|
updated_count = 0
|
||||||
|
for row in rows:
|
||||||
|
playlist_id, session_id, session_date, playlist_type, current_name = row
|
||||||
|
|
||||||
|
# Parse session date properly
|
||||||
|
session_date = sessions_service.get_date_names(
|
||||||
|
datetime.date.fromisoformat(session_date)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate proper name based on playlist type
|
||||||
|
if playlist_type == PlaylistType.SESSION_SETLIST.value:
|
||||||
|
new_name = f"Sessió del {session_date.day} de {session_date.month_name} de {session_date.year}"
|
||||||
|
elif playlist_type == PlaylistType.SESSION_SLOWJAM.value:
|
||||||
|
new_name = f"Slow Jam del {session_date.day} de {session_date.month_name} de {session_date.year}"
|
||||||
|
else:
|
||||||
|
print(f"Unknown playlist type {playlist_type} for playlist {playlist_id}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Update the playlist name using existing function
|
||||||
|
playlists_write.update_playlist_name(
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
name=new_name,
|
||||||
|
con=con
|
||||||
|
)
|
||||||
|
|
||||||
|
current_name_display = current_name or "None"
|
||||||
|
print(
|
||||||
|
f"Updated playlist {playlist_id} (session {session_id}) "
|
||||||
|
f"from '{current_name_display}' to '{new_name}'"
|
||||||
|
)
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
con.commit()
|
||||||
|
print(f"Migration completed! Updated {updated_count} playlist names.")
|
||||||
|
print("DONE!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user