mirror of
https://gitlab.com/parroquia-san-leandro/cancionero-web.git
synced 2025-04-27 07:36:17 +02:00
* Redirection wasn't being encoded properly * CSS to LESS transition * Improved Makefile * Explained red color in index * Unified link and button styles
238 lines
8.6 KiB
Python
238 lines
8.6 KiB
Python
import re
|
|
import functools as ft
|
|
from datetime import datetime
|
|
import locale
|
|
|
|
|
|
def join_list(the_list, separator="\n"):
|
|
return ft.reduce(lambda x, y: x + (separator if x else "") + str(y), the_list, "")
|
|
|
|
|
|
def readfile(file):
|
|
with open(file, 'r') as f:
|
|
return join_list(f.readlines(), '')
|
|
|
|
|
|
locale.setlocale(locale.LC_ALL, "es_ES.UTF-8")
|
|
song_template = readfile("res/song.html")
|
|
|
|
|
|
class Song:
|
|
def __init__(self, name, number, author=None, origin=None, latex_file=None, audios=None, capo=0, category=None):
|
|
if audios is None:
|
|
audios = []
|
|
self.verses = []
|
|
self.name = name.replace("\\\\", "-").replace("\\", "")
|
|
self.number = number
|
|
self.author = author
|
|
self.origin = origin
|
|
self.latex_file = latex_file
|
|
self.audios = audios
|
|
self.capo = capo
|
|
self.category = category
|
|
|
|
def __str__(self):
|
|
return song_template.format(
|
|
name=self.name,
|
|
author="<div>Autor: %s</div>" % self.author if self.author else "",
|
|
origin="<div>Basada en: %s</div>" % self.origin if self.origin else "",
|
|
capo_settings="""<div><span class="capo">Tono original: Cejilla {s.capo}</span>
|
|
<button style="margin-left: 0.5em;" onclick="transpose({s.capo})">Transponer para quitarla</button></div>"""
|
|
.format(s=self) if self.capo != 0 else "",
|
|
song_html=join_list(self.verses),
|
|
audios_header="<h3>Audios</h3>" if len(self.audios) > 0 else "",
|
|
audios_html=join_list(self.audios),
|
|
latex_file=self.latex_file)
|
|
|
|
def set_capo(self, capo):
|
|
self.capo = capo
|
|
|
|
def add_audio(self, audio):
|
|
assert isinstance(audio, Audio)
|
|
self.audios.append(audio)
|
|
|
|
def add_verse(self, verse):
|
|
assert isinstance(verse, Verse)
|
|
self.verses.append(verse)
|
|
|
|
def get_url(self):
|
|
return "%03d %s" % (self.number, self.name.replace("¿", "").replace("?", ""))
|
|
|
|
def chorded(self):
|
|
for v in self.verses:
|
|
if v.chorded():
|
|
return True
|
|
return False
|
|
|
|
|
|
class Verse:
|
|
def __init__(self, is_chorus=False):
|
|
self.is_chorus = is_chorus
|
|
self.lines = []
|
|
|
|
def __str__(self):
|
|
return """
|
|
<div class="%s">
|
|
%s
|
|
</div>
|
|
""" % ("chorus" if self.is_chorus else "verse", join_list([str(l) for l in self.lines], "\n<br>\n"))
|
|
|
|
def add_line(self, line):
|
|
assert isinstance(line, Line)
|
|
self.lines.append(line)
|
|
|
|
def chorded(self):
|
|
for line in self.lines:
|
|
if line.chorded():
|
|
return True
|
|
return False
|
|
|
|
|
|
class Line:
|
|
ECHO_BEGIN = '<span class="echo">'
|
|
ECHO_END = '</span>'
|
|
|
|
def __init__(self, text, extras):
|
|
self.text = text
|
|
self.extras = extras
|
|
self.chord_arr = []
|
|
self.lyric_arr = []
|
|
self.build()
|
|
self.remove_brackets()
|
|
|
|
def __str__(self):
|
|
assert len(self.chord_arr) == len(self.lyric_arr)
|
|
return join_list(["""<table class="chordedline"><tr class="chord"><td rowspan="%s">%s%s</td></tr>%s</table>"""
|
|
% (self.chord_arr[i]["rowspan"] if "rowspan" in self.chord_arr[i] else 1,
|
|
'<span class="%s"></span>' % self.chord_arr[i]["class"] if "class" in self.chord_arr[i] else "",
|
|
self.chord_arr[i]["chord"] if "chord" in self.chord_arr[i] else "",
|
|
'<tr class="lyric"><td>%s</td></tr>' % self.lyric_arr[i] if "rowspan" not in self.chord_arr[i] else ''
|
|
) for i in range(len(self.chord_arr))], separator='')
|
|
|
|
def add_chord(self, index, chord):
|
|
self.add_item(index, "chord", chord)
|
|
|
|
def add_dir_rep(self, index, data):
|
|
self.add_item(index, "dir-rep", data)
|
|
|
|
def add_rep(self, index, data):
|
|
self.add_item(index, "rep", data)
|
|
|
|
def add_echo(self, index):
|
|
self.add_item(index, "echo", None)
|
|
|
|
def add_item(self, index, the_type, data):
|
|
if index not in self.extras:
|
|
self.extras[index] = []
|
|
self.extras[index].append({'type': the_type, 'data': data})
|
|
|
|
def build(self):
|
|
self.chord_arr = [{}]
|
|
self.lyric_arr = [""]
|
|
|
|
inside_echo = False
|
|
mid = True
|
|
for i in range(len(self.text) + 1):
|
|
for e in self.extras[i] if i in self.extras else []:
|
|
if e["type"] == "chord":
|
|
floating = (i < len(self.text) and self.text[i] == ' ' and
|
|
(i == 0 or self.text[i - 1] == ' ')) or \
|
|
(i >= len(self.text) and self.text[i - 1] == ' ')
|
|
self.chord_arr.append({'chord': e["data"]})
|
|
if inside_echo and self.lyric_arr[-1] != '':
|
|
self.lyric_arr[-1] += Line.ECHO_END
|
|
if floating:
|
|
self.lyric_arr.append('')
|
|
self.chord_arr.append({})
|
|
self.lyric_arr.append(Line.ECHO_BEGIN if inside_echo else '')
|
|
mid = True
|
|
elif e["type"] == "dir-rep":
|
|
self.chord_arr.append({'class': e["data"], 'rowspan': 2})
|
|
self.lyric_arr.append('')
|
|
if mid and inside_echo:
|
|
self.lyric_arr[-1] += Line.ECHO_END
|
|
mid = False
|
|
elif e["type"] == "rep":
|
|
self.lyric_arr.append('<span class="rep">(x%d)</span>' % (e["data"]))
|
|
self.chord_arr.append({})
|
|
mid = False
|
|
elif e["type"] == "echo":
|
|
if not mid:
|
|
self.lyric_arr.append(Line.ECHO_BEGIN if inside_echo else '')
|
|
mid = True
|
|
start = Line.ECHO_BEGIN + '('
|
|
end = ')' + Line.ECHO_END
|
|
self.lyric_arr[-1] += end if inside_echo else start
|
|
inside_echo = not inside_echo
|
|
else:
|
|
print("Unrecognized type", e["type"])
|
|
if i != len(self.text):
|
|
if not mid:
|
|
self.chord_arr.append({})
|
|
self.lyric_arr.append(Line.ECHO_BEGIN if inside_echo else '')
|
|
mid = True
|
|
self.lyric_arr[-1] += self.text[i]
|
|
for i in range(len(self.lyric_arr)):
|
|
self.lyric_arr[i] = re.sub(r"(^ | $)", " ", self.lyric_arr[i])
|
|
|
|
def remove_brackets(self):
|
|
self.lyric_arr = [l.replace('}', '').replace('{', '') for l in self.lyric_arr]
|
|
|
|
def chorded(self):
|
|
for key in self.extras:
|
|
for i in self.extras[key]:
|
|
if i["type"] == "chord":
|
|
return True
|
|
return False
|
|
|
|
|
|
class Chord:
|
|
N_CHORDS = 12
|
|
CHORDS_LAT = ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si']
|
|
CHORDS_ENG = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B&', 'B']
|
|
ENG_INDEX = {'C': 0, 'C#': 1, 'D&': 1, 'D': 2, 'D#': 3, 'E&': 3, 'E': 4, 'F&': 4, 'F': 5, 'E#': 5, 'F#': 6, 'G&': 6, 'G': 7, 'G#': 8, 'A&': 8, 'A': 9, 'A#': 10, 'B&': 10, 'B': 11, 'C&': 11, 'B#': 0}
|
|
|
|
def __init__(self, text, base_transpose=0):
|
|
self.text = text
|
|
self.chords = []
|
|
self.base_transpose = base_transpose
|
|
ignore = False
|
|
for i, char in enumerate(text):
|
|
if ignore:
|
|
ignore = False
|
|
continue
|
|
if "A" <= char <= "G":
|
|
if len(text) > i + 1 and (text[i + 1] == "#" or text[i + 1] == "&"):
|
|
self.chords.append({'text': char + text[i + 1], 'chord': True})
|
|
ignore = True
|
|
else:
|
|
self.chords.append({'text': char, 'chord': True})
|
|
else:
|
|
self.chords.append({'text': char, 'chord': False})
|
|
|
|
def __str__(self):
|
|
res = ""
|
|
for c in self.chords:
|
|
if c['chord']:
|
|
res += "<span class='c'>%s</span>" % Chord.CHORDS_LAT[Chord.ENG_INDEX[c['text']]]
|
|
else:
|
|
res += c['text']
|
|
return res
|
|
|
|
|
|
class Audio:
|
|
def __init__(self, date, audio_file):
|
|
assert isinstance(date, datetime)
|
|
self.date = date
|
|
self.date_text = date.strftime("%A %-d de %B del %Y")
|
|
self.audio_file = audio_file
|
|
|
|
def __str__(self):
|
|
return """
|
|
<div>
|
|
Audio del %s <a href="%s"><span>Descargar</span></a>
|
|
<audio controls style='width: 100%%;'>
|
|
<source src='%s' type='audio/mpeg'/>
|
|
</audio>
|
|
</div>
|
|
""" % (self.date_text, self.audio_file, self.audio_file)
|