songs2slides

A tool that automatically finds song lyrics and creates lyric slideshows
git clone https://git.ashermorgan.net/songs2slides/
Log | Files | Refs | README

commit a726f5b33ceeba5393f80ea1dd9bd3324ff91304
parent 70f3228dfc5c8701229890a43e9467675bd23614
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date:   Sun, 10 May 2020 13:50:14 -0700

Merge pull request #4 from AsherMorgan/web-settings

Implement customizable settings in the web app.
Diffstat:
MREADME.md | 6+++---
MSongs2Slides/config.py | 46+++++++++++++++++++++-------------------------
MSongs2Slides/models.py | 52+++++++++++++++++++++++++---------------------------
MSongs2Slides/routes.py | 33+++++++++++++++++++++++++--------
MSongs2Slides/static/index.css | 49++++++++++++++++++++++++++++++++++++++++++-------
MSongs2Slides/static/index.js | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
MSongs2Slides/templates/index.html | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mcliapp.py | 10+++++-----
8 files changed, 342 insertions(+), 82 deletions(-)

diff --git a/README.md b/README.md @@ -6,7 +6,7 @@ Not all features are currently available in the web interface. - Can parse any song given the artist and title. - New slides are automatically started at the beginning of verses, bridges, choruses, etc. - The user can easily review and edit the slides. -- The default powerpoint format may be changed at anytime in `Songs2Slides/config.py`. +- The format of the powerpoint can be customized. - The slides can be added to a new or existing powerpoint. ## Usage @@ -18,8 +18,8 @@ To use the command line interface, run `cliapp.py`. ``` python cliapp.py ``` -To use the web interface, run `webapp.py` and then go to https://localhost:5000 in your web browser. +To use the web interface, run `webapp.py` and then open http://localhost:5000 in your web browser. ``` python webapp.py -start https://localhost:5000 +start http://localhost:5000 ``` \ No newline at end of file diff --git a/Songs2Slides/config.py b/Songs2Slides/config.py @@ -1,32 +1,29 @@ -parsing = { +defaultSettings = { + # Parsing settings "title-slides": True, "lines-per-slide": 4, "remove-parentheses": False, -} - -slide = { - "width": 13.333, - "height": 7.5, - "color": [0, 0, 0], -} -margin = { - "left": 0.5, - "right": 0.5, - "top": 0.5, - "bottom": 0.5, -} + # Slide settings + "slide-width": 13.333, + "slide-height": 7.5, + "slide-color": "#000000", + + # Margin settings + "margin-left": 0.5, + "margin-right": 0.5, + "margin-top": 0.5, + "margin-bottom": 0.5, -font = { - "family": "Calibri", - "size": 40, - "bold": False, - "italic": False, - "color": [255, 255, 255], -} + # Font settings + "font-family": "Calibri", + "font-size": 40, + "font-bold": False, + "font-italic": False, + "font-color": "#FFFFFF", -paragraph = { - "vertical-alignment": "middle", # Can be top, middle, or bottom + # Paragraph settings + "vertical-alignment": "Middle", # Can be Top, Middle, or Bottom "line-spacing": 1.25, "word-wrap": True -} -\ No newline at end of file +} diff --git a/Songs2Slides/models.py b/Songs2Slides/models.py @@ -1,6 +1,5 @@ # Import dependencies from bs4 import BeautifulSoup -from Songs2Slides import config from pptx import Presentation from pptx.dml.color import RGBColor from pptx.enum.text import MSO_ANCHOR, PP_ALIGN @@ -42,12 +41,12 @@ def GetLyrics(title, artist): # Parses the lyrics of a song into slides -def ParseLyrics(title, artist): +def ParseLyrics(title, artist, settings): # Get lyrics rawLyrics, title, artist = GetLyrics(title, artist) # Remove content in parentheses - if (config.parsing["remove-parentheses"]): + if (settings["remove-parentheses"]): rawLyrics = re.sub(r'\([^)]*\)', '', rawLyrics) # Parse Lyrics @@ -61,11 +60,11 @@ def ParseLyrics(title, artist): # Add title slide slides = [] - if (config.parsing["title-slides"]): + if (settings["title-slides"]): slides += ["{0}\n{1}".format(title, artist)] # Parse lyrics into slides - slideSize = config.parsing["lines-per-slide"] + slideSize = settings["lines-per-slide"] for i in range(0, len(rawLines)): if (rawLines[i] == ""): # Start a new slide without content @@ -74,7 +73,7 @@ def ParseLyrics(title, artist): elif (rawLines[i][0] == "["): # Ignore pass - elif (slideSize == config.parsing["lines-per-slide"]): + elif (slideSize == settings["lines-per-slide"]): # Start a new slide with content slides.append(rawLines[i]) slideSize = 1 @@ -97,7 +96,7 @@ def ParseLyrics(title, artist): # Create powerpoint -def CreatePptx(parsedLyrics, filepath, openFirst): +def CreatePptx(parsedLyrics, filepath, settings, openFirst): if (openFirst): try: # Open presentation @@ -107,24 +106,24 @@ def CreatePptx(parsedLyrics, filepath, openFirst): prs = Presentation() # Set slide width and height - prs.slide_width = Inches(config.slide["width"]) - prs.slide_height = Inches(config.slide["height"]) + prs.slide_width = Inches(settings["slide-width"]) + prs.slide_height = Inches(settings["slide-height"]) else: # Create presentation prs = Presentation() # Set slide width and height - prs.slide_width = Inches(config.slide["width"]) - prs.slide_height = Inches(config.slide["height"]) + prs.slide_width = Inches(settings["slide-width"]) + prs.slide_height = Inches(settings["slide-height"]) # Get blank slide blank_slide_layout = prs.slide_layouts[6] # Get margins - left = Inches(config.margin["left"]) - top = Inches(config.margin["top"]) - width = prs.slide_width - Inches(config.margin["left"] + config.margin["right"]) - height = prs.slide_height - Inches(config.margin["top"] + config.margin["bottom"]) + left = Inches(settings["margin-left"]) + top = Inches(settings["margin-top"]) + width = prs.slide_width - Inches(settings["margin-left"] + settings["margin-right"]) + height = prs.slide_height - Inches(settings["margin-top"] + settings["margin-bottom"]) for lyric in parsedLyrics: # Add slide @@ -132,7 +131,7 @@ def CreatePptx(parsedLyrics, filepath, openFirst): # Apply slide formating slide.background.fill.solid() - slide.background.fill.fore_color.rgb = RGBColor(config.slide["color"][0], config.slide["color"][1], config.slide["color"][2]) + slide.background.fill.fore_color.rgb = RGBColor.from_string(settings["slide-color"][1:]) # Add text box txBox = slide.shapes.add_textbox(left, top, width, height) @@ -140,10 +139,10 @@ def CreatePptx(parsedLyrics, filepath, openFirst): tf.clear() # Apply text formating - tf.word_wrap = config.paragraph["word-wrap"] - if (config.paragraph["vertical-alignment"].lower() == "top"): + tf.word_wrap = settings["word-wrap"] + if (settings["vertical-alignment"].lower() == "top"): tf.vertical_anchor = MSO_ANCHOR.TOP - elif (config.paragraph["vertical-alignment"].lower() == "bottom"): + elif (settings["vertical-alignment"].lower() == "bottom"): tf.vertical_anchor = MSO_ANCHOR.BOTTOM else: tf.vertical_anchor = MSO_ANCHOR.MIDDLE @@ -153,13 +152,13 @@ def CreatePptx(parsedLyrics, filepath, openFirst): p.text = lyric # Apply pharagraph formating - p.font.name = config.font["family"] - p.font.size = Pt(config.font["size"]) - p.font.bold = config.font["bold"] - p.font.italic = config.font["italic"] - p.font.color.rgb = RGBColor(config.font["color"][0], config.font["color"][1], config.font["color"][2]) + p.font.name = settings["font-family"] + p.font.size = Pt(settings["font-size"]) + p.font.bold = settings["font-bold"] + p.font.italic = settings["font-italic"] + p.font.color.rgb = RGBColor.from_string(settings["font-color"][1:]) p.alignment = PP_ALIGN.CENTER - p.line_spacing = config.paragraph["line-spacing"] + p.line_spacing = settings["line-spacing"] # Save powerpoint - prs.save(filepath) -\ No newline at end of file + prs.save(filepath) diff --git a/Songs2Slides/routes.py b/Songs2Slides/routes.py @@ -3,7 +3,8 @@ from flask import render_template, request, send_file, url_for, jsonify import io import json import os -from Songs2Slides import app, models, config +from Songs2Slides import app, models +from Songs2Slides.config import defaultSettings import tempfile @@ -15,16 +16,27 @@ def index(): +# Settings JSON file +@app.route("/settings.json", methods=["GET"]) +def settings(): + return jsonify(defaultSettings) + + + # Get Powerpoint @app.route("/pptx", methods=["POST"]) def pptx(): - # Parse POST parameters + # Get settings + if "settings" in request.json: + settings = request.json["settings"] + else: + settings = defaultSettings + if "songs" in request.json: - # Get lyrics lyrics = [] for song in request.json["songs"]: try: - lyrics += models.ParseLyrics(song[0], song[1]) + lyrics += models.ParseLyrics(song[0], song[1], settings) except: pass elif "lyrics" in request.json: @@ -34,7 +46,7 @@ def pptx(): try: # Create powerpoint temp = tempfile.NamedTemporaryFile(mode="w+t", suffix=".pptx", delete=False) - models.CreatePptx(lyrics, temp.name, False) + models.CreatePptx(lyrics, temp.name, settings, False) temp.close() # Read file into stream @@ -52,13 +64,19 @@ def pptx(): # Get lyrics @app.route("/lyrics", methods=["POST"]) def lyrics(): + # Get settings + if "settings" in request.json: + settings = request.json["settings"] + else: + settings = defaultSettings + # Get lyrics lyrics = [] for song in request.json["songs"]: try: - lyrics += models.ParseLyrics(song[0], song[1]) + lyrics += models.ParseLyrics(song[0], song[1], settings) except: pass # Return lyrics - return jsonify({"lyrics": lyrics}) -\ No newline at end of file + return jsonify({"lyrics": lyrics}) diff --git a/Songs2Slides/static/index.css b/Songs2Slides/static/index.css @@ -3,6 +3,16 @@ body { text-align: center; font-family: Arial, Helvetica, sans-serif; touch-action: manipulation; + margin-bottom: 50px; +} + +#footer { + position: fixed; + bottom: 0px; + padding-bottom: 5px; + padding-top: 5px; + width: 100%; + background-color: #FFFFFF; } @@ -12,13 +22,6 @@ button { height: 25px; } -#title { - font-size: 25px; - font-weight: bold; - margin-bottom: 20px; - display: inline-block; -} - /******** Songs styles ********/ @@ -72,4 +75,36 @@ button { #lyricsBack { float: left; +} + + + +/******** Settings styles ********/ +#settings { + max-width: 300px; + margin: auto; + text-align: left; + line-height: 30px; + background-color: #F0F0F0; + padding: 10px; +} + +h3:first-child { + margin-top: 0px; +} + +h3 { + margin-bottom: 0px; +} + +input[type="number"] { + width: 75px; +} + +#settingsFooter { + margin-top: 15px; +} + +#resetButton { + float: right; } \ No newline at end of file diff --git a/Songs2Slides/static/index.js b/Songs2Slides/static/index.js @@ -3,6 +3,121 @@ setId = 0; // Next valid song id number +// Finishes setting up the page +function onLoad() { + // Load settings + if (localStorage.getItem("settings") == null) { + resetSettings(); + } + else { + loadSettings(JSON.parse(localStorage.getItem("settings"))); + } + + // Add song + AddSong(); +} + + + +// Shows settings interface +function showSettings() { + document.getElementById("songs").hidden = true; + document.getElementById("lyricsContainer").hidden = true; + document.getElementById("thankyou").hidden = true; + document.getElementById("settings").hidden = false; +} + + + +// Loads settings +function loadSettings(settings) { + // Parsing settings + document.getElementById("title-slides").checked = settings["title-slides"]; + document.getElementById("lines-per-slide").value = settings["lines-per-slide"]; + document.getElementById("remove-parentheses").checked = settings["remove-parentheses"]; + + // Slide settings + document.getElementById("slide-width").value = settings["slide-width"]; + document.getElementById("slide-height").value = settings["slide-height"]; + document.getElementById("slide-color").value = settings["slide-color"]; + + // Margin settings + document.getElementById("margin-left").value = settings["margin-left"]; + document.getElementById("margin-right").value = settings["margin-right"]; + document.getElementById("margin-top").value = settings["margin-top"]; + document.getElementById("margin-bottom").value = settings["margin-bottom"]; + + // Font settings + document.getElementById("font-family").value = settings["font-family"]; + document.getElementById("font-size").value = settings["font-size"]; + document.getElementById("font-bold").checked = settings["font-bold"]; + document.getElementById("font-italic").checked = settings["font-italic"]; + document.getElementById("font-color").value = settings["font-color"]; + + // Pharagraph settings + document.getElementById("vertical-alignment").value = settings["vertical-alignment"]; + document.getElementById("line-spacing").value = settings["line-spacing"]; + document.getElementById("word-wrap").checked = settings["word-wrap"]; +} + + + +// Saves settings to local storage +function saveSettings() { + // Get settings + const settings = { + // Parsing settings + "title-slides": document.getElementById("title-slides").checked, + "lines-per-slide": Number(document.getElementById("lines-per-slide").value), + "remove-parentheses": document.getElementById("remove-parentheses").checked, + + // Slide settings + "slide-width": Number(document.getElementById("slide-width").value), + "slide-height": Number(document.getElementById("slide-height").value), + "slide-color": document.getElementById("slide-color").value, + + // Margin settings + "margin-left": Number(document.getElementById("margin-left").value), + "margin-right": Number(document.getElementById("margin-right").value), + "margin-top": Number(document.getElementById("margin-top").value), + "margin-bottom": Number(document.getElementById("margin-bottom").value), + + // Font settings + "font-family": document.getElementById("font-family").value, + "font-size": Number(document.getElementById("font-size").value), + "font-bold": document.getElementById("font-bold").checked, + "font-italic": document.getElementById("font-italic").checked, + "font-color": document.getElementById("font-color").value, + + // Pharagraph settings + "vertical-alignment": document.getElementById("vertical-alignment").value, + "line-spacing": Number(document.getElementById("line-spacing").value), + "word-wrap": document.getElementById("word-wrap").checked, + } + + // Save settings + localStorage.setItem("settings", JSON.stringify(settings)); +} + + + +// Resets all settings to their default values +async function resetSettings() { + // Send POST request + const rawResponse = await fetch("/settings.json", { + method: 'GET' + }); + const json = await rawResponse.json(); + + // Load changes + loadSettings(json); + + // Save changes + localStorage.setItem("settings", JSON.stringify(json)); +} + + + // Adds a song function AddSong() { // Create row @@ -58,7 +173,7 @@ async function SubmitSongs() { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' - }, body: JSON.stringify({"songs":songs}) + }, body: JSON.stringify({"songs":songs,"settings":JSON.parse(localStorage.getItem("settings"))}) }); // Download powerpoint @@ -82,7 +197,7 @@ async function ReviewLyrics() { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' - }, body: JSON.stringify({"songs":songs}) + }, body: JSON.stringify({"songs":songs,"settings":JSON.parse(localStorage.getItem("settings"))}) }); const json = await rawResponse.json(); @@ -107,7 +222,7 @@ async function SubmitLyrics() { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' - }, body: JSON.stringify({"lyrics":lyrics}) + }, body: JSON.stringify({"lyrics":lyrics,"settings":JSON.parse(localStorage.getItem("settings"))}) }); // Download powerpoint @@ -125,6 +240,7 @@ function Back() { document.getElementById("songs").hidden = false; document.getElementById("lyricsContainer").hidden = true; document.getElementById("thankyou").hidden = true; + document.getElementById("settings").hidden = true; } @@ -143,4 +259,4 @@ function Reset() { // Makes songs visible Back(); -} -\ No newline at end of file +} diff --git a/Songs2Slides/templates/index.html b/Songs2Slides/templates/index.html @@ -7,8 +7,8 @@ <script src="{{ url_for('static', filename='index.js') }}"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/downloadjs/1.4.8/download.min.js"></script> </head> - <body onload="AddSong()"> - <label id="title">Songs2Slides</label> + <body onload="onLoad();"> + <h1>Songs2Slides</h1> <div id="songs"> <div id="songsButtons"> @@ -42,5 +42,104 @@ <p>Thankyou for using Songs2Slides</p> <p>Click <a href="javascript:Reset()">here</a> to create another PowerPoint.</p> </div> + + <div id="settings" hidden> + <h3>Parsing:</h3> + <div> + Add title slides: + <input type="checkbox" id="title-slides" onchange="saveSettings();"/> + </div> + <div> + Default lines per slide: + <input type="number" id="lines-per-slide" onchange="saveSettings();"/> + </div> + <div> + Remove content in parentheses: + <input type="checkbox" id="remove-parentheses" onchange="saveSettings();"/> + </div> + + <h3>Slide settings</h3> + <div> + Width (Inches): + <input type="number" id="slide-width" onchange="saveSettings();"/> + </div> + <div> + Height (Inches): + <input type="number" id="slide-height" onchange="saveSettings();"/> + </div> + <div> + Background color: + <input type="color" id="slide-color" onchange="saveSettings();"/> + </div> + + <h3>Margin settings</h3> + <div> + Left (Inches): + <input type="number" id="margin-left" onchange="saveSettings();"/> + </div> + <div> + Right (Inches): + <input type="number" id="margin-right" onchange="saveSettings();"/> + </div> + <div> + Top (Inches): + <input type="number" id="margin-top" onchange="saveSettings();"/> + </div> + <div> + Bottom (Inches): + <input type="number" id="margin-bottom" onchange="saveSettings();"/> + </div> + + <h3>Font settings</h3> + <div> + Family: + <select id="font-family" onchange="saveSettings();"> + <option>Calibri</option> + </select> + </div> + <div> + Size: + <input type="number" id="font-size" onchange="saveSettings();"/> + </div> + <div> + Bold: + <input type="checkbox" id = "font-bold" onchange="saveSettings();"/> + </div> + <div> + Italic: + <input type="checkbox" id="font-italic" onchange="saveSettings();"/> + </div> + <div> + Color: + <input type="color" id="font-color" onchange="saveSettings();"/> + </div> + + <h3>Pharagraph settings</h3> + <div> + Vertical Alignment: + <select id="vertical-alignment" onchange="saveSettings();"> + <option>Top</option> + <option>Middle</option> + <option>Bottom</option> + </select> + </div> + <div> + Line spacing: + <input type="number" id="line-spacing" onchange="saveSettings();"/> + </div> + <div> + Word wrap: + <input type="checkbox" id="word-wrap" onchange="saveSettings();"/> + </div> + + <div id="settingsFooter"> + <a href="javascript:Back();">Back</a> + <button id="resetButton" onclick="resetSettings();">Reset</button> + </div> + </div> + + <div id="footer"> + <a href="javascript:showSettings();">Settings</a> + </div> </body> </html> \ No newline at end of file diff --git a/cliapp.py b/cliapp.py @@ -1,7 +1,8 @@ # Import dependencies import json import os -from Songs2Slides import models, config +from Songs2Slides import models +from Songs2Slides.config import defaultSettings import subprocess import sys import tempfile @@ -24,7 +25,7 @@ if (__name__ == "__main__"): # Get song lyrics try: - lyrics += models.ParseLyrics(title, artist) + lyrics += models.ParseLyrics(title, artist, defaultSettings) except: print("The song could not be found. Make sure that you spelled it correctly.") song -= 1 @@ -79,7 +80,7 @@ if (__name__ == "__main__"): # Create powerpoint try: - models.CreatePptx(lyrics, filepath, openFirst) + models.CreatePptx(lyrics, filepath, defaultSettings, openFirst) except: print("There was an error while creating the powerpoint.") input("Press enter to exit...") @@ -92,4 +93,4 @@ if (__name__ == "__main__"): except: print("There was an error while opening the powerpoint.") input("Press enter to exit...") - sys.exit() -\ No newline at end of file + sys.exit()