Rediseño de la interfaz y arreglos menores

- ¡Ahora con tema oscuro! Se activa según el navegador del usuario.
- URL de audios simplificada.
- Nuevos iconos para canciones con acordes/audios.
- Enlaces de la cabecera actualizados.
- Siempre mostramos los ajustes.
- Insertar espacio forzoso para separar algunas palabras que se juntaban.
- Soporte para canciones con dos líneas de acordes (e.g. Engrandece).
This commit is contained in:
Carlos Galindo 2023-09-08 00:37:10 +02:00
parent e838b066de
commit 03522304fa
15 changed files with 203 additions and 83 deletions

View file

@ -14,11 +14,11 @@ public:
mkdir public mkdir public
public/index.html: $(ALL_TEMPLATES) $(PY_SRC) public/index.html: $(ALL_TEMPLATES) $(PY_SRC)
python3 src/latex_scanner.py --latex latex/cancionero.tex --audios audios/Canciones --other-latex latex/canciones/ python3 src/latex_scanner.py --latex latex/cancionero.tex --audios audios --other-latex latex/canciones/
public/audios: audios public public/audios: audios public
rm -f public/audios rm -f public/audios
ln -s ../audios/Canciones public/audios #ln -s ../audios/Canciones public/audios
public/main.css: res/less/main.less res/less/colors.less public/main.css: res/less/main.less res/less/colors.less
lessc $< $@ lessc $< $@

View file

@ -1,12 +1,19 @@
@nochordscolor: #FBB; @background: #FFF;
@nochordscolor-hover: #FAA; @background-dark: darken(@background, 100%);
@title: #369; @title: #369;
@title-dark: lighten(@title, 50%);
@subtitle: #444; @subtitle: #444;
@subtitle-dark: lighten(@subtitle, 100%);
@text: #333; @text: #333;
@text-dark: lighten(@text, 100%);
@text-hover: #607D8B; @text-hover: #607D8B;
@text-hover-dark: @text-hover;
@secondary: #EEE; @secondary: #EEE;
@secondary-dark: #333;
@secondary-hover: #CCC; @secondary-hover: #CCC;
@secondary-hover-dark: #444;
@chord-color: darkgreen; @chord-color: darkgreen;
@chord-color-dark: lightgreen;
@transition: .5s; @transition: .5s;
@border-radius: 4px; @border-radius: 10px;

View file

@ -7,24 +7,22 @@
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
> li { > li {
background-color: @secondary; display: flex;
gap: 5px;
margin: .5em; margin: .5em;
padding: .6em; padding: .6em;
border: 1px solid @text;
border-radius: @border-radius; border-radius: @border-radius;
.number { @media (prefers-color-scheme: dark) {
font-weight: bolder; border: 1px solid @text-dark;
color: black;
} }
.number { font-weight: bolder; }
.name { flex-grow: 99; }
&:hover { &:hover {
color: @text-hover; background-color: @secondary;
background-color: @secondary-hover;
transition-duration: @transition; transition-duration: @transition;
} @media (prefers-color-scheme: dark) {
&.noChords { background-color: @secondary-dark;
background-color: @nochordscolor;
&:hover {
color: @text-hover;
background-color: @nochordscolor-hover;
} }
} }
} }
@ -38,9 +36,18 @@ ul.songs {
margin: .2em; margin: .2em;
} }
} }
span.noChords { img.guitar-icon, img.music-icon {
background-color: @nochordscolor; width: 1em;
padding: 0.3em 0.4em; padding: 0 0.2em;
border-radius: @border-radius;
display: inline-block;
} }
img.guitar-icon {
/* Green color filter */
filter: invert(42%) sepia(93%) saturate(1352%) hue-rotate(55deg) brightness(119%) contrast(119%);
}
img.music-icon {
/* Orange color filter */
filter: invert(46%) sepia(100%) saturate(681%) hue-rotate(360deg) brightness(106%) contrast(105%);
}
p { img.guitar-icon, img.music-icon {
vertical-align: -0.25em;
}}

View file

@ -6,8 +6,14 @@ h1 {
width: max-content; width: max-content;
max-width: 100%; max-width: 100%;
text-align: center; text-align: center;
@media (prefers-color-scheme: dark) {
color: @title-dark;
}
> a, > a:hover, > a:active, > a:visited { > a, > a:hover, > a:active, > a:visited {
color: @title; color: @title;
@media (prefers-color-scheme: dark) {
color: @title-dark;
}
} }
} }
@ -15,6 +21,9 @@ h2, h3 {
color: @subtitle; color: @subtitle;
font-weight: lighter; font-weight: lighter;
margin-bottom: 0; margin-bottom: 0;
@media (prefers-color-scheme: dark) {
color: @subtitle-dark;
}
} }
footer { footer {
@ -24,8 +33,13 @@ footer {
body { body {
font-family: Helvetica, Arial, sans-serif; font-family: Helvetica, Arial, sans-serif;
color: @text; color: @text;
background: @background;
max-width: 75em; max-width: 75em;
margin: 1em; margin: 1em;
@media (prefers-color-scheme: dark) {
color: @text-dark;
background: @background-dark;
}
@media (min-width: 75em + 1em) { @media (min-width: 75em + 1em) {
margin: auto; margin: auto;
} }
@ -43,6 +57,9 @@ nav {
} }
} }
nav a > span {
border-width: 4px;
}
a { a {
text-decoration: none; text-decoration: none;
> span { > span {
@ -50,11 +67,20 @@ a {
padding: 0.5em; padding: 0.5em;
margin: 0.2em; margin: 0.2em;
display: inline-block; display: inline-block;
background: @secondary; border-radius: @border-radius * 5;
border-radius: @border-radius; border: 1px solid @text;
@media (prefers-color-scheme: dark) {
color: @text-dark;
border-color: @text-dark;
}
&:hover { &:hover {
background: @secondary-hover; background: @secondary-hover;
transition-duration: @transition; transition-duration: @transition;
@media (prefers-color-scheme: dark) {
background: @secondary-hover-dark;
}
} }
&#nav-web { border-color: #6ecf00; }
&#nav-audios { border-color: orange; }
} }
} }

View file

@ -5,10 +5,6 @@ table.chordedline {
display: inline-block; display: inline-block;
} }
.song div {
padding: 0.5em 0;
}
.chordedline td { .chordedline td {
padding: 0px; padding: 0px;
} }
@ -16,11 +12,21 @@ table.chordedline {
.chord { .chord {
font-style: italic; font-style: italic;
color: @chord-color; color: @chord-color;
@media (prefers-color-scheme: dark) {
color: @chord-color-dark;
}
} }
div.chorus { .song div {
margin: 0.5em 0;
}
div.chorus, p.chorus {
font-weight: bold; font-weight: bold;
border-left: black 2px solid; border-left: @text 2px solid;
@media (prefers-color-scheme: dark) {
border-left: @text-dark 2px solid;
}
padding-left: 0.5em; padding-left: 0.5em;
div { div {
padding-left: 0.5em; padding-left: 0.5em;
@ -37,6 +43,10 @@ div.chorus {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
height: 2em; height: 2em;
@media (prefers-color-scheme: dark) {
/* Black to white filter for svgs */
filter: invert(100%);
}
} }
.lrep { .lrep {
@ -63,10 +73,18 @@ button, select#transposeSelect {
padding: .4em; padding: .4em;
border-style: none; border-style: none;
border-radius: @border-radius; border-radius: @border-radius;
color: @text;
background-color: @secondary; background-color: @secondary;
@media (prefers-color-scheme: dark) {
color: @text-dark;
background-color: @secondary-dark;
}
&:hover { &:hover {
background-color: @secondary-hover; background-color: @secondary-hover;
transition-duration: @transition; transition-duration: @transition;
@media (prefers-color-scheme: dark) {
background-color: @secondary-hover-dark;
}
} }
} }
@ -75,4 +93,7 @@ select#transposeSelect {
-moz-appearance: none; -moz-appearance: none;
font-weight: bold; font-weight: bold;
color: black; color: black;
@media (prefers-color-scheme: dark) {
color: @text-dark;
}
} }

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zm2 226.3c37.1 22.4 62 63.1 62 109.7s-24.9 87.3-62 109.7c-7.6 4.6-17.4 2.1-22-5.4s-2.1-17.4 5.4-22C269.4 401.5 288 370.9 288 336s-18.6-65.5-46.5-82.3c-7.6-4.6-10-14.4-5.4-22s14.4-10 22-5.4zm-91.9 30.9c6 2.5 9.9 8.3 9.9 14.8V400c0 6.5-3.9 12.3-9.9 14.8s-12.9 1.1-17.4-3.5L113.4 376H80c-8.8 0-16-7.2-16-16V312c0-8.8 7.2-16 16-16h33.4l35.3-35.3c4.6-4.6 11.5-5.9 17.4-3.5zm51 34.9c6.6-5.9 16.7-5.3 22.6 1.3C249.8 304.6 256 319.6 256 336s-6.2 31.4-16.3 42.7c-5.9 6.6-16 7.1-22.6 1.3s-7.1-16-1.3-22.6c5.1-5.7 8.1-13.1 8.1-21.3s-3.1-15.7-8.1-21.3c-5.9-6.6-5.3-16.7 1.3-22.6z"/></svg>

After

Width:  |  Height:  |  Size: 946 B

1
res/static/gear.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
res/static/guitar.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M465 7c-9.4-9.4-24.6-9.4-33.9 0L383 55c-2.4 2.4-4.3 5.3-5.5 8.5l-15.4 41-77.5 77.6c-45.1-29.4-99.3-30.2-131 1.6c-11 11-18 24.6-21.4 39.6c-3.7 16.6-19.1 30.7-36.1 31.6c-25.6 1.3-49.3 10.7-67.3 28.6C-16 328.4-7.6 409.4 47.5 464.5s136.1 63.5 180.9 18.7c17.9-17.9 27.4-41.7 28.6-67.3c.9-17 15-32.3 31.6-36.1c15-3.4 28.6-10.5 39.6-21.4c31.8-31.8 31-85.9 1.6-131l77.6-77.6 41-15.4c3.2-1.2 6.1-3.1 8.5-5.5l48-48c9.4-9.4 9.4-24.6 0-33.9L465 7zM208 256a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/></svg>

After

Width:  |  Height:  |  Size: 727 B

1
res/static/music.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M499.1 6.3c8.1 6 12.9 15.6 12.9 25.7v72V368c0 44.2-43 80-96 80s-96-35.8-96-80s43-80 96-80c11.2 0 22 1.6 32 4.6V147L192 223.8V432c0 44.2-43 80-96 80s-96-35.8-96-80s43-80 96-80c11.2 0 22 1.6 32 4.6V200 128c0-14.1 9.3-26.6 22.8-30.7l320-96c9.7-2.9 20.2-1.1 28.3 5z"/></svg>

After

Width:  |  Height:  |  Size: 510 B

View file

@ -1,6 +1,6 @@
<footer> <footer>
<p> <p>
&copy; 2020 Carlos Galindo, Parroquia San Leandro &copy; 2023 Carlos Galindo, Parroquia San Leandro
<a href="https://gitlab.com/parroquia-san-leandro/cancionero-web"><span>Código fuente</span></a> <a href="https://gitlab.com/parroquia-san-leandro/cancionero-web"><span>Código fuente</span></a>
</p> </p>
</footer> </footer>

View file

@ -2,9 +2,9 @@
<h1><a href="{{ path }}">Cancionero San Leandro</a></h1> <h1><a href="{{ path }}">Cancionero San Leandro</a></h1>
<nav class="nav"> <nav class="nav">
<ul> <ul>
<li><a href="https://sanleandro-obispo.net/"><span>Parroquia San Leandro</span></a></li> <li><a href="https://sanleandrovalencia.es/"><span id="nav-web">Parroquia San Leandro</span></a></li>
<li><a href="https://nube.sanleandro-obispo.net/s/X23Jzz5A6dpCfr2"><span>Grabaciones</span></a></li> <li><a href="https://nube.sanleandrovalencia.es/s/X23Jzz5A6dpCfr2"><span id="nav-audios">Grabaciones</span></a></li>
<li><a href="https://sanleandro-obispo.net/cancionero/"><span>Cancionero en PDF</span></a></li> <li><a href="https://sanleandrovalencia.es/cancionero/"><span>Cancionero en PDF</span></a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View file

@ -15,17 +15,25 @@
</a> </a>
{% endfor %} {% endfor %}
</ul> </ul>
Las canciones sin acordes están marcadas en <span class="noChords">rojo</span>. <p><strong>Leyenda</strong>: <img src="music.svg" class="music-icon"> tiene música grabada; <img src="guitar.svg" class="guitar-icon"> tiene acordes.</p>
<ol class="songs"> <ol class="songs">
{% for category, songs in sorted_categories.items %} {% for category, songs in sorted_categories.items %}
<h3 id="{{ category|slugify }}">{{ category|title }}</h3> <h3 id="{{ category|slugify }}">{{ category|title }}</h3>
{% for song in songs %} {% for song in songs %}
<a href="{{ song.url }}"> <a href="{{ song.url }}">
<li {% if not song.chorded %}class="noChords" {% endif %}> <li>
<span class="number">{{ song.number }}.</span> <span class="number">{{ song.number }}.</span>
{{ song.name }} <span class="name">
{% if song.author %} por {{ song.author }} {% endif %} {{ song.name }}
{% if song.origin %} basada en {{ song.origin }} {% endif %} {% if song.author %} por {{ song.author }} {% endif %}
{% if song.origin %} basada en {{ song.origin }} {% endif %}
</span>
{% if song.has_audios %}
<img src="music.svg" class="music-icon filter-green">
{% endif %}
{% if song.chorded %}
<img src="guitar.svg" class="guitar-icon filter-green">
{% endif %}
</li> </li>
</a> </a>
{% endfor %} {% endfor %}
@ -34,4 +42,4 @@
</main> </main>
{% include "footer.html" %} {% include "footer.html" %}
</body> </body>
</html> </html>

View file

@ -16,8 +16,7 @@
{% if song.origin %} {% if song.origin %}
<span>basada en: <strong>{{ song.origin }}</strong></span> <span>basada en: <strong>{{ song.origin }}</strong></span>
{% endif %} {% endif %}
<details> <h3>Ajustes</h3>
<summary><h3 style="display: inline-block;">Ajustes</h3></summary>
<div> <div>
<label>Cambiar tamaño de letra </label> <label>Cambiar tamaño de letra </label>
<button class="small" onclick="size(-1)">-</button> <button class="small" onclick="size(-1)">-</button>
@ -29,7 +28,7 @@
<label><input id="showChords" type="checkbox" checked onchange="showChords(this.checked)"/> Mostrar acordes</label> <label><input id="showChords" type="checkbox" checked onchange="showChords(this.checked)"/> Mostrar acordes</label>
</div> </div>
<div id="transposeControls" class="showChords"> <div id="transposeControls" class="showChords">
<label>Transponer acordes </label> <label for="transposeSelect">Transponer acordes </label>
<button class="small" onclick="transposeAdd(-2)">-2</button> <button class="small" onclick="transposeAdd(-2)">-2</button>
<button class="small" onclick="transposeAdd(-1)">-1</button> <button class="small" onclick="transposeAdd(-1)">-1</button>
<select id="transposeSelect" disabled> <select id="transposeSelect" disabled>
@ -58,16 +57,15 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
</details>
<h3>Canción</h3> <h3>Canción</h3>
<div id="wholeSongDiv"> <div id="wholeSongDiv">
<div id="songLyrics" class="showLyrics" style="display: none;"> <div id="songLyrics" class="showLyrics" style="display: none;">
{% for verse in song.verses %} {% for verse in song.verses %}
<div class="{{ verse.kind }}"> <p class="{{ verse.kind }}">
{% for line in verse.lines %} {% for line in verse.lines %}
{{ line }}<br/> {{ line }}<br/>
{% endfor %} {% endfor %}
</div> </p>
{% endfor %} {% endfor %}
</div> </div>
<div id="songChords" class="showChords"> <div id="songChords" class="showChords">
@ -82,13 +80,45 @@
{% if chord.class %} {% if chord.class %}
<span class="{{ chord.class }}"></span> <span class="{{ chord.class }}"></span>
{% endif %} {% endif %}
{% for c in chord.chord.items %} {% if chord.chord.trfmt == "normal" %}
{% if c.chord %} {% for c in chord.chord.items_t %}
<span class="c">{{ c.text|safe }}</span> {% if c.chord %}
{% else %} <span class="c">{{ c.text|safe }}</span>
<span>{{ c.text|safe }}</span> {% else %}
<span>{{ c.text|safe }}</span>
{% endif %}
{% endfor %}
{% endif %}
{% if chord.chord.trfmt == "double" %}
<span>
{% for c in chord.chord.items_n %}
{% if c.chord %}
<span class="c">{{ c.text|safe }}</span>
{% else %}
<span>{{ c.text|safe }}</span>
{% endif %}
{% endfor %}
<br/>
{% for c in chord.chord.items_t %}
{% if c.chord %}
<span class="c">{{ c.text|safe }}</span>
{% else %}
<span>{{ c.text|safe }}</span>
{% endif %}
{% endfor %}
<span/>
{% endif %}
{% if chord.chord.trfmt == "hover" %}
<span>
{% for c in chord.chord.items_n %}
{% if c.chord %}
<span class="c">{{ c.text|safe }}</span>
{% else %}
<span>{{ c.text|safe }}</span>
{% endif %}
{% endfor %}
<br/>&nbsp;</span>
{% endif %} {% endif %}
{% endfor %}
</td> </td>
</tr> </tr>
<tr class="lyric"> <tr class="lyric">

View file

@ -90,6 +90,7 @@ class SongLoader:
memorizing = False memorizing = False
replay_index = 0 replay_index = 0
transpose = 0 transpose = 0
trfmt = "normal"
for line in open(song_file, "r").readlines(): for line in open(song_file, "r").readlines():
# Remove newline # Remove newline
@ -117,13 +118,11 @@ class SongLoader:
continue continue
# Command lookup # Command lookup
re_transpose_match = re.match(r"\\transpose *?{(-?\d+?)}", remain) if re_transpose_match := re.match(r"\\transpose *?{(-?\d+?)}", remain):
if re_transpose_match:
text = beginning + text[i + len(re_transpose_match.group(0)):] text = beginning + text[i + len(re_transpose_match.group(0)):]
transpose = int(re_transpose_match.group(1)) transpose = int(re_transpose_match.group(1))
continue continue
re_song_begin_match = re.match(r"\\beginsong *?{(.*?)}(\[.*?])?", remain) if re_song_begin_match := re.match(r"\\beginsong *?{(.*?)}(\[.*?])?", remain):
if re_song_begin_match:
text = beginning + text[i + len(re_song_begin_match.group(0)):] text = beginning + text[i + len(re_song_begin_match.group(0)):]
if current_song is not None: if current_song is not None:
print("error end-begin song! %s at %s" % (line, song_file)) print("error end-begin song! %s at %s" % (line, song_file))
@ -135,6 +134,7 @@ class SongLoader:
category=self.category, category=self.category,
latex_file=song_file[song_file.index('/') + 1:]) latex_file=song_file[song_file.index('/') + 1:])
transpose = 0 transpose = 0
trfmt = "normal"
memory = None memory = None
memorizing = False memorizing = False
replay_index = 0 replay_index = 0
@ -148,8 +148,7 @@ class SongLoader:
current_song = None current_song = None
self.index += 1 self.index += 1
continue continue
re_verse_cmd_match = re.match(r"\\(begin|end)(verse|chorus)", remain) if re_verse_cmd_match := re.match(r"\\(begin|end)(verse|chorus)", remain):
if re_verse_cmd_match:
text = beginning + text[i + len(re_verse_cmd_match.group(0)):] text = beginning + text[i + len(re_verse_cmd_match.group(0)):]
is_chorus = re_verse_cmd_match.group(2) == "chorus" is_chorus = re_verse_cmd_match.group(2) == "chorus"
if current_song is None: if current_song is None:
@ -170,8 +169,7 @@ class SongLoader:
current_song.add_verse(current_verse) current_song.add_verse(current_verse)
current_verse = None current_verse = None
continue continue
re_capo_match = re.match(r"\\capo{(\d+?)}", remain) if (re_capo_match := re.match(r"\\capo{(\d+?)}", remain)) and current_song:
if re_capo_match and current_song:
text = beginning + text[i + len(re_capo_match.group(0)):] text = beginning + text[i + len(re_capo_match.group(0)):]
current_song.set_capo(int(re_capo_match.group(1))) current_song.set_capo(int(re_capo_match.group(1)))
continue continue
@ -182,8 +180,7 @@ class SongLoader:
ignore = True ignore = True
text = beginning + text[i + len("\\else"):] text = beginning + text[i + len("\\else"):]
continue continue
re_echo_match = re.match(r"\\echo[ \t]*?{((.|{.*?})*?)}", remain) if re_echo_match := re.match(r"\\echo[ \t]*?{((.|{.*?})*?)}", remain):
if re_echo_match:
text = beginning + re_echo_match.group(1) + "\\echoend" + text[i + len(re_echo_match.group(0)):] text = beginning + re_echo_match.group(1) + "\\echoend" + text[i + len(re_echo_match.group(0)):]
extra_put(extras, i, "echo") extra_put(extras, i, "echo")
continue continue
@ -191,10 +188,9 @@ class SongLoader:
text = beginning + text[i + len("\\echoend"):] text = beginning + text[i + len("\\echoend"):]
extra_put(extras, i, "echo") extra_put(extras, i, "echo")
continue continue
re_chord_match = re.match(r"\\\[(.+?)]", remain) if re_chord_match := re.match(r"\\\[(.+?)]", remain):
if re_chord_match:
text = beginning + text[i + len(re_chord_match.group(0)):] text = beginning + text[i + len(re_chord_match.group(0)):]
c = Chord(re_chord_match.group(1), transpose) c = Chord(re_chord_match.group(1), transpose, trfmt)
extra_put(extras, i, "chord", c) extra_put(extras, i, "chord", c)
if memorizing: if memorizing:
memory.append(c) memory.append(c)
@ -205,13 +201,11 @@ class SongLoader:
extra_put(extras, i, "chord", memory[replay_index]) extra_put(extras, i, "chord", memory[replay_index])
replay_index += 1 replay_index += 1
continue continue
re_dir_rep_match = re.match(r"\\([lr]rep)", remain) if re_dir_rep_match := re.match(r"\\([lr]rep)", remain):
if re_dir_rep_match:
text = beginning + text[i + len(re_dir_rep_match.group(0)):] text = beginning + text[i + len(re_dir_rep_match.group(0)):]
extra_put(extras, i, "dir-rep", re_dir_rep_match.group(1)) extra_put(extras, i, "dir-rep", re_dir_rep_match.group(1))
continue continue
re_rep_match = re.match(r"\\rep{(\d+?)}", remain) if re_rep_match := re.match(r"\\rep{(\d+?)}", remain):
if re_rep_match:
text = beginning + text[i + len(re_rep_match.group(0)):] text = beginning + text[i + len(re_rep_match.group(0)):]
extra_put(extras, i, 'rep', int(re_rep_match.group(1))) extra_put(extras, i, 'rep', int(re_rep_match.group(1)))
continue continue
@ -224,6 +218,19 @@ class SongLoader:
text = beginning + text[i + len("\\replay"):] text = beginning + text[i + len("\\replay"):]
replay_index = 0 replay_index = 0
continue continue
# Double or single transpose mode
if re_trfmt := re.match(r"\\renewcommand{\\trchordformat}\[2\]{\\vbox{\\hbox{#1}\\hbox{#2}}}", remain):
text = beginning + text[i + len(re_trfmt.group(0)):]
trfmt = "double"
continue
if re_trfmt := re.match(r"\\renewcommand{\\trchordformat}\[2\]{\\vbox{\\hbox{#1}\\hbox{}}}", remain):
text = beginning + text[i + len(re_trfmt.group(0)):]
trfmt = "hover"
continue
if re_trfmt := re.match(r"\\renewcommand{\\trchordformat}\[2\]{\\hbox{#2}}", remain):
text = beginning + text[i + len(re_trfmt.group(0)):]
trfmt = "normal"
continue
# Command lookup end, removing any unrecognized command # Command lookup end, removing any unrecognized command
re_macro_match = re.match(r"\\([^ \t{\[]+)[ \t]*?({.*?}|\[.*?])*", remain) re_macro_match = re.match(r"\\([^ \t{\[]+)[ \t]*?({.*?}|\[.*?])*", remain)
if re_macro_match: if re_macro_match:

View file

@ -40,7 +40,7 @@ class Song:
self.verses.append(verse) self.verses.append(verse)
def url(self): def url(self):
return "%03d %s" % (self.number, self.name.replace("¿", "").replace("?", "")) return "%03d %s/" % (self.number, self.name.replace("¿", "").replace("?", ""))
def chorded(self): def chorded(self):
for v in self.verses: for v in self.verses:
@ -48,6 +48,9 @@ class Song:
return True return True
return False return False
def has_audios(self):
return len(self.audios) > 0
class Verse: class Verse:
def __init__(self, is_chorus=False): def __init__(self, is_chorus=False):
@ -149,6 +152,7 @@ class Line:
self.lyric_arr.append(Line.ECHO_BEGIN if inside_echo else '') self.lyric_arr.append(Line.ECHO_BEGIN if inside_echo else '')
mid = True mid = True
self.lyric_arr[-1] += self.text[i] self.lyric_arr[-1] += self.text[i]
self.lyric_arr = [re.sub(r'^ | $', '&nbsp;', l) for l in self.lyric_arr]
self.lyric_arr = [l if l != "" else "&nbsp;" for l in self.lyric_arr] self.lyric_arr = [l if l != "" else "&nbsp;" for l in self.lyric_arr]
def remove_brackets(self): def remove_brackets(self):
@ -162,8 +166,8 @@ class Line:
return False return False
def chord_eng2lat(text): def chord_eng2lat(text, transpose = 0):
return Chord.CHORDS_LAT[Chord.ENG_INDEX[text]] return Chord.CHORDS_LAT[(Chord.ENG_INDEX[text] + transpose + len(Chord.CHORDS_LAT)) % len(Chord.CHORDS_LAT)]
class Chord: class Chord:
@ -172,28 +176,34 @@ class Chord:
CHORDS_ENG = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B&', 'B'] 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} 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): def __init__(self, text, transpose = 0, trfmt = "normal"):
self.text = text + "&nbsp;" self.text = text + "&nbsp;"
self.items = [] self.items_n = [] # normal version
self.base_transpose = base_transpose self.items_t = [] # transposed version
self.transpose = transpose
self.trfmt = trfmt
ignore = False ignore = False
for i, char in enumerate(text): for i, char in enumerate(text):
if ignore: if ignore:
ignore = False ignore = False
continue continue
if "A" <= char <= "G": if "A" <= char <= "G":
if len(self.items) > 0 and not self.items[-1]['text'].endswith("&nbsp;"): if len(self.items_n) > 0 and not self.items_n[-1]['text'].endswith("&nbsp;"):
self.items[-1]['text'] += "&nbsp;" self.items_n[-1]['text'] += "&nbsp;"
self.items_t[-1]['text'] += "&nbsp;"
if len(text) > i + 1 and (text[i + 1] == "#" or text[i + 1] == "&"): if len(text) > i + 1 and (text[i + 1] == "#" or text[i + 1] == "&"):
self.items.append({'text': chord_eng2lat(char + text[i + 1]), 'chord': True}) self.items_n.append({'text': chord_eng2lat(char + text[i + 1]), 'chord': True})
self.items_t.append({'text': chord_eng2lat(char + text[i + 1], transpose), 'chord': True})
ignore = True ignore = True
else: else:
self.items.append({'text': chord_eng2lat(char), 'chord': True}) self.items_n.append({'text': chord_eng2lat(char), 'chord': True})
self.items_t.append({'text': chord_eng2lat(char, transpose), 'chord': True})
else: else:
self.items.append({'text': char, 'chord': False}) self.items_n.append({'text': char, 'chord': False})
if len(self.items) > 0 and not self.items[-1]['text'].endswith("&nbsp;"): self.items_t.append({'text': char, 'chord': False})
self.items[-1]['text'] += "&nbsp;" if len(self.items_n) > 0 and not self.items_n[-1]['text'].endswith("&nbsp;"):
self.items_n[-1]['text'] += "&nbsp;"
self.items_t[-1]['text'] += "&nbsp;"
def __str__(self): def __str__(self):
return self.text return self.text