commit 7d76c2d1987db4118760a39c6e076a2b01a29115
parent 5f362372df19b2dd58ef2732b78e6e8ed67dfd82
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date: Thu, 23 Apr 2020 19:31:25 -0700
Merge pull request #1 from AsherMorgan/feature-web
Implement web interface.
Diffstat:
12 files changed, 448 insertions(+), 279 deletions(-)
diff --git a/Songs2Slides.py b/Songs2Slides.py
@@ -1,252 +0,0 @@
-# Import dependencies
-from bs4 import BeautifulSoup
-import json
-import os
-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 requests
-import subprocess
-import sys
-import tempfile
-
-
-
-# Gets the lyrics
-def GetLyrics(artist, song):
- # Convert to lowercase
- artist = artist.lower()
- song = song.lower()
-
- # Replace invalid characters
- old = [" ", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "'", "?", "/", "|", "\\", ".", ",", "á", "é", "í", "ó", "ñ", "ú"]
- new = ["-", "", "", "", "s", "", "-", "-and-", "", "", "", "-", "-", "", "", "", "", "", "", "", "a", "e", "i", "o", "n", "u"]
- for i in range(0, len(old)):
- artist = artist.replace(old[i], new[i])
- song = song.replace(old[i], new[i])
-
- # Remove unnecessary dashes
- artist = "-".join(list(filter(lambda a: a != "", artist.split("-"))))
- song = "-".join(list(filter(lambda a: a != "", song.split("-"))))
-
- # Get lyrics
- page = requests.get("https://genius.com/{0}-{1}-lyrics".format(artist, song))
- lyrics = BeautifulSoup(page.text, "html.parser").find("div", class_="lyrics").get_text()
-
- # Return lyrics
- return lyrics
-
-
-
-# Parses the lyrics into blocks
-def ParseLyrics(lyrics, settings):
- # Split lyrics
- rawLines = lyrics.split("\n")
-
- # Remove starting and ending newlines
- del rawLines[0]
- del rawLines[0]
- del rawLines[-1]
- del rawLines[-1]
-
- # Parse lyrics into lines
- lines = []
- slideSize = settings["lines-per-slide"]
- for i in range(0, len(rawLines)):
- if (rawLines[i] == ""):
- # Start a new slide without content
- lines.append("")
- slideSize = 0
- elif (rawLines[i][0] == "["):
- # Ignore
- pass
- elif (slideSize == settings["lines-per-slide"]):
- # Start a new slide with content
- lines.append(rawLines[i])
- slideSize = 1
- elif (slideSize == 0):
- # Continue a blank slide
- lines[-1] = lines[-1] + rawLines[i]
- slideSize += 1
- else:
- # Continue a slide
- lines[-1] = lines[-1] + "\n" + rawLines[i]
- slideSize += 1
- return lines
-
-
-
-# Create powerpoint
-def CreatePptx(parsedLyrics, filepath, openFirst, settings):
- 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(settings["slide-color"][0], settings["slide-color"][1], settings["slide-color"][2])
-
- # 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(settings["font-color"][0], settings["font-color"][1], settings["font-color"][2])
- p.alignment = PP_ALIGN.CENTER
- p.line_spacing = settings["line-spacing"]
-
- # Save powerpoint
- prs.save(filepath)
-
-
-
-# Run CLI
-if (__name__ == "__main__"):
- # Load settings
- try:
- with open(os.path.join(os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))), "settings.json")) as f:
- settings = json.load(f)
- except:
- print("There was an error while loading the settings.")
- input("Press enter to exit...")
- sys.exit()
-
- # 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:
- parsedLyrics = ParseLyrics(GetLyrics(artist, title), settings)
- if (settings["title-slides"]):
- lyrics += ["{0}\n{1}".format(title, artist)]
- lyrics += parsedLyrics
- if (lyrics[-1] != ""):
- lyrics += [""]
- 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)
- for line in lyrics:
- temp.writelines(line)
- temp.writelines("\n\n")
- temp.close()
-
- # Open temp file and wait
- subprocess.Popen(["notepad", temp.name]).wait()
-
- # Read file
- with open(temp.name) as f:
- rawLines = f.read()
-
- # Parse lyrics
- newLyrics = rawLines.split("\n\n")
- del newLyrics[-1]
- lyrics = newLyrics
- 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:
- CreatePptx(lyrics, filepath, openFirst, settings)
- 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:
- os.startfile(filepath)
- except:
- print("There was an error while opening the powerpoint.")
- input("Press enter to exit...")
- sys.exit()
-\ No newline at end of file
diff --git a/Songs2Slides/__init__.py b/Songs2Slides/__init__.py
@@ -0,0 +1,6 @@
+# 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
@@ -0,0 +1,31 @@
+parsing = {
+ "title-slides": True,
+ "lines-per-slide": 4,
+}
+
+slide = {
+ "width": 13.333,
+ "height": 7.5,
+ "color": [0, 0, 0],
+}
+
+margin = {
+ "left": 0.5,
+ "right": 0.5,
+ "top": 0.5,
+ "bottom": 0.5,
+}
+
+font = {
+ "family": "Calibri",
+ "size": 40,
+ "bold": False,
+ "italic": False,
+ "color": [255, 255, 255],
+}
+
+paragraph = {
+ "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
@@ -0,0 +1,142 @@
+# 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
+from pptx.util import Inches, Pt
+import requests
+
+
+
+# Gets the lyrics
+def GetLyrics(artist, song):
+ # Convert to lowercase
+ artist = artist.lower()
+ song = song.lower()
+
+ # Replace invalid characters
+ old = [" ", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "'", "?", "/", "|", "\\", ".", ",", "á", "é", "í", "ó", "ñ", "ú"]
+ new = ["-", "", "", "", "s", "", "-", "-and-", "", "", "", "-", "-", "", "", "", "", "", "", "", "a", "e", "i", "o", "n", "u"]
+ for i in range(0, len(old)):
+ artist = artist.replace(old[i], new[i])
+ song = song.replace(old[i], new[i])
+
+ # Remove unnecessary dashes
+ artist = "-".join(list(filter(lambda a: a != "", artist.split("-"))))
+ song = "-".join(list(filter(lambda a: a != "", song.split("-"))))
+
+ # Get lyrics
+ page = requests.get("https://genius.com/{0}-{1}-lyrics".format(artist, song))
+ lyrics = BeautifulSoup(page.text, "html.parser").find("div", class_="lyrics").get_text()
+
+ # Return lyrics
+ return lyrics
+
+
+
+# Parses the lyrics into blocks
+def ParseLyrics(lyrics):
+ # Split lyrics
+ rawLines = lyrics.split("\n")
+
+ # Remove starting and ending newlines
+ del rawLines[0]
+ del rawLines[0]
+ del rawLines[-1]
+ del rawLines[-1]
+
+ # Parse lyrics into lines
+ lines = []
+ slideSize = config.parsing["lines-per-slide"]
+ for i in range(0, len(rawLines)):
+ if (rawLines[i] == ""):
+ # Start a new slide without content
+ lines.append("")
+ slideSize = 0
+ elif (rawLines[i][0] == "["):
+ # Ignore
+ pass
+ elif (slideSize == config.parsing["lines-per-slide"]):
+ # Start a new slide with content
+ lines.append(rawLines[i])
+ slideSize = 1
+ elif (slideSize == 0):
+ # Continue a blank slide
+ lines[-1] = lines[-1] + rawLines[i]
+ slideSize += 1
+ else:
+ # Continue a slide
+ lines[-1] = lines[-1] + "\n" + rawLines[i]
+ slideSize += 1
+ return lines
+
+
+
+# Create powerpoint
+def CreatePptx(parsedLyrics, filepath, openFirst):
+ if (openFirst):
+ try:
+ # Open presentation
+ prs = Presentation(filepath)
+ except:
+ # Create presentation
+ prs = Presentation()
+
+ # Set slide width and height
+ prs.slide_width = Inches(config.slide["width"])
+ prs.slide_height = Inches(config.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"])
+
+ # 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"])
+
+ 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(config.slide["color"][0], config.slide["color"][1], config.slide["color"][2])
+
+ # Add text box
+ txBox = slide.shapes.add_textbox(left, top, width, height)
+ tf = txBox.text_frame
+ tf.clear()
+
+ # Apply text formating
+ tf.word_wrap = config.paragraph["word-wrap"]
+ if (config.paragraph["vertical-alignment"].lower() == "top"):
+ tf.vertical_anchor = MSO_ANCHOR.TOP
+ elif (config.paragraph["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 = 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.alignment = PP_ALIGN.CENTER
+ p.line_spacing = config.paragraph["line-spacing"]
+
+ # Save powerpoint
+ prs.save(filepath)
+\ No newline at end of file
diff --git a/Songs2Slides/routes.py b/Songs2Slides/routes.py
@@ -0,0 +1,51 @@
+# Import dependencies
+from flask import render_template, request, send_file, url_for
+import io
+import json
+import os
+from Songs2Slides import app, models, config
+import tempfile
+
+
+
+# Home page
+@app.route("/", methods=["GET"])
+def index():
+ return render_template("index.html")
+
+
+
+# Powerpoint download
+@app.route("/pptx", methods=["POST"])
+def pptx():
+ # Parse POST parameters
+ data = json.loads(request.form["data"])
+
+ # Get lyrics
+ lyrics = []
+ for song in data:
+ try:
+ parsedLyrics = models.ParseLyrics(models.GetLyrics(song[1], song[0]))
+ if (config.parsing["title-slides"]):
+ lyrics += ["{0}\n{1}".format(song[0], song[1])]
+ lyrics += parsedLyrics
+ if (lyrics[-1] != ""):
+ lyrics += [""]
+ except:
+ pass
+
+ try:
+ # Create powerpoint
+ temp = tempfile.NamedTemporaryFile(mode="w+t", suffix=".pptx", delete=False)
+ models.CreatePptx(lyrics, temp.name, False)
+ temp.close()
+
+ # 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')
diff --git a/Songs2Slides/static/index.css b/Songs2Slides/static/index.css
@@ -0,0 +1,18 @@
+body {
+ text-align: center;
+ font-family: Arial, Helvetica, sans-serif;
+ touch-action: manipulation;
+}
+
+button {
+ height: 25px;
+}
+
+#title {
+ font-size: 25px;
+ font-weight: bold;
+}
+
+.songRemove {
+ cursor: pointer;
+}
+\ No newline at end of file
diff --git a/Songs2Slides/static/index.js b/Songs2Slides/static/index.js
@@ -0,0 +1,46 @@
+// Declare global variables
+setId = 0; // Next valid song id number
+
+
+
+// Adds a song
+function Add() {
+ // Create row
+ var 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", `var element = document.getElementById('song-${setId}'); element.parentNode.removeChild(element);`);
+
+ // Add row
+ document.getElementById("songs").appendChild(clone);
+
+ // Increment setId
+ setId++;
+}
+
+
+
+// Prepares form data for a POST request
+function PrepareForm() {
+ // Get song info
+ var titles = [];
+ for (title of document.getElementsByClassName("title")) {
+ titles.push(title.value);
+ }
+ var artists = [];
+ for (artist of document.getElementsByClassName("artist")) {
+ artists.push(artist.value);
+ }
+
+ // Prepare data
+ data = []
+ for (var i = 0; i < titles.length; i++) {
+ data.push([titles[i], artists[i]])
+ }
+
+ // Set data
+ document.getElementsByName("data")[0].value = JSON.stringify(data)
+}
+\ No newline at end of file
diff --git a/Songs2Slides/templates/index.html b/Songs2Slides/templates/index.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Songs2Slides</title>
+ <meta name="viewport" content="width=device-width, user-scalable=no"/>
+ <link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}"></link>
+ <script src="{{ url_for('static', filename='index.js') }}"></script>
+ </head>
+ <body onload="Add();">
+ <label id="title">Songs2Slides</label>
+ <br/>
+ <label>Create a lyrics powerpoints from a list of songs.</label>
+ <br/><br/>
+
+ <form action="/pptx" method="POST" onsubmit="PrepareForm();">
+ <input hidden name="data">
+ <button type="button" onclick="Add();">Add song</button>
+ <button type="submit">Create Powerpoint</button>
+ </form>
+
+
+ <template id="songTemplate">
+ <div>
+ <input type="text" class="title" placeholder="Song Title">
+ <input type="text" class="artist" placeholder="Song Artist">
+ <a id="remove" class="songRemove">╳</a>
+ </div>
+ </template>
+
+ <br/>
+ <div id="songs"></div>
+ </body>
+</html>
+\ No newline at end of file
diff --git a/cliapp.py b/cliapp.py
@@ -0,0 +1,104 @@
+# Import dependencies
+import json
+import os
+from Songs2Slides import models, config
+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:
+ parsedLyrics = models.ParseLyrics(models.GetLyrics(artist, title))
+ if (config.parsing["title-slides"]):
+ lyrics += ["{0}\n{1}".format(title, artist)]
+ lyrics += parsedLyrics
+ if (lyrics[-1] != ""):
+ lyrics += [""]
+ 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)
+ for line in lyrics:
+ temp.writelines(line)
+ temp.writelines("\n\n")
+ temp.close()
+
+ # Open temp file and wait
+ subprocess.Popen(["notepad", temp.name]).wait()
+
+ # Read file
+ with open(temp.name) as f:
+ rawLines = f.read()
+
+ # Parse lyrics
+ newLyrics = rawLines.split("\n\n")
+ del newLyrics[-1]
+ lyrics = newLyrics
+ 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:
+ models.CreatePptx(lyrics, filepath, 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:
+ os.startfile(filepath)
+ except:
+ print("There was an error while opening the powerpoint.")
+ input("Press enter to exit...")
+ sys.exit()
+\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
@@ -1,3 +1,4 @@
bs4
python-pptx
-requests
-\ No newline at end of file
+requests
+flask
+\ No newline at end of file
diff --git a/settings.json b/settings.json
@@ -1,23 +0,0 @@
-{
- "title-slides": true,
- "lines-per-slide": 4,
-
- "slide-width": 13.333,
- "slide-height": 7.5,
- "slide-color": [0, 0, 0],
-
- "margin-left": 0.5,
- "margin-right": 0.5,
- "margin-top": 0.5,
- "margin-bottom": 0.5,
-
- "font-family": "Calibri",
- "font-size": 40,
- "font-bold": false,
- "font-italic": false,
- "font-color": [255, 255, 255],
-
- "vertical-alignment": "middle",
- "line-spacing": 1.25,
- "word-wrap": true
-}
-\ No newline at end of file
diff --git a/webapp.py b/webapp.py
@@ -0,0 +1,6 @@
+# Import app
+from Songs2Slides import app
+
+# Run app
+if (__name__ == "__main__"):
+ app.run(debug=True)
+\ No newline at end of file