Nuevo generador de cancioneros en línea.

This commit is contained in:
Carlos Galindo 2025-05-04 00:26:46 +02:00
parent 3d9bb9a26b
commit c622a27e34
13 changed files with 5870 additions and 0 deletions

31
Makefile_generador Normal file
View file

@ -0,0 +1,31 @@
.PHONY=static all clean
ALL_STATIC := $(patsubst res_gen/static/%,generador_public/%,$(wildcard res_gen/static/*))
ALL_TEMPLATES := $(wildcard res_gen/templates/*.html)
PY_SRC := $(wildcard src/**.py)
all: generador_public generador_public/index.html static generador_public/res/canciones
static: $(ALL_STATIC)
generador_public:
mkdir generador_public
generador_public/res/canciones:
rsync -a latex/canciones generador_public/res/.
generador_public/index.html: $(ALL_TEMPLATES) $(PY_SRC)
python3 src/create_generator.py \
--latex latex/cancionero.tex \
--other-latex latex/canciones/ \
--author NONE --title NONE
generador_public/%: res_gen/static/%
cp -ra $< $@
clean:
rm -rf generador_public songs.dat
.PHONY: upload
upload: all
rsync -a --delete generador_public/* uberspace:html/cancionero-generador/.

View file

@ -0,0 +1,209 @@
<?php
$errorlist = array();
function delTree($dir) {
$files = array_diff(scandir($dir), array('.', '..'));
foreach ($files as $file) {
if (!is_dir("$dir/$file")) {
unlink("$dir/$file");
}
}
return rmdir($dir);
}
function param_conversion($name, $type) {
global $errorlist;
if (validParam($name, "string")) {
if (!is_numeric($_POST[$name])) {
$errorlist[] = "El parámetro $name debería ser numérico, y es: " . $_POST[$name];
} else {
if ($type == "integer")
$_POST[$name] = intval($_POST[$name]);
else if ($type == "double")
$_POST[$name] = doubleval($_POST[$name]);
else {
$errorlist[] = "$type es un tipo inválido al que convertir el parámetro $name";
return false;
}
return true;
}
}
return false;
}
function validParam($name, $type) {
global $errorlist;
if (!isset($_POST[$name]))
$errorlist[] = "No se ha incluido el parámetro '$name'";
else if (($realtype = gettype($_POST[$name])) != $type)
$errorlist[] = "El parámetro '$name' debería ser de tipo $type, y es de tipo $realtype";
else
return true;
return false;
}
function validEmptyBool($name) {
if (!isset($_POST[$name]))
$_POST[$name] = "false";
return validBool($name);
}
function validBool($name) {
if (validParam($name, "string")) {
if ($_POST[$name] != "true")
$_POST[$name] = "false";
return true;
}
return false;
}
function validInt($name, $min, $max) {
global $errorlist;
if (param_conversion($name, "integer") && validParam($name, "integer")) {
if ($_POST[$name] < $min || $_POST[$name] > $max)
$errorlist[] = "El parámetro numérico '$name' tiene un valor fuera de rango. Valor: " . $_POST[$name] . ". Rango válido: [$min, $max].";
else
return true;
}
return false;
}
function validDouble($name, $min, $max) {
global $errorlist;
if (param_conversion($name, "double") && validParam($name, "double")) {
if ($_POST[$name] < $min || $_POST[$name] > $max)
$errorlist[] = "El parámetro numérico '$name' tiene un valor fuera de rango. Valor: " . $_POST[$name] . ". Rango válido: [$min, $max].";
else
return true;
}
return false;
}
$TEX_ERROR = "Fatal error occurred, no output PDF file produced!";
$error = false;
// Check fields and their correctness
$error |= !validBool("chorded");
if (!$error && $_POST["chorded"] != "true") {
$error |= !validInt("paperheight", 1, 9999);
$error |= !validInt("paperwidth", 1, 9999);
$error |= !validDouble("margin", 0.01, 999);
$error |= !validInt("fontsize", 8, 100);
$error |= !validInt("titlefontsize", 8, 100);
$error |= !validInt("subtitlefontsize", 8, 100);
}
$error |= !validEmptyBool("ifcolumns");
if (!$error && $_POST["ifcolumns"] == "true") {
$error |= !validInt("columns", 1, 20);
}
$error |= !validEmptyBool("ifcolumnsep");
if (!$error && $_POST["ifcolumnsep"] == "true") {
$error |= !validDouble("columnsep", 0.01, 999);
}
$error |= !validInt("breaksallowed", 0, 3);
$error |= !validParam("title", "string");
$error |= !validParam("songs", "array");
if (!$error) {
foreach ($_POST["songs"] as $song) {
if (!str_starts_with($song, "canciones/") || str_contains($song, "..")) {
$error = true;
$errorlist[] = "La canción $song no cumple los requisitos.";
}
}
}
// Create tmp dir and copy/link necessary files
if (!$error) {
$resdir = getcwd() . "/res";
$tmpdir = substr(shell_exec("mktemp -d"), 0, -1); // remove trailing newline
symlink("$resdir/songs.sty", "$tmpdir/songs.sty");
symlink("$resdir/estilo.sty", "$tmpdir/estilo.sty");
symlink("$resdir/acordes.sty", "$tmpdir/acordes.sty");
symlink("$resdir/canciones", "$tmpdir/canciones");
symlink("$resdir/changepage.sty", "$tmpdir/changepage.sty");
symlink("$resdir/tocloft.sty", "$tmpdir/tocloft.sty");
symlink("$resdir/uarial.sty", "$tmpdir/uarial.sty");
}
// Generate and fill template
if (!$error) {
$lines = file("$resdir/template.tex");
if ($_POST["chorded"] == "true") {
$lines = preg_replace('/%CHORDED%/', '', $lines);
} else {
$lines = preg_replace('/%LYRICS%/', '', $lines);
$lines = preg_replace('/%PAPERHEIGHT%/', $_POST["paperheight"], $lines);
$lines = preg_replace('/%PAPERWIDTH%/', $_POST["paperwidth"], $lines);
$lines = preg_replace('/%MARGIN%/', $_POST["margin"], $lines);
if ($_POST["ifcolumns"] == "true") {
$lines = preg_replace('/%SONGCOLUMNS%/', '', $lines);
$lines = preg_replace('/%SONGCOLNUM%/', "\\songcolumns{" . $_POST["columns"] . "}", $lines);
} else {
$lines = preg_replace('/%SONGCOLUMNS%/', 'onesongcolumn', $lines);
}
$lines = preg_replace('/%FONTSIZE%/', $_POST["fontsize"], $lines);
$lines = preg_replace('/%LINESKIP%/', $_POST["fontsize"] + 4, $lines);
$lines = preg_replace('/%TITLEFONTSIZE%/', $_POST["titlefontsize"], $lines);
$lines = preg_replace('/%TITLELINESKIP%/', $_POST["titlefontsize"] + 4, $lines);
$lines = preg_replace('/%SUBTITLEFONTSIZE%/', $_POST["subtitlefontsize"], $lines);
}
$lines = preg_replace('/%TITLE%/', $_POST["title"], $lines);
if ($_POST["ifcolumnsep"] == "true") {
$lines = preg_replace('/%IFCOLUMNSEP%/', '', $lines);
$lines = preg_replace('/%COLUMNSEP%/', $_POST["columnsep"], $lines);
}
$songinput = "";
foreach ($_POST["songs"] as $song) {
$songinput .= '\input{' . $song . "}\n";
}
$lines = preg_replace('/%SONGLIST%/', $songinput, $lines);
$fwrite = file_put_contents("$tmpdir/cancionero.tex", $lines);
if (!$fwrite) {
$error = true;
$errorlist[] = "No se pudo escribir el archivo LaTeX.";
}
}
// Compile PDF
if (!$error) {
chdir($tmpdir);
do {
$latexlog = shell_exec("/usr/bin/pdflatex -interaction=nonstopmode cancionero.tex");
if (str_contains($latexlog, $TEX_ERROR)) {
$error = true;
$errorlist[] = "Ha sucedido un error en la ejecución, véase el log más abajo.";
break;
}
} while (str_contains($latexlog, "(rerunfilecheck)"));
}
// Return PDF or error page
if (!$error && file_exists($pdffile = "$tmpdir/cancionero.pdf")) {
header("Content-Type: application/pdf");
header('Content-Disposition: inline;filename=cancionero.pdf');
header('Content-Length: ' . filesize($pdffile));
readfile($pdffile);
} else {
echo '<h1>Ha ocurrido un error...</h1>';
echo '<p>No se ha podido generar el cancionero.</p>';
echo '<p><strong>Por favor,</strong> guarda (Ctrl+S) o imprime (Ctrl+P) esta página y envíasela a Carlos, para que lo pueda solucionar.</p>';
echo '<h2>Errores individuales</h2>';
echo '<ul>';
foreach ($errorlist as $errormsg) {
echo "<li>$errormsg</li>";
}
echo '</ul>';
if (isset($tmpdir) && file_exists("$tmpdir/cancionero.log")) {
echo '<h2>Detalles del error</h2>';
echo '<pre>';
readfile("$tmpdir/cancionero.log");
echo '</pre>';
}
echo '<p>Fin de la página.</p>';
}
// Cleanup
if (isset($tmpdir) && file_exists($tmpdir))
delTree($tmpdir);
?>

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="100" width="100">
<path fill="#FFF" stroke="#06D" stroke-width="10" d="m43,35H5v60h60V57M45,5v10l10,10-30,30 20,20 30-30 10,10h10V5z"></path>
</svg>

After

Width:  |  Height:  |  Size: 198 B

View file

@ -0,0 +1 @@
Deny from all

View file

@ -0,0 +1,66 @@
% Base del estilo con acordes
\ProvidesPackage{acordes}
% Incluimos el paquete que genera el libro de acordes con la opción chorded
\RequirePackage[chorded]{songs}
% Formato del papel: A4 (297x210) con todos los márgenes de 15mm
\RequirePackage[paperheight=297mm,paperwidth=210mm,margin=15mm,heightrounded]{geometry}
% Incluimos el paquete básico de estilo, ver estilo.sty en la misma carpeta
\RequirePackage{estilo}
% Notas que se deben reconocer como las notas La, Si, Do, Re, Mi, Fa, Sol
% Esto sirve para transponer canciones automáticamente y para cambiar los nombres
% por los que se quiera: ver siguiente instrucción
\notenamesin{A}{B}{C}{D}{E}{F}{G}
% Imprime las notas con nombres romances con la primera en mayúscula.
% Se puede cambiar a cualquier conjunto de 7 palabras.
\notenamesout{La}{Si}{Do}{Re}{Mi}{Fa}{Sol}
% Este paquete no sé por qué lo incluí, pero no me atrevo a quitarlo
\RequirePackage{etoolbox} % TODO: probar a quitar
% Esto permite hacer un índice con múltiples columnas (solo para el índice secuencial)
% Los índices alfabéticos y por autor son internos del paquete "songs".
\RequirePackage[toc]{multitoc}
% Configura el número de columnas de los índices a 2
\renewcommand{\multicolumntoc}{2}
% Coloca una barra vertical de ancho 1pt entre cada columna del índice.
%\setlength{\columnseprule}{1pt}
% Incluimos el paquete hyperref, para poder tener enlaces clicables en los índices.
% Esto es muy útil a la hora de navegar por el documento en un ordenador.
\RequirePackage[bookmarks]{hyperref}
% Ver último ajuste en letra.sty
\songpos{1}
% Configuración del título (ver letra.sty), similar pero con otras dimensiones
\renewcommand\makeprelude{%
\resettitles
\checkoddpage
\ifoddpage
\begin{minipage}{70mm}
{
\raggedright
{\fontsize{15pt}{17pt}\sffamily\bfseries\songtitle} \par
{\fontsize{9pt}{\baselineskip}\extendprelude}
}
\end{minipage}
\hfill
\begin{minipage}{12mm}
{\hfill \printsongnum{\thesongnum}}
\end{minipage}
\else
\begin{minipage}{12mm}
\printsongnum{\thesongnum}
\end{minipage}
\hfill
\begin{minipage}{70mm}
{
\raggedleft {\fontsize{15pt}{17pt}\sffamily\bfseries\songtitle} \par
{\fontsize{9pt}{\baselineskip}\extendprelude}
}
\end{minipage}
\fi
}
% TODO Para añadir las canciones al índice numérico
% https://tex.stackexchange.com/questions/192157/latex-songs-use-tableofcontents/192160?r=SearchResults#192160

View file

@ -0,0 +1,142 @@
%% LaTeX2e file `changepage.sty'
%% generated by the `filecontents' environment
%% from source `changepage' on 2009/10/20.
%%
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{changepage}[2009/10/20 v1.0c check page and change page layout]
% Don't use this with memoir
\newcommand*{\cp@whoopsusingmemoir}{}
\@ifclassloaded{memoir}{\def\cp@whoopsusingmemoir{\endinput}}
\cp@whoopsusingmemoir
% New \verb|\if| for the strict option
\newif\ifstrictpagecheck
\strictpagecheckfalse
% User commands for switching strict page checking on/off
\newcommand*{\strictpagecheck}{\strictpagechecktrue}
\newcommand*{\easypagecheck}{\strictpagecheckfalse}
% Declare and process options
\DeclareOption{strict}{\strictpagechecktrue}
\ProcessOptions\relax
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% New commands for strict checking of odd/even page.
%% Works by writing a label and then checking its pageref.
\newif\ifoddpage
\newcounter{cp@cntr}
\newcount\cp@tempcnt % instead of \verb|\@memcnta|
\newcommand{\cplabel}{^_}
\gdef\thepmemc@@page{\the\c@page}
\long\def\pmemprotected@write#1#2#3{% modified \verb|\protected@write|
\begingroup
\let\thepmemc@@page\relax
#2%
\let\protect\@unexpandable@protect
\edef\reserved@a{\write#1{#3}}%
\reserved@a
\endgroup
\if@nobreak\ifvmode\nobreak\fi\fi}
\newcommand*{\pmemlabel}[1]{\@bsphack
\pmemprotected@write\@auxout{}%
{\string\newpmemlabel{#1}{\thepmemc@@page}}%
\@esphack}
\newcommand*{\newpmemlabel}[2]{{\global\@namedef{m@#1}{#2}}}
\newcommand*{\pmemlabelref}[1]{%
\expandafter\ifx\csname m@#1\endcsname\relax
0% % 0 if there is no label yet in the aux file
\else
\csname m@#1\endcsname
\fi}
\DeclareRobustCommand{\checkoddpage}{%
\oddpagefalse%
\ifstrictpagecheck%
\stepcounter{cp@cntr}\pmemlabel{\cplabel\thecp@cntr}%
\cp@tempcnt=\pmemlabelref{\cplabel\thecp@cntr}\relax
\ifodd\cp@tempcnt\oddpagetrue\fi
\else
\ifodd\c@page\oddpagetrue\fi
\fi}
% End newcommands for strict checking of odd/even page.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Copy of some of the code from the ifmtarg package to save requiring ifmtarg
\begingroup
\catcode`\Q=3
\long\gdef\@ifmtarg#1{\@xifmtarg#1QQ\@secondoftwo\@firstoftwo\@nil}
\long\gdef\@xifmtarg#1#2Q#3#4#5\@nil{#4}
\endgroup
% Set the page output parameters
\DeclareRobustCommand{\ch@ngetext}{%
\setlength{\@colht}{\textheight}\setlength{\@colroom}{\textheight}%
\setlength{\vsize}{\textheight}\setlength{\columnwidth}{\textwidth}%
\if@twocolumn%
\advance\columnwidth-\columnsep \divide\columnwidth\tw@%
\@firstcolumntrue%
\fi%
\setlength{\hsize}{\columnwidth}%
\setlength{\linewidth}{\hsize}}
\DeclareRobustCommand{\changetext}[5]{%
\@ifmtarg{#1}{}{\addtolength{\textheight}{#1}}%
\@ifmtarg{#2}{}{\addtolength{\textwidth}{#2}}%
\@ifmtarg{#3}{}{\addtolength{\evensidemargin}{#3}}%
\@ifmtarg{#4}{}{\addtolength{\oddsidemargin}{#4}}%
\@ifmtarg{#5}{}{\addtolength{\columnsep}{#5}}%
\ch@ngetext}
\DeclareRobustCommand{\changepage}[9]{%
\@ifmtarg{#1}{}{\addtolength{\textheight}{#1}}%
\@ifmtarg{#2}{}{\addtolength{\textwidth}{#2}}%
\@ifmtarg{#3}{}{\addtolength{\evensidemargin}{#3}}%
\@ifmtarg{#4}{}{\addtolength{\oddsidemargin}{#4}}%
\@ifmtarg{#5}{}{\addtolength{\columnsep}{#5}}%
\ch@ngetext%
\@ifmtarg{#6}{}{\addtolength{\topmargin}{#6}}%
\@ifmtarg{#7}{}{\addtolength{\headheight}{#7}}%
\@ifmtarg{#8}{}{\addtolength{\headsep}{#8}}%
\@ifmtarg{#9}{}{\addtolength{\footskip}{#9}}}
\newenvironment{adjustwidth}[2]{%
\begin{list}{}{%
\topsep\z@%
\listparindent\parindent%
\parsep\parskip%
\@ifmtarg{#1}{\setlength{\leftmargin}{\z@}}%
{\setlength{\leftmargin}{#1}}%
\@ifmtarg{#2}{\setlength{\rightmargin}{\z@}}%
{\setlength{\rightmargin}{#2}}%
}
\item[]}{\end{list}}
\newenvironment{adjustwidth*}[2]{%
\begin{list}{}{%
\topsep\z@%
\listparindent\parindent%
\parsep\parskip%
\checkoddpage
\ifoddpage% odd numbered page
\@ifmtarg{#1}{\setlength{\leftmargin}{\z@}}%
{\setlength{\leftmargin}{#1}}%
\@ifmtarg{#2}{\setlength{\rightmargin}{\z@}}%
{\setlength{\rightmargin}{#2}}%
\else% even numbered page
\@ifmtarg{#2}{\setlength{\leftmargin}{\z@}}%
{\setlength{\leftmargin}{#2}}%
\@ifmtarg{#1}{\setlength{\rightmargin}{\z@}}%
{\setlength{\rightmargin}{#1}}%
\fi}
\item[]}{\end{list}}
\endinput

View file

@ -0,0 +1,65 @@
% Base del estilo general del cancionero: aquí ajusto cosas generales para ambos cancioneros
% y cargo los paquetes necesarios para diversas funciones
\ProvidesPackage{estilo}
% Para poder poner acentos y letras como la Ñ
\RequirePackage[utf8]{inputenc}
% Para que los títulos de los índices sean en español
\RequirePackage[spanish]{babel}
% Cargar la fuente Arial 030 para usarla en los números
\RequirePackage[scaled]{uarial}
% Ajustamos la codificación de las fuentes a T1 (necesario para Arial)
\RequirePackage[T1]{fontenc}
% Cargar la fuente Helvetica para usarla por defecto en todo el texto
% sans serif
\RequirePackage[scaled]{helvet}
% Este paquete nos da el comando \includepdf que sirve para insertar
% cualquier PDF como una página más
\RequirePackage[final]{pdfpages}
% Para la nota musical
\RequirePackage{textcomp}
% Para el indice general (secuencial)
\RequirePackage{tocloft}
% Para que las páginas no tengan cabecera ni pie de página (numeración y capítulo actual)
\pagestyle{empty}
% Para que el paquete de canciones no numere las estrofas (queda demasiado formal).
% Se podría reactivar para hacer diapositivas o hojas cortas de canciones
\noversenumbers
% Hacemos que las letras de las canciones sean sans serif
\renewcommand{\lyricfont}{\sffamily}
% Número con la fuente URW Arial 030 (similar a los números magnéticos que hay en la parroquia)
\renewcommand{\printsongnum}[1]{\fontfamily{ua1}\selectfont\bfseries\LARGE#1}
% Esto está comentado, si se descomenta cambiaría los estribillos a cursiva
%\renewcommand{\chorusfont}{\slshape}
% Esto está comentado, si se descomenta cambiaría los estribillos a negrita
%\renewcommand{\chorusfont}{\bfseries}
% Necesitamos este paquete y la opción para saber si estamos en página par o impar (alineación de números)
\RequirePackage{changepage}
\strictpagecheck
% Añade un nuevo campo a las canciones para mostrar en qué melodía están basadas
\newsongkey{m}{\def\melody{}}{\def\melody{\textmusicalnote #1\par}}
% Añade un nuevo campo para definir el índice (toc={...})
\newcommand\toctitle{}
\newcommand\toclink{}
{\makeatletter\gdef\toclink{\@ifundefined{href}{}{{song\theSB@songsnum-\thesongnum
.\ifnum\value{section}=0 1\else2\fi}}}}
\newcommand\addtotoc[1]{\addtocontents{toc}{\protect\contentsline
{\ifnum\value{section}>0sub\fi section}{\numberline\thesongnum#1}{\thepage}\toclink}}
\newsongkey{toc}
{\def\toctitle{\resettitles\addtotoc\songtitle}}
{\def\toctitle{\addtotoc{#1}}}
% Modifica el preludio (cabecera) de cada canción para incluir melody y toctitle
\renewcommand{\extendprelude}{
\showrefs\showauthors
\melody\toctitle
}
% En la tabla de contenidos, evita que aparezca el número de página (innecesario)
\cftpagenumbersoff{chapter}
\cftpagenumbersoff{section}
\cftpagenumbersoff{subsection}

3808
res_gen/static/res/songs.sty Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
\documentclass{book}
% \ifchorded
%CHORDED% \usepackage{acordes}
% \else
%LYRICS% \usepackage[lyric,noshading,%SONGCOLUMNS%]{songs}
%LYRICS% \usepackage{estilo}
%LYRICS% \usepackage[paperwidth=%PAPERWIDTH%mm,paperheight=%PAPERHEIGHT%mm,margin=%MARGIN%cm]{geometry}
%LYRICS% \renewcommand{\lyricfont}{\fontsize{%FONTSIZE%}{%LINESKIP%}\sffamily}
\usepackage{hyperref}
\songpos{0} % 0 a 3, cuanto mas alto menos se cortan las canciones
\ifchorded
\else
\renewcommand\makeprelude{%
\resettitles%
{%
\raggedright%
%LYRICS% {\fontsize{%TITLEFONTSIZE%}{%TITLELINESKIP%}\sffamily\bfseries\songtitle} \par
%LYRICS% {\fontsize{%SUBTITLEFONTSIZE%}{\baselineskip}\extendprelude}
}%
}%
\fi
\begin{document}
\songsection{\normalfont \textsc{\huge %TITLE%}}
\begin{songs}{}
%SONGCOLNUM%
%IFCOLUMNSEP%\columnsep=%COLUMNSEP%cm
%SONGLIST%
\end{songs}
\end{document}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
\ProvidesPackage{uarial}%
[2006/03/21 1.0 (WaS)]
\RequirePackage{keyval}
\define@key{UaI}{scaled}[.95]{%
\expandafter\def\csname ua1@Scale\endcsname{#1}}
\def\ProcessOptionsWithKV#1{%
\let\@tempc\relax
\let\UaI@tempa\@empty
\ifx\@classoptionslist\relax\else
\@for\CurrentOption:=\@classoptionslist\do{%
\@ifundefined{KV@#1@\CurrentOption}%
{}%
{%
\edef\UaI@tempa{\UaI@tempa,\CurrentOption,}%
\@expandtwoargs\@removeelement\CurrentOption
\@unusedoptionlist\@unusedoptionlist
}%
}%
\fi
\edef\UaI@tempa{%
\noexpand\setkeys{#1}{%
\UaI@tempa\@ptionlist{\@currname.\@currext}%
}%
}%
\UaI@tempa
\let\CurrentOption\@empty
}
\ProcessOptionsWithKV{UaI}
\AtEndOfPackage{%
\let\@unprocessedoptions\relax
}
\renewcommand{\sfdefault}{ua1}
\endinput
%%
%% End of file `ua1.sty'.

View file

@ -0,0 +1,258 @@
<!doctype html>
<html lang="es_ES">
<head>
<title>Generador de cancioneros</title>
<meta charset='utf-8' />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
.chorded .hideChorded {
display: none;
}
.lyrics .hideLyrics {
display: none;
}
a.external {
background-image: url(img/link-external.svg);
background-position: center right;
background-repeat: no-repeat;
background-size: 0.857em;
padding-right: 1em;
}
a {
color: #36c;
}
a:hover {
color: #3056a9;
}
</style>
</head>
<body>
<h1>Generador de cancioneros</h1>
<form id="form" action="generar_pdf.php" method="post" target="_blank">
<h2>Ajustes generales</h2>
<p>
Selecciona el tipo de cancionero:
<label><input type="radio" name="chorded" value="true" onchange="setChorded(event)" required>Con acordes</label>
<label><input type="radio" name="chorded" value="false" onchange="setChorded(event)" required>Sin acordes</label>
</p>
<fieldset>
<legend>Propiedades de página</legend>
<p class="hideChorded">
Tamaño del papel:
<label><input class="lyricsonly" type="number" size="4" name="paperheight" id="paperheight" required>&nbsp;mm (alto)</label> &times;
<label><input class="lyricsonly" type="number" size="4" name="paperwidth" id="paperwidth" required>&nbsp;mm (ancho)</label>
<strong>o</strong> elige un tamaño estándar
<select class="lyricsonly" onchange="setPaperSize(event)">
<option value=""></option>
<option value="paperheight=297,paperwidth=210">A4 (vertical)</option>
<option value="paperheight=210,paperwidth=297">A4 (horizontal)</option>
<option value="paperheight=210,paperwidth=148">A5 (vertical)</option>
<option value="paperheight=148,paperwidth=210">A5 (horizontal)</option>
<option value="paperheight=210,paperwidth=99">Cancionero San Leandro</option>
</select>
</p><p>
Columnas: <label>
<input type="checkbox" name="ifcolumns" value="true" checked onclick="toggleCols(event)">&nbsp;Habilitar</label> | <label><input type="number" name="columns" id="columns" value="2" size="3" min="1" max="10" required> columnas</label>
</p><p>
Separación extra entre columnas: <label>
<input type="checkbox" name="ifcolumnsep" onclick="toggleColSep(event)" value="true">&nbsp;Habilitar</label> | <label><input type="number" size="4" min="0.1" step="0.1" name="columnsep" id="columnsep" disabled="true" required> cm.</label>
</p>
<p class="hideChorded">
<label>Margen en los bordes: <input class="lyricsonly" type="number" min="0.1" step="0.1" size="4" name="margin" value="1.5" required>&nbsp;cm.</label>
</p>
</fieldset>
<fieldset class="hideChorded">
<legend>Tamaño de letra</legend>
<p class="hideChorded">
Tamaño general: <label><input class="lyricsonly" type="number" min="8" size="4" name="fontsize" value="14" required>&nbsp;pt.</label>
</p>
<p class="hideChorded">
Tamaño de títulos de canción: <label><input class="lyricsonly" type="number" min="8" size="4" name="titlefontsize" value="20" required>&nbsp;pt.</label>
</p>
<p class="hideChorded">
Tamaño de subtítulos de canción: <label><input class="lyricsonly" type="number" min="8" size="4" name="subtitlefontsize" value="10" required>&nbsp;pt.</label>
</p>
</fieldset>
<p>
<label>¿Cuando debe cortarse una canción? <select name="breaksallowed">
<option value="0">Sin restricción</option>
<option value="1">Entre columnas o de una página par a impar</option>
<option value="2">Solo entre columnas</option>
<option value="3">Nunca</option>
</select></label>
</p><p>
<label>Título del cancionero: <input type="text" name="title" size="40" value="Cancionero" required></label>
</p>
<h2>Lista de canciones</h2>
<p>
<input type="button" value="Borrar todas" onclick="clearSongs()">
<input type="button" value="Añadir canción" onclick="addSong()">
<input type="button" value="Añadir canción aleatoria" onclick="addRandomSong()">
<input type="button" value="Añadir la primera canción de cada sección" onclick="addFirstSongs()">
<input type="button" value="Añadir una canción por sección (aleatorias)" onclick="addRandomSongs()">
</p>
<ol id='songlist'></ol>
<template id='songtemplate'>
<li><select name="songs[]" onchange="setSong(event)" required>
<option value="">Selecciona una canción</option>{% for category, songs in categorized_songs %}
<optgroup label="&mdash; {{ category }}">{% for s in songs %}
<option value="{{ s.latex_file }}">{{ s.number }}. {{ s.name }}</option>{% endfor %}
</optgroup>{% endfor %}
</select>
<input type="button" value="Subir" onclick="songUp(event)">
<input type="button" value="Bajar" onclick="songDown(event)">
<input type="button" value="Quitar" onclick="songDelete(event)">
<a target="_blank"></a>
</li>
</template>
<h2>Generar el cancionero</h2>
<p>
<input type="submit" value="Generar cancionero en PDF">
<input type="reset" value="Restablecer formulario">
</p>
</form>
<script>
function addSong() {
let ol = document.getElementById("songlist");
let node = document.getElementById("songtemplate").content.firstElementChild.cloneNode(true);
ol.appendChild(node);
return node;
}
function songUp(event) {
const li = event.target.parentElement;
const prev = li.previousElementSibling;
const ol = li.parentElement;
if (prev)
ol.insertBefore(li, prev);
}
function songDown(event) {
const li = event.target.parentElement;
const next = li.nextElementSibling;
const after = next?.nextElementSibling;
const ol = li.parentElement;
if (after)
ol.insertBefore(li, after);
else if (next)
ol.appendChild(li);
}
function songDelete(event) {
const li = event.target.parentElement;
const ol = li.parentElement;
ol.removeChild(li);
}
function clearSongs() {
const ol = document.getElementById("songlist");
ol.replaceChildren();
}
function selectRandomizeOptions(select, optionParent) {
const options = optionParent.getElementsByTagName("option");
do {
const i = Math.floor(Math.random() * options.length);
select.value = options[i].value;
} while (!select.value);
select.dispatchEvent(new Event("change"));
}
function addRandomSong() {
const li = addSong();
const select = li.firstElementChild;
selectRandomizeOptions(select, select);
}
function addFirstSongs() {
addSongs(false);
}
function addRandomSongs() {
addSongs(true);
}
function addSongs(random) {
const li = addSong();
const groups = li.firstElementChild.getElementsByTagName("optgroup");
for (let g of groups) {
const select = addSong().firstElementChild;
if (random) {
selectRandomizeOptions(select, g);
} else {
select.value = g.firstElementChild.value;
select.dispatchEvent(new Event("change"));
}
}
li.parentElement.removeChild(li);
}
function setChorded(event) {
const radio = event.target;
const chorded = radio.checked && radio.value === "true";
const elems = document.getElementsByClassName("lyricsonly");
for (const e of elems)
e.disabled = chorded;
const form = document.getElementById("form");
if (chorded) {
form.classList.remove("lyrics");
form.classList.add("chorded");
} else if (radio.checked) {
form.classList.remove("chorded");
form.classList.add("lyrics");
}
}
function setPaperSize(event) {
const select = event.target;
for (let x of select.value.split(',')) {
const pair = x.split('=');
document.getElementById(pair[0]).value = pair[1];
}
}
function setSong(event) {
const select = event.target;
const a = select.parentElement.lastElementChild;
let validSong = false;
if (select.value) {
const title = select.selectedOptions[0].textContent;
let num = title.substring(0, title.indexOf('.'));
while (num.length < 3)
num = '0' + num;
if (num != '000') {
validSong = true;
a.textContent = "Ver la letra";
a.classList = ["external"]
a.href = `https://canciones.sanleandrovalencia.es/${num}`;
}
}
if (!validSong) {
a.textContent = "";
a.href = ""
a.classList = [];
}
}
function toggleCols(event) {
const input = event.target;
const cols = document.getElementById("columns");
if (input.checked)
cols.value = 2;
else
cols.value = 1;
cols.disabled = !input.checked;
}
function toggleColSep(event) {
const input = event.target;
const colsep = document.getElementById("columnsep");
if (input.checked)
colsep.value = 0.5;
else
colsep.value = "";
colsep.disabled = !input.checked;
}
</script>
</body>
</html>

55
src/create_generator.py Normal file
View file

@ -0,0 +1,55 @@
import latex_scanner
from os.path import join
from django.template import Engine, Context
from django.conf import settings
import pickle
def create_generator(songs, out_dir, dj_engine):
'''Generate an HTML file with the list of all known songs for the generator.'''
# Remove songs which share the same .tex file
files = set()
remove = set()
for song in songs:
if song.latex_file not in files:
files.add(song.latex_file)
else:
remove.add(song.latex_file)
selected_songs = [s for s in songs if s.latex_file not in remove]
category_set = set(s.category for s in selected_songs)
categories = sorted(category_set, key=lambda c:min([s.number for s in songs if s.category == c]))
dictionary = [(c, [s for s in selected_songs if s.category == c]) for c in categories]
# Export to html
context = Context({'categorized_songs': dictionary})
with open(join(out_dir, "index.html"), 'w') as f:
f.write(dj_engine.get_template("generador.html").render(context))
def create_argparser():
'''Parse main's arguments.'''
parser = latex_scanner.create_argparser()
parser.add_argument("--generator-dir", required=False, nargs=1, default=["generador_public"],
help="The path where the generated files should be placed.")
return parser
if __name__ == '__main__':
args = create_argparser().parse_args()
try:
with open("songs.dat", "rb") as f:
songs = pickle.load(f)
except OSError:
loader = latex_scanner.SongLoader(args.latex[0])
if args.other_latex:
loader.scan_others(args.other_latex[0], args.other_index[0])
songs = loader.songs
try:
with open("songs.dat", "wb") as f:
pickle.dump(songs, f)
except OSError:
pass
settings.configure(USE_TZ=False, USE_I18N=False)
e = Engine(dirs=["res_gen/templates/"])
create_generator(songs, args.generator_dir[0], e)