commit 070a73a9c03f377a248c60afcd7a8ee7123924f0
parent 4109768149eed0b380a315f62397d162f1680958
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sun, 18 Feb 2024 20:26:41 -0800
Remove v2.2.0 legacy code
Diffstat:
25 files changed, 1 insertion(+), 1435 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
@@ -1,25 +0,0 @@
-name: Python Application
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- test:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Set up Python 3.8
- uses: actions/setup-python@v2
- with:
- python-version: 3.8
-
- - name: Install dependencies
- run: pip install -r requirements.txt
-
- - name: Run tests with unittest
- run: python -m unittest
diff --git a/README.md b/README.md
@@ -1,26 +1,2 @@
# Songs2Slides
-Creates a lyrics powerpoint from a list of songs. This program does NOT add any copyright information to the powerpoint. The user must do this manually.
-
-## Features
-- 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 format of the powerpoint can be customized.
-- The slides can be added to a new or existing powerpoint.
-
-## Usage
-Note: Songs2Slides requires Python 3.6.x
-
-Install the python requirements.
-```
-pip install -r requirements.txt
-```
-To use the command line interface, run `cliapp.py`.
-```
-python cliapp.py
-```
-To use the web interface, run `webapp.py` and then open http://localhost:5000 in your web browser.
-```
-python webapp.py
-start http://localhost:5000
-```
+Creates a lyrics powerpoint from a list of songs
diff --git a/Songs2Slides/__init__.py b/Songs2Slides/__init__.py
@@ -1,6 +0,0 @@
-# Create app
-from flask import Flask
-app = Flask(__name__)
-
-# Define routes
-from Songs2Slides import routes
-\ No newline at end of file
diff --git a/Songs2Slides/config.py b/Songs2Slides/config.py
@@ -1,46 +0,0 @@
-# Contains default parsing and PowerPoint settings
-defaultSettings = {
- # Parsing settings
- "title-slides": True,
- "slide-between-songs": True,
- "lines-per-slide": 4,
- "remove-parentheses": False,
-
- # 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 settings
- "font-family": "Calibri",
- "font-size": 40,
- "font-bold": False,
- "font-italic": False,
- "font-color": "#FFFFFF",
-
- # Paragraph settings
- "vertical-alignment": "Middle", # Can be Top, Middle, or Bottom
- "line-spacing": 1.25,
- "word-wrap": True
-}
-
-
-
-# Contains cached and custom song information
-cachedSongs = {
- # Keys should be lowercase ASCII strings without whitespace or special characters
- "test-artist-test-song": {
- # Title and Artist of the song (formated however you want)
- "title":"Test Song",
- "artist":"Test Artist",
-
- # Lyrics with two newlines between each stanza and no newlines at the beginning or end
- "lyrics":"test1\ntest2\n\ntest3\ntest4"
- }
-}
diff --git a/Songs2Slides/core.py b/Songs2Slides/core.py
@@ -1,224 +0,0 @@
-# Import dependencies
-from bs4 import BeautifulSoup
-from pptx import Presentation
-from pptx.dml.color import RGBColor
-from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
-from pptx.util import Inches, Pt
-import re
-import requests
-from Songs2Slides import config
-from unidecode import unidecode
-
-
-
-def GetLyrics(title, artist):
- """
- Get the lyrics to a song.
-
- Parameters
- ----------
- title : str
- The title of the song.
- artist : str
- The name of the song's artist.
-
- Returns
- -------
- lyrics : str
- The lyrics to the song.
- title : str
- The title of the song.
- artist : str
- The name of the song's artist.
- """
-
- # Convert to lowercase
- artist = artist.lower()
- title = title.lower()
-
- # Replace invalid characters
- old = [" ", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "'", "?", "/", "|", "\\", ".", ","]
- new = ["-", "", "", "", "s", "", "-", "-and-", "", "", "", "-", "-", "", "", "", "", "", "", ""]
- for i in range(0, len(old)):
- artist = artist.replace(old[i], new[i])
- title = title.replace(old[i], new[i])
-
- # Replace unicode characters
- artist = unidecode(artist)
- title = unidecode(title)
-
- # Remove unnecessary dashes
- artist = "-".join(list(filter(lambda a: a != "", artist.split("-"))))
- title = "-".join(list(filter(lambda a: a != "", title.split("-"))))
-
- # Get song info
- if (f"{artist}-{title}" in config.cachedSongs):
- # Get the cache key
- key = f"{artist}-{title}"
-
- # Get info from cache
- lyrics = config.cachedSongs[key]["lyrics"]
- title = config.cachedSongs[key]["title"]
- artist = config.cachedSongs[key]["artist"]
- else:
- # Get page from the internet
- page = requests.get(f"https://genius.com/{artist}-{title}-lyrics")
- soup = BeautifulSoup(page.text, "html.parser")
-
- # Find song info
- divs = soup.find_all("div", class_="Lyrics__Container-sc-1ynbvzw-8")
- lyrics = "\n".join([div.get_text(separator="\n") for div in divs])
- title = soup.find("h1", class_="SongHeader__Title-sc-1b7aqpg-7").get_text()
- artist = soup.find("a", class_="SongHeader__Artist-sc-1b7aqpg-9").get_text()
-
- # Return lyrics
- return lyrics, title, artist
-
-
-
-def ParseLyrics(title, artist, settings):
- """
- Parse the lyrics of a song into slides.
-
- Parameters
- ----------
- title : str
- The title of the song.
- artist : str
- The name of the song's artist.
- settings : dict
- The settings to use when parsing the lyrics into slides.
-
- Returns
- -------
- list
- A list of strings containing the lyrics to the song. Each list item is one slide.
- """
-
- # Get lyrics
- rawLyrics, title, artist = GetLyrics(title, artist)
-
- # Remove content in parentheses
- if (settings["remove-parentheses"]):
- rawLyrics = re.sub(r'\s?\([^)]*\)', '', rawLyrics)
-
- # Parse Lyrics
- rawLines = rawLyrics.split("\n")
-
- # Add title slide
- slides = []
- if (settings["title-slides"]):
- slides += ["{0}\n{1}".format(title, artist)]
-
- # Parse lyrics into slides
- slideSize = settings["lines-per-slide"]
- for i in range(0, len(rawLines)):
- if (rawLines[i] == "" or rawLines[i].startswith("[")):
- # Start a new slide without content
- slides.append("")
- slideSize = 0
- elif (slideSize == settings["lines-per-slide"]):
- # Start a new slide with content
- slides.append(rawLines[i])
- slideSize = 1
- elif (slideSize == 0):
- # Continue a blank slide
- slides[-1] = slides[-1] + rawLines[i]
- slideSize += 1
- else:
- # Continue a slide
- slides[-1] = slides[-1] + "\n" + rawLines[i]
- slideSize += 1
-
- # Add/remove blank slide
- if (slides[-1] != "" and settings["slide-between-songs"]):
- slides += [""]
- elif (slides[-1] == "" and not settings["slide-between-songs"]):
- del slides[-1]
-
- # Return parsed lyrics
- return slides
-
-
-
-def CreatePptx(parsedLyrics, filepath, settings, openFirst):
- """
- Create a PowerPoint from a list of lyrics.
-
- Parameters
- ----------
- parsedLyrics : list
- The list of strings containing the lyrics. Each list item will be turned into one slide.
- filepath : str
- The filepath to save the PowerPoint to.
- settings : dict
- The settings to use while creating the filepath.
- openFirst : bool
- Whether to add on to the PowerPoint file if it already exists.
- """
-
- if (openFirst):
- try:
- # Open presentation
- prs = Presentation(filepath)
- except:
- # Create presentation
- prs = Presentation()
-
- # Set slide width and 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(settings["slide-width"])
- prs.slide_height = Inches(settings["slide-height"])
-
- # Get blank slide
- blank_slide_layout = prs.slide_layouts[6]
-
- # Get margins
- 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
- slide = prs.slides.add_slide(blank_slide_layout)
-
- # Apply slide formating
- slide.background.fill.solid()
- 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)
- tf = txBox.text_frame
- tf.clear()
-
- # Apply text formating
- tf.word_wrap = settings["word-wrap"]
- if (settings["vertical-alignment"].lower() == "top"):
- tf.vertical_anchor = MSO_ANCHOR.TOP
- elif (settings["vertical-alignment"].lower() == "bottom"):
- tf.vertical_anchor = MSO_ANCHOR.BOTTOM
- else:
- tf.vertical_anchor = MSO_ANCHOR.MIDDLE
-
- # Add pharagraph
- p = tf.paragraphs[0]
- p.text = lyric
-
- # Apply pharagraph formating
- 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 = settings["line-spacing"]
-
- # Save powerpoint
- prs.save(filepath)
diff --git a/Songs2Slides/routes.py b/Songs2Slides/routes.py
@@ -1,101 +0,0 @@
-# Import dependencies
-from copy import deepcopy
-from flask import render_template, request, send_file, url_for, jsonify
-import io
-import json
-import os
-from Songs2Slides import app, core
-from Songs2Slides.config import defaultSettings
-import tempfile
-
-
-
-# Home page
-@app.route("/", methods=["GET"])
-def home():
- return render_template("home.html")
-
-
-
-# Settings page
-@app.route("/settings/", methods=["GET"])
-def settings():
- return render_template("settings.html", title = "Settings")
-
-
-
-# Settings JSON file
-@app.route("/settings.json", methods=["GET"])
-def settingsJSON():
- return jsonify(defaultSettings)
-
-
-
-# Get Powerpoint
-@app.route("/pptx", methods=["POST"])
-def pptx():
- # Get settings
- settings = deepcopy(defaultSettings)
- try:
- for setting in request.json["settings"]:
- settings[setting] = request.json["settings"][setting]
- except:
- pass
-
- try:
- # Get temp
- temp = tempfile.NamedTemporaryFile(mode="w+t", suffix=".pptx", delete=False)
- temp.close()
-
- # Get lyrics
- lyrics = json.loads(request.form["lyrics"])
-
- # Save uploaded powerpoint
- if (request.files["pptxFile"].filename != ""):
- request.files["pptxFile"].save(temp.name)
-
- # Create powerpoint
- core.CreatePptx(lyrics, temp.name, settings, True)
-
- # Read file into stream
- with open(temp.name, 'rb') as f:
- pptx = io.BytesIO(f.read())
- finally:
- # Delete temp file
- os.remove(temp.name)
-
- # Return powerpoint
- return send_file(pptx, as_attachment=True, attachment_filename='download.pptx')
-
-
-
-# Get lyrics
-@app.route("/lyrics", methods=["POST"])
-def lyrics():
- # Get settings
- settings = deepcopy(defaultSettings)
- try:
- for setting in request.json["settings"]:
- settings[setting] = request.json["settings"][setting]
- except:
- pass
-
- # Get lyrics
- lyrics = []
- failed = []
- for song in request.json["songs"]:
- try:
- lyrics += core.ParseLyrics(song[0], song[1], settings)
- except:
- failed += [song]
-
- # Return lyrics
- return jsonify({"lyrics": lyrics, "errors": failed})
-
-
-
-# 404 page
-@app.errorhandler(404)
-def error404(e):
- message = "The requested URL was not found on the server."
- return render_template("error.html", title="404 Not Found", code="404", message=message), 404
diff --git a/Songs2Slides/static/error.js b/Songs2Slides/static/error.js
@@ -1,8 +0,0 @@
-/**
- * Finishes setting up the page
- * @returns {void}
- */
-function onLoad() {
- // Set theme
- UpdateTheme();
-}
diff --git a/Songs2Slides/static/favicon-180.png b/Songs2Slides/static/favicon-180.png
Binary files differ.
diff --git a/Songs2Slides/static/favicon-32.png b/Songs2Slides/static/favicon-32.png
Binary files differ.
diff --git a/Songs2Slides/static/global.css b/Songs2Slides/static/global.css
@@ -1,88 +0,0 @@
-/******** Variable styles ********/
-:root {
- --theme-color: #ffff50;
- --foreground-color: #000000;
- --background-color: #ffffff;
- --border-color: #808080;
- --hover-color: #f0f0f0;
-}
-
-
-
-/******** Global styles ********/
-body {
- text-align: center;
- font-family: Arial, Helvetica, sans-serif;
- touch-action: manipulation;
- margin-top: 80px;
- color: var(--foreground-color);
- background-color: var(--background-color);
-}
-
-button:not(.songRemove), input, select, textarea {
- background-color: var(--background-color);
- border: 1px solid var(--border-color);
- color: var(--foreground-color);
- border-radius: 0px;
-}
-
-button:hover:enabled:not(.songRemove) {
- cursor: pointer;
- background-color: var(--hover-color);
-}
-
-button:disabled {
- background-color: var(--hover-color);
- color: var(--border-color);
-}
-
-input[type="file"] {
- background-color: var(--hover-color);
- border: none;
-}
-
-h1 {
- margin-top: 15px;
- margin-bottom: 15px;
-}
-
-#header {
- position: fixed;
- top: 0px;
- left: 0px;
- width: 100%;
- background-color: var(--theme-color);
-}
-
-
-.container {
- margin: auto;
- padding: 10px;
- background-color: var(--hover-color);
-}
-
-button {
- height: 25px;
-}
-
-
-
-/******** Dark styles ********/
-body.dark {
- --foreground-color: #e0e0e0;
- --background-color: #121416;
- --hover-color: #323436;
- --border-color: #505050;
-}
-
-.dark h1 {
- color: #000000;
-}
-
-.dark button:not(.songRemove), .dark input, .dark select {
- --background-color: #222426;
-}
-
-.dark a {
- color: #0080ff;
-}
diff --git a/Songs2Slides/static/global.js b/Songs2Slides/static/global.js
@@ -1,32 +0,0 @@
-/**
- * Updates the interface theme
- * @param {bool} theme - False for light mode, true for dark mode
- * @returns {void}
- */
-function UpdateTheme(theme = null) {
- // Get theme from localStorage
- if (theme === null) {
- theme = localStorage.getItem("theme");
- }
-
- // Detect preferred color scheme
- if (theme === null) {
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
- theme = "Dark";
- }
- else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
- theme = "Light";
- }
- }
-
- // Apply theme
- if (theme === "Dark") {
- document.body.classList.add("dark");
- }
- else {
- document.body.classList.remove("dark");
- }
-
- // Save theme
- localStorage.setItem("theme", theme);
-}
diff --git a/Songs2Slides/static/home.css b/Songs2Slides/static/home.css
@@ -1,110 +0,0 @@
-/******** Variable styles ********/
-:root {
- --song-color: #C0C0C0;
- --error-color: #E00000;
-}
-
-
-
-/******** Songs styles ********/
-#songsContainer {
- width: fit-content;
- width: -moz-fit-content;
-}
-
-h3 {
- margin-top: 0px;
-}
-
-.song {
- width: fit-content;
- width: -moz-fit-content;
- margin-left: auto;
- margin-right: auto;
- margin-top: 10px;
- margin-bottom: 10px;
- padding: 8px;
- background: var(--song-color);
- border-radius: 5px;
-}
-
-.songRemove {
- border: none;
- padding: 0px;
- margin: 0px;
- cursor: pointer;
- color: var(--foreground-color);
- background-color: var(--song-color);
-}
-
-#addSong {
- display: inline-block;
- padding: 0px;
- height: 40px;
- width: 40px;
- background-color: var(--song-color);
- border: none;
- border-radius: 50%;
-}
-
-#addSong img {
- height: 40px;
- width: 40px;
-}
-
-#songsButtons {
- margin-top: 15px;
- height: 25px;
- text-align: left;
-}
-
-#reviewButton {
- float: right;
-}
-
-
-
-/******** Lyrics styles ********/
-#lyricsContainer {
- max-width: 500px;
-}
-
-#lyricsContainer p {
- text-align: left;
-}
-
-#rawLyrics {
- width: 100%;
- resize: none;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-
-#fileUpload {
- text-align: left;
- margin-top: 5px;
-}
-
-#errors {
- margin-top: 15px;
- color: var(--error-color);
- font-weight: bold;
-}
-
-#lyricsButtons {
- margin-top: 15px;
- text-align: left;
- height: 25px;
-}
-
-#submitLyricsButton {
- float: right;
-}
-
-
-
-/******** Dark styles ********/
-body.dark {
- --song-color: #525456
-}
diff --git a/Songs2Slides/static/home.js b/Songs2Slides/static/home.js
@@ -1,173 +0,0 @@
-// Declare global variables
-setId = 0; // Next valid song id number
-
-
-
-/**
- * Finishes setting up the page
- * @returns {void}
- */
-function onLoad() {
- // Add song
- AddSong();
-
- // Set theme
- UpdateTheme();
-}
-
-
-
-/**
- * Adds a blank song to the songs div
- * @returns {void}
- */
-function AddSong() {
- // Create row
- let clone = document.getElementById("songTemplate").content.cloneNode(true);
-
- // Set row id
- clone.children[0].setAttribute("id", `song-${setId}`);
-
- // Add remove button onclick event
- clone.getElementById("remove").setAttribute("onclick", `let element = document.getElementById('song-${setId}'); element.parentNode.removeChild(element);`);
-
- // Add row
- document.getElementById("songs").appendChild(clone);
-
- // Increment setId
- setId++;
-}
-
-
-
-/**
- * Gets the list of songs in the songs div
- * @returns {list} - List of songs information
- */
-function getSongs() {
- // Get song info
- let titles = [];
- for (title of document.getElementsByClassName("title")) {
- titles.push(title.value);
- }
- let artists = [];
- for (artist of document.getElementsByClassName("artist")) {
- artists.push(artist.value);
- }
-
- // Prepare songs
- let songs = []
- for (let i = 0; i < titles.length; i++) {
- songs.push([titles[i], artists[i]])
- }
-
- // Set songs
- return songs
-}
-
-
-
-/**
- * Gets the parsed lyrics for the user to review
- * @returns {void}
- */
-async function ReviewLyrics() {
- // Show and hide elements
- document.getElementById("rawLyrics").value = "Loading lyrics...";
- document.getElementById("rawLyrics").readOnly = true;
- document.getElementById("errors").textContent = "";
- document.getElementById("songsContainer").hidden = true;
- document.getElementById("lyricsContainer").hidden = false;
-
- // Get songs
- let songs = getSongs();
-
- // Send POST request
- const rawResponse = await fetch("/lyrics", {
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- }, body: JSON.stringify({"songs":songs,"settings":JSON.parse(localStorage.getItem("settings"))})
- });
- const json = await rawResponse.json();
-
- // Set lyrics
- document.getElementById("rawLyrics").value = json["lyrics"].join("\n\n")
- document.getElementById("rawLyrics").readOnly = false;
-
- // Set errors
- if (json["errors"].length != 0)
- {
- let errors = "The lyrics to the following songs could not be found: ";
- for (error of json["errors"]) {
- errors += `"${error[0]}" by "${error[1]}", `;
- }
- errors = errors.slice(0, -2); // Remove trailing ', '
- document.getElementById("errors").textContent = errors;
- }
-}
-
-
-
-/**
- * Prepares the lyrics form to be submitted and shows the thankyou screen
- * @returns {void}
- */
-async function SubmitLyrics() {
- // Get lyrics
- let lyrics = document.getElementById("rawLyrics").value.split(/\n\s*\n/);
-
- // Set hidden form values
- document.getElementById("pptxSettingsField").value = localStorage.getItem("settings");
- document.getElementById("lyricsField").value = JSON.stringify(lyrics);
-
- // Show and hide elements
- document.getElementById("lyricsContainer").hidden = true;
- document.getElementById("thankyou").hidden = false;
-}
-
-
-
-/**
- * Makes the previous screen visible
- * @returns {void}
- */
-function Back() {
- if (document.getElementById("lyricsContainer").hidden == false) {
- document.getElementById("songsContainer").hidden = false;
- document.getElementById("lyricsContainer").hidden = true;
- document.getElementById("thankyou").hidden = true;
- }
- else if (document.getElementById("thankyou").hidden == false) {
- document.getElementById("songsContainer").hidden = true;
- document.getElementById("lyricsContainer").hidden = false;
- document.getElementById("thankyou").hidden = true;
- }
-}
-
-
-
-/**
- * Clears the list of songs and makes the songs screen visible
- * @returns {void}
- */
-function Reset() {
- // Remove songs
- let songs = document.getElementsByClassName("song");
- while (songs.length > 0) {
- // Get song
- songs[0].parentNode.removeChild(songs[0]);
- }
-
- // Reset pptx file input
- document.getElementsByName("pptxFile")[0].value = null;
-
- // Add blank song
- AddSong();
-
- // Makes songs visible
- document.getElementById("songsContainer").hidden = false;
- document.getElementById("lyricsContainer").hidden = true;
- document.getElementById("thankyou").hidden = true;
-}
diff --git a/Songs2Slides/static/plus.png b/Songs2Slides/static/plus.png
Binary files differ.
diff --git a/Songs2Slides/static/settings.css b/Songs2Slides/static/settings.css
@@ -1,32 +0,0 @@
-/******** Settings styles ********/
-#settings {
- max-width: 300px;
- text-align: left;
- line-height: 30px;
-}
-
-h2 {
- margin-top: 0px;
- margin-bottom: 0px;
- text-align: center;
-}
-
-h4:first-of-type {
- margin-top: 0px;
-}
-
-h4 {
- margin-bottom: 0px;
-}
-
-input[type="number"] {
- width: 75px;
-}
-
-#settingsFooter {
- margin-top: 15px;
-}
-
-#resetButton {
- float: right;
-}
diff --git a/Songs2Slides/static/settings.js b/Songs2Slides/static/settings.js
@@ -1,127 +0,0 @@
-/**
- * Finishes setting up the page
- * @returns {void}
- */
-function onLoad() {
- // Load settings
- if (localStorage.getItem("settings") == null) {
- resetSettings();
- }
- else {
- loadSettings(JSON.parse(localStorage.getItem("settings")));
- }
-}
-
-
-
-// Loads settings
-/**
- * Loads settings
- * @param {object} settings - The settings object
- * @returns {void}
- */
-function loadSettings(settings) {
- // Interface settings (not stored with other settings)
- UpdateTheme();
- if (document.body.classList.contains("dark")) {
- document.getElementById("theme").value = "Dark";
- }
- else {
- document.getElementById("theme").value = "Light";
- }
-
- // Parsing settings
- document.getElementById("title-slides").checked = settings["title-slides"];
- document.getElementById("slide-between-songs").checked = settings["slide-between-songs"];
- 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 localStorage
- * @returns {void}
- */
-function saveSettings() {
- // Save interface settings and update interface
- UpdateTheme(document.getElementById("theme").value);
-
- // Get settings
- const settings = {
- // Parsing settings
- "title-slides": document.getElementById("title-slides").checked,
- "slide-between-songs": document.getElementById("slide-between-songs").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
- * @returns {void}
- */
-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));
-}
diff --git a/Songs2Slides/templates/error.html b/Songs2Slides/templates/error.html
@@ -1,12 +0,0 @@
-{% extends "layout.html" %}
-
-{% block header %}
- <script src="{{ url_for('static', filename='error.js') }}"></script>
-{% endblock header %}
-
-{% block content %}
- <h1>{{ code }}</h1>
- <p>{{ message }}</p>
-
- <p><a href="{{ url_for('home') }}">Return to the homepage</a></p>
-{% endblock content %}
diff --git a/Songs2Slides/templates/home.html b/Songs2Slides/templates/home.html
@@ -1,62 +0,0 @@
-{% extends "layout.html" %}
-
-{% block header %}
- <link rel="stylesheet" href="{{ url_for('static', filename='home.css') }}"></link>
- <script src="{{ url_for('static', filename='home.js') }}"></script>
-{% endblock header %}
-
-{% block content %}
- <div id="songsContainer" class="container">
- <h3>Choose your songs</h3>
-
- <template id="songTemplate">
- <div class="song">
- <input type="text" class="title" placeholder="Song Title"/>
- <input type="text" class="artist" placeholder="Song Artist"/>
- <button id="remove" class="songRemove">╳</button>
- </div>
- </template>
- <div id="songs"></div>
-
- <div>
- <button id="addSong" onclick="AddSong();">
- <img src="{{ url_for('static', filename='plus.png')}}"/>
- </button>
- </div>
-
- <div id="songsButtons">
- <a href="/settings/">Settings</a>
- <button id="reviewButton" onclick="ReviewLyrics();">Next</button>
- </div>
- </div>
-
- <form id="lyricsContainer" class="container" hidden action="/pptx" method="POST" enctype="multipart/form-data">
- <h3>Review your PowerPoint</h3>
-
- <p>Review and edit the parsed lyrics below and then click the create PowerPoint button.</p>
- <p>One blank line represents the start of a new slide and three blank lines represent a blank slide.</p>
-
- <input type="text" id="pptxSettingsField" name="settings" hidden>
- <input type="text" id="lyricsField" name="lyrics" hidden>
-
- <textarea rows="15" id="rawLyrics"></textarea>
-
- <div id="fileUpload">
- <label>Starting PowerPoint (optional): </label>
- <input type="file" name="pptxFile" accept="application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.ms-powerpoint">
- </div>
-
- <p id="errors"></p>
-
- <div id="lyricsButtons">
- <a id="lyricsBack" href="javascript:Back()">Back</a>
- <button id="submitLyricsButton" onclick="SubmitLyrics()">Create Powerpoint</button>
- </div>
- </form>
-
- <div id="thankyou" hidden>
- <h3>Thank you for using Songs2Slides</h3>
- <p><a href="javascript:Back()">Edit your PowerPoint</a></p>
- <p><a href="javascript:Reset()">Create another PowerPoint</a></p>
- </div>
-{% endblock content %}
diff --git a/Songs2Slides/templates/layout.html b/Songs2Slides/templates/layout.html
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- {% if title %}
- <title>{{ title }}</title>
- {% else %}
- <title>SongsSlides</title>
- {% endif %}
-
- <meta name="description" content="Quickly and easily create lyric powerpoints from a list of songs.">
- <meta name="viewport" content="width=device-width, user-scalable=no"/>
- <link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon-32.png') }}">
- <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='favicon-180.png') }}">
- <link rel="stylesheet" href="{{ url_for('static', filename='global.css') }}"></link>
- <script src="{{ url_for('static', filename='global.js') }}"></script>
-
- {% block header %}{% endblock %}
- </head>
- <body onload="onLoad();">
- <div id="header">
- <h1>Songs2Slides</h1>
- </div>
-
- {% block content %}{% endblock %}
- </body>
-</html>
diff --git a/Songs2Slides/templates/settings.html b/Songs2Slides/templates/settings.html
@@ -1,130 +0,0 @@
-{% extends "layout.html" %}
-
-{% block header %}
- <link rel="stylesheet" href="{{ url_for('static', filename='settings.css') }}"></link>
- <script src="{{ url_for('static', filename='settings.js') }}"></script>
-{% endblock header %}
-
-{% block content %}
- <div id="settings" class="container">
- <h2>Settings</h2>
- <h4>Interface</h4>
- <div>
- Theme:
- <select id="theme" onchange="saveSettings();">
- <option>Light</option>
- <option>Dark</option>
- </select>
- </div>
- <h4>Parsing</h4>
- <div>
- Add title slides:
- <input type="checkbox" id="title-slides" onchange="saveSettings();"/>
- </div>
- <div>
- Blank slide between songs:
- <input type="checkbox" id="slide-between-songs" onchange="saveSettings();"/>
- </div>
- <div>
- Default lines per slide:
- <input type="number" min="1" step="1" id="lines-per-slide" onchange="saveSettings();"/>
- </div>
- <div>
- Remove content in parentheses:
- <input type="checkbox" id="remove-parentheses" onchange="saveSettings();"/>
- </div>
-
- <h4>Slide settings</h4>
- <div>
- Width (Inches):
- <input type="number" min="0" step="any" id="slide-width" onchange="saveSettings();"/>
- </div>
- <div>
- Height (Inches):
- <input type="number" min="0" step="any" id="slide-height" onchange="saveSettings();"/>
- </div>
- <div>
- Background color:
- <input type="color" id="slide-color" onchange="saveSettings();"/>
- </div>
-
- <h4>Margin settings</h4>
- <div>
- Left (Inches):
- <input type="number" min="0" step="any" id="margin-left" onchange="saveSettings();"/>
- </div>
- <div>
- Right (Inches):
- <input type="number" min="0" step="any" id="margin-right" onchange="saveSettings();"/>
- </div>
- <div>
- Top (Inches):
- <input type="number" min="0" step="any" id="margin-top" onchange="saveSettings();"/>
- </div>
- <div>
- Bottom (Inches):
- <input type="number" min="0" step="any" id="margin-bottom" onchange="saveSettings();"/>
- </div>
-
- <h4>Font settings</h4>
- <div>
- Family:
- <input id="font-family" list="fonts" onchange="saveSettings();">
- <datalist id="fonts">
- <option>Arial</option>
- <option>Century</option>
- <option>Century Gothic</option>
- <option>Calibri</option>
- <option>Comic Sans MS</option>
- <option>Courier New</option>
- <option>Georgia</option>
- <option>Impact</option>
- <option>Rockwell</option>
- <option>Segoe UI</option>
- <option>Tahoma</option>
- <option>Times New Roman</option>
- <option>Trebuchet MS</option>
- <option>Verdana</option>
- </datalist>
- </div>
- <div>
- Size:
- <input type="number" min="0" step="1" 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>
-
- <h4>Pharagraph settings</h4>
- <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" min="0" step="any" id="line-spacing" onchange="saveSettings();"/>
- </div>
- <div>
- Word wrap:
- <input type="checkbox" id="word-wrap" onchange="saveSettings();"/>
- </div>
-
- <div id="settingsFooter">
- <a href="../">Back</a>
- <button id="resetButton" onclick="resetSettings();">Reset</button>
- </div>
- </div>
-{% endblock content %}
diff --git a/Tests/__init__.py b/Tests/__init__.py
diff --git a/Tests/test_core.py b/Tests/test_core.py
@@ -1,88 +0,0 @@
-# Import dependencies
-from Songs2Slides import core, config
-import unittest
-from unittest.mock import patch
-
-
-
-class TestCore(unittest.TestCase):
- # Test GetLyrics method on cached songs
- def test_GetLyrics_cache(self):
- # Set cached songs
- config.cachedSongs = {
- "test-artist-test-song": {
- "title": "Test Song",
- "artist": "Test Artist",
- "lyrics":"test1\ntest2\n\ntest3\ntest4"
- }
- }
-
- # Test cached song
- lyrics, title, artist = core.GetLyrics("tEsT sOnG", "tEsT aRtIsT")
- self.assertEqual(lyrics, "test1\ntest2\n\ntest3\ntest4")
- self.assertEqual(title, "Test Song")
- self.assertEqual(artist, "Test Artist")
-
-
-
- # Test GetLyrics method on songs on the internet
- def test_GetLyrics_web(self):
- with patch('Songs2Slides.core.requests.get') as mocked_get:
- # Initialize mocked_get
- mocked_get.return_value.text = b"<!DOCTYPE html><html><head></head><body><h1 class=\"SongHeader__Title-sc-1b7aqpg-7\">Test Song 2</h1><a class=\"SongHeader__Artist-sc-1b7aqpg-9\">Test Artist</a><div class=\"Lyrics__Root-sc-1ynbvzw-1\"><div class=\"Lyrics__Container-sc-1ynbvzw-8\">[Verse 1]<br><a><span>Test1<br>Test2<br>Test3</span></a><br><a><span>Test4<br>Test5</span></a></div><div class=\"Lyrics__Container-sc-1ynbvzw-8\">[Verse 2]<br><a><span>Test10<br>Test20</span></a><br>Test30<br>Test40<br><a><span>Test50</span></a></div></div></body></html>"
-
- # Get song lyrics
- lyrics, title, artist = core.GetLyrics("tEsT sOnG 2", "tEsT aRtIsT")
-
- # Validate responce
- mocked_get.assert_called_with("https://genius.com/test-artist-test-song-2-lyrics")
- self.assertEqual(lyrics, "[Verse 1]\nTest1\nTest2\nTest3\nTest4\nTest5\n[Verse 2]\nTest10\nTest20\nTest30\nTest40\nTest50")
- self.assertEqual(title, "Test Song 2")
- self.assertEqual(artist, "Test Artist")
-
-
-
- # Test ParseLyrics method
- def test_ParseLyrics(self):
- # Initialize settings
- settings = {
- "title-slides": True,
- "slide-between-songs": True,
- "lines-per-slide": 4,
- "remove-parentheses": False,
- }
-
- # Mock core.getLyrics method
- with patch('Songs2Slides.core.GetLyrics') as mocked_get:
- # Initialize mocked_get
- mocked_get.return_value = ("[Verse 1]\nTest1\nTest2\nTest3\nTest4\nTest5 (Test5)\n[Verse 2]\nTest10\nTest20\nTest30\nTest40\nTest50(Test50)", "Test Song", "Test Artist")
-
- # Test parser
- lyrics = core.ParseLyrics("tEsT sOnG 2", "tEsT aRtIsT", settings)
- self.assertEqual(lyrics, ["Test Song\nTest Artist", "Test1\nTest2\nTest3\nTest4", "Test5 (Test5)", "Test10\nTest20\nTest30\nTest40", "Test50(Test50)", ""])
- mocked_get.assert_called_with("tEsT sOnG 2", "tEsT aRtIsT")
-
- # Test parser without title slide
- settings["title-slides"] = False
- lyrics = core.ParseLyrics("tEsT sOnG", "tEsT aRtIsT", settings)
- self.assertEqual(lyrics, ["Test1\nTest2\nTest3\nTest4", "Test5 (Test5)", "Test10\nTest20\nTest30\nTest40", "Test50(Test50)", ""])
-
- # Test parser without slide at end
- settings["slide-between-songs"] = False
- lyrics = core.ParseLyrics("tEsT sOnG", "tEsT aRtIsT", settings)
- self.assertEqual(lyrics, ["Test1\nTest2\nTest3\nTest4", "Test5 (Test5)", "Test10\nTest20\nTest30\nTest40", "Test50(Test50)"])
-
- # Test parser with 3 lines per slide
- settings["lines-per-slide"] = 3
- lyrics = core.ParseLyrics("tEsT sOnG", "tEsT aRtIsT", settings)
- self.assertEqual(lyrics, ["Test1\nTest2\nTest3", "Test4\nTest5 (Test5)", "Test10\nTest20\nTest30", "Test40\nTest50(Test50)"])
-
- # Test parser with parentheses remover
- settings["remove-parentheses"] = True
- lyrics = core.ParseLyrics("tEsT sOnG 2", "tEsT aRtIsT", settings)
- self.assertEqual(lyrics, ["Test1\nTest2\nTest3", "Test4\nTest5", "Test10\nTest20\nTest30", "Test40\nTest50"])
-
- # Test parser with blank line
- mocked_get.return_value = ("[Verse 1]\nTest1\n[Instrumental]\n[Verse 2]\nTest2", "Test Song", "Test Artist")
- lyrics = core.ParseLyrics("tEsT sOnG 2", "tEsT aRtIsT", settings)
- self.assertEqual(lyrics, ["Test1", "", "Test2"])
diff --git a/cliapp.py b/cliapp.py
@@ -1,106 +0,0 @@
-# Import dependencies
-import os
-import re
-from Songs2Slides import core
-from Songs2Slides.config import defaultSettings
-import subprocess
-import sys
-import tempfile
-
-
-
-# Run CLI
-if (__name__ == "__main__"):
- # Print title
- print("Songs2Slides")
- print()
-
- # Get song lyrics
- lyrics = []
- song = 1
- while (True):
- # Get song information
- title = input("Song #{0} Title: ".format(song))
- artist = input("Song #{0} Artist: ".format(song))
-
- # Get song lyrics
- try:
- lyrics += core.ParseLyrics(title, artist, defaultSettings)
- except:
- print("The song could not be found. Make sure that you spelled it correctly.")
- song -= 1
-
- # Add more songs
- if (song >= 1 and input("Do you want to add another song? (y/n): ").lower() == "n"):
- break
- else:
- song += 1
-
- # Review lyrics
- if (input("Do you want to review the parsed lyrics first? (y/n): ").lower() == "y"):
- try:
- # Create temp file
- temp = tempfile.NamedTemporaryFile(mode="w+t", suffix=".txt", delete=False)
- temp.writelines("\n\n".join(lyrics))
- temp.close()
-
- # Open temp file and wait
- if (sys.platform == "win32"):
- subprocess.Popen(["notepad", temp.name]).wait()
- elif (sys.platform == "darwin"):
- subprocess.Popen(["open", temp.name]).wait()
- else:
- subprocess.Popen(["xdg-open", temp.name]).wait()
-
- # Read file
- with open(temp.name) as f:
- rawLines = f.read()
-
- # Parse lyrics
- lyrics = re.split("\n\s*\n", rawLines)
- except:
- print("There was an error while reviewing the lyrics. The unrevised lyrics will be used instead.")
- finally:
- # Delete temp file
- os.remove(temp.name)
-
- # Get filepath
- filepath = input("Enter a filepath to save the powerpoint to: ")
-
- # Add extension
- if (len(filepath) == 0):
- filepath = "Untitled.pptx"
- elif (len(filepath) < 4):
- filepath += ".pptx"
- elif (len(filepath) == 4 and filepath[-4:] != ".ppt"):
- filepath += ".pptx"
- elif (len(filepath) > 4 and filepath[-5:] != ".pptx" and filepath[-4:] != ".ppt"):
- filepath += ".pptx"
-
- # Confirm overwrite
- if (os.path.exists(filepath)):
- openFirst = (input("This powerpoint already exists. Do you want to add on to it? (y/n): ").lower() == "y")
- else:
- openFirst = False
-
- # Create powerpoint
- try:
- core.CreatePptx(lyrics, filepath, defaultSettings, openFirst)
- except:
- print("There was an error while creating the powerpoint.")
- input("Press enter to exit...")
- sys.exit()
-
- # Open powerpoint
- if (input("Do you want to view the powerpoint now? (y/n): ").lower() == "y"):
- try:
- if (sys.platform == "win32"):
- os.startfile(filepath)
- elif (sys.platform == "darwin"):
- subprocess.Popen(["open", filepath])
- else:
- subprocess.Popen(["xdg-open", filepath])
- except:
- print("There was an error while opening the powerpoint.")
- input("Press enter to exit...")
- sys.exit()
diff --git a/requirements.txt b/requirements.txt
@@ -1,5 +0,0 @@
-bs4
-python-pptx
-requests
-flask
-unidecode
-\ No newline at end of file
diff --git a/webapp.py b/webapp.py
@@ -1,6 +0,0 @@
-# Import app
-from Songs2Slides import app
-
-# Run app
-if (__name__ == "__main__"):
- app.run(debug=True)
-\ No newline at end of file