Update Exploring Near Earth Objects project and add Meme Generator project
This commit is contained in:
35
Meme_Generator/MemeEngine/MemeEngine.py
Normal file
35
Meme_Generator/MemeEngine/MemeEngine.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Meme engine module for handling the generation of meme images."""
|
||||
|
||||
import random
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
|
||||
|
||||
class MemeEngine:
|
||||
"""Class to generate memes from images and text."""
|
||||
|
||||
def __init__(self, output_path):
|
||||
"""Initialize meme engine with path to save generated memes."""
|
||||
self.output_path = output_path
|
||||
|
||||
def make_meme(self, img_path, text, author, width=500) -> str:
|
||||
"""Generate a meme image with given text and author."""
|
||||
output_file = f"{self.output_path}/{random.randint(0,10000)}.jpg"
|
||||
message = f"{text}\n- {author}"
|
||||
|
||||
try:
|
||||
with Image.open(img_path) as img:
|
||||
# Resize image while maintaining aspect ratio
|
||||
width = 500 if img.size[0] > 500 else img.size[0]
|
||||
ratio = width / (img.size[0] * 1.0)
|
||||
height = ratio * img.size[1]
|
||||
img = img.resize((int(width), int(height)), Image.NEAREST)
|
||||
draw = ImageDraw.Draw(img)
|
||||
font = ImageFont.truetype(
|
||||
"./_data/font/calibri_regular.ttf", int(height / 20)
|
||||
)
|
||||
draw.text((20, 20), message, font=font, fill="white")
|
||||
img.save(output_file)
|
||||
except Exception as err:
|
||||
print(f"Error: {err}")
|
||||
|
||||
return output_file
|
||||
1
Meme_Generator/MemeEngine/__init__.py
Normal file
1
Meme_Generator/MemeEngine/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .MemeEngine import MemeEngine
|
||||
24
Meme_Generator/QuoteEngine/CSVIngestor.py
Normal file
24
Meme_Generator/QuoteEngine/CSVIngestor.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Module for ingesting CSV files containing quotes."""
|
||||
|
||||
from typing import List
|
||||
from .IngestorInterface import IngestorInterface
|
||||
from .QuoteModel import QuoteModel
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class CSVIngestor(IngestorInterface):
|
||||
"""Subclass for ingesting CSV files."""
|
||||
|
||||
allowed_extensions = ["csv"]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, path: str) -> List[QuoteModel]:
|
||||
"""Parse the CSV file to extract quotes."""
|
||||
if not cls.can_ingest(path):
|
||||
raise Exception("Invalid ingest path")
|
||||
quotes = []
|
||||
df = pd.read_csv(path, header=0, sep=",", names=["body", "author"])
|
||||
for _, row in df.iterrows():
|
||||
quotes.append(QuoteModel(row["body"], row["author"]))
|
||||
return quotes
|
||||
25
Meme_Generator/QuoteEngine/DocxIngestor.py
Normal file
25
Meme_Generator/QuoteEngine/DocxIngestor.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Module for ingesting Docx files containing quotes."""
|
||||
|
||||
import docx
|
||||
from typing import List
|
||||
from .IngestorInterface import IngestorInterface
|
||||
from .QuoteModel import QuoteModel
|
||||
|
||||
|
||||
class DocxIngestor(IngestorInterface):
|
||||
"""Subclass for ingesting Docx files."""
|
||||
|
||||
allowed_extensions = ["docx"]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, path: str) -> List[QuoteModel]:
|
||||
"""Parse the Docx file to extract quotes."""
|
||||
if not cls.can_ingest(path):
|
||||
raise Exception("Invalid ingest path")
|
||||
quotes = []
|
||||
doc = docx.Document(path)
|
||||
for para in doc.paragraphs:
|
||||
if para.text != "":
|
||||
parts = para.text.split(" - ")
|
||||
quotes.append(QuoteModel(parts[0], parts[1]))
|
||||
return quotes
|
||||
22
Meme_Generator/QuoteEngine/Ingestor.py
Normal file
22
Meme_Generator/QuoteEngine/Ingestor.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Ingestor module to select appropriate ingestor based on file type."""
|
||||
|
||||
from typing import List
|
||||
from .IngestorInterface import IngestorInterface
|
||||
from .QuoteModel import QuoteModel
|
||||
from .CSVIngestor import CSVIngestor
|
||||
from .TextIngestor import TextIngestor
|
||||
from .DocxIngestor import DocxIngestor
|
||||
from .PDFIngestor import PDFIngestor
|
||||
|
||||
|
||||
class Ingestor(IngestorInterface):
|
||||
"""Subclass to select appropriate ingestor."""
|
||||
|
||||
ingestors = [CSVIngestor, TextIngestor, DocxIngestor, PDFIngestor]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, path: str) -> List[QuoteModel]:
|
||||
"""Select the appropriate ingestor to parse the file."""
|
||||
for ingestor in cls.ingestors:
|
||||
if ingestor.can_ingest(path):
|
||||
return ingestor.parse(path)
|
||||
23
Meme_Generator/QuoteEngine/IngestorInterface.py
Normal file
23
Meme_Generator/QuoteEngine/IngestorInterface.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Ingestor Interface module for quote ingestion."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
from .QuoteModel import QuoteModel
|
||||
|
||||
|
||||
class IngestorInterface(ABC):
|
||||
"""Base Ingestor Interface."""
|
||||
|
||||
allowed_extensions = []
|
||||
|
||||
@classmethod
|
||||
def can_ingest(cls, path: str) -> bool:
|
||||
"""Check if the ingestor can ingest the file based on its extension."""
|
||||
ext = path.split(".")[-1]
|
||||
return ext in cls.allowed_extensions
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def parse(cls, path: str) -> List[QuoteModel]:
|
||||
"""Abstract method to parse the file and return a list of QuoteModel objects."""
|
||||
pass
|
||||
40
Meme_Generator/QuoteEngine/PDFIngestor.py
Normal file
40
Meme_Generator/QuoteEngine/PDFIngestor.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""Module for ingesting PDF files containing quotes."""
|
||||
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
from typing import List
|
||||
from .IngestorInterface import IngestorInterface
|
||||
from .QuoteModel import QuoteModel
|
||||
|
||||
|
||||
class PDFIngestor(IngestorInterface):
|
||||
"""Subclass for ingesting PDF files."""
|
||||
|
||||
allowed_extensions = ["pdf"]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, path: str) -> List[QuoteModel]:
|
||||
"""Parse the PDF file to extract quotes."""
|
||||
if not cls.can_ingest(path):
|
||||
raise Exception("Invalid ingest path")
|
||||
|
||||
quotes = []
|
||||
tmp = f"./tmp/{random.randint(0, 10000)}.txt"
|
||||
try:
|
||||
# pdftotext <input-pdf> <output-text-file>
|
||||
call = subprocess.call(["pdftotext", path, tmp])
|
||||
with open(tmp, "r") as file:
|
||||
lines = file.readlines()
|
||||
except FileNotFoundError as err:
|
||||
print(f"Error: {err}")
|
||||
else:
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line:
|
||||
parts = line.split(" - ")
|
||||
quotes.append(QuoteModel(parts[0], parts[1]))
|
||||
finally:
|
||||
if os.path.exists(tmp):
|
||||
os.remove(tmp)
|
||||
return quotes
|
||||
14
Meme_Generator/QuoteEngine/QuoteModel.py
Normal file
14
Meme_Generator/QuoteEngine/QuoteModel.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""QuoteModel module for representing a quote with its body and author."""
|
||||
|
||||
|
||||
class QuoteModel:
|
||||
"""Quote model class."""
|
||||
|
||||
def __init__(self, body, author):
|
||||
"""Initialize the QuoteModel object."""
|
||||
self.body = body
|
||||
self.author = author
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of the QuoteModel object."""
|
||||
return f"{self.body} - {self.author}"
|
||||
26
Meme_Generator/QuoteEngine/TextIngestor.py
Normal file
26
Meme_Generator/QuoteEngine/TextIngestor.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""Module for ingesting text files containing quotes."""
|
||||
|
||||
from typing import List
|
||||
from .IngestorInterface import IngestorInterface
|
||||
from .QuoteModel import QuoteModel
|
||||
|
||||
|
||||
class TextIngestor(IngestorInterface):
|
||||
"""Subcalss for ingesting text files."""
|
||||
|
||||
allowed_extensions = ["txt"]
|
||||
|
||||
@classmethod
|
||||
def parse(cls, path: str) -> List[QuoteModel]:
|
||||
"""Parse the text file to extract quotes."""
|
||||
if not cls.can_ingest(path):
|
||||
raise Exception("Invalid ingest path")
|
||||
quotes = []
|
||||
with open(path, "r") as file:
|
||||
for line in file.readlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
parts = line.split(" - ", 1)
|
||||
if len(parts) == 2:
|
||||
quotes.append(QuoteModel(parts[0], parts[1]))
|
||||
return quotes
|
||||
7
Meme_Generator/QuoteEngine/__init__.py
Normal file
7
Meme_Generator/QuoteEngine/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .IngestorInterface import IngestorInterface
|
||||
from .CSVIngestor import CSVIngestor
|
||||
from .DocxIngestor import DocxIngestor
|
||||
from .PDFIngestor import PDFIngestor
|
||||
from .TextIngestor import TextIngestor
|
||||
from .Ingestor import Ingestor
|
||||
from .QuoteModel import QuoteModel
|
||||
39
Meme_Generator/README.md
Normal file
39
Meme_Generator/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## Meme Generator Project
|
||||
|
||||
### Overview
|
||||
The goal of this project is to build a "meme generator" – a multimedia application to dynamically generate memes, including an image with an overlaid quote.
|
||||
|
||||
|
||||
### Set up environment
|
||||
```sh
|
||||
$ sudo apt-get install -y xpdf
|
||||
$ pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Run program
|
||||
|
||||
Python test \
|
||||
Usage: `python3 meme.py --path [image_path] --body [quote_body] --author [quote_author]`
|
||||
```sh
|
||||
$ mkdir tmp
|
||||
$ python3 meme.py
|
||||
```
|
||||
|
||||
Flask test
|
||||
```sh
|
||||
$ mkdir static
|
||||
$ python3 app.py
|
||||
```
|
||||
Open link `http://127.0.0.1:5000` in web browser
|
||||
|
||||
### Primary modules
|
||||
|
||||
`QuoteEngine`: Meme engine module for handling the generation of meme images.\
|
||||
`MemeEngine`: QuoteModel module for representing a quote with its body and author.
|
||||
|
||||
### Sub modules
|
||||
|
||||
`CSVIngestor`: Module for ingesting CSV files containing quotes. \
|
||||
`DocxIngestor`: Module for ingesting Docx files containing quotes. \
|
||||
`TextIngestor`: Module for ingesting text files containing quotes. \
|
||||
`PDFIngestor`: Module for ingesting PDF files containing quotes.
|
||||
3
Meme_Generator/_data/DogQuotes/DogQuotesCSV.csv
Normal file
3
Meme_Generator/_data/DogQuotes/DogQuotesCSV.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
body,author
|
||||
Chase the mailman,Skittle
|
||||
"When in doubt, go shoe-shopping",Mr. Paws
|
||||
|
BIN
Meme_Generator/_data/DogQuotes/DogQuotesDOCX.docx
Normal file
BIN
Meme_Generator/_data/DogQuotes/DogQuotesDOCX.docx
Normal file
Binary file not shown.
BIN
Meme_Generator/_data/DogQuotes/DogQuotesPDF.pdf
Normal file
BIN
Meme_Generator/_data/DogQuotes/DogQuotesPDF.pdf
Normal file
Binary file not shown.
2
Meme_Generator/_data/DogQuotes/DogQuotesTXT.txt
Normal file
2
Meme_Generator/_data/DogQuotes/DogQuotesTXT.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
To bork or not to bork - Bork
|
||||
He who smelt it... - Stinky
|
||||
6
Meme_Generator/_data/SimpleLines/SimpleLines.csv
Normal file
6
Meme_Generator/_data/SimpleLines/SimpleLines.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
body,author
|
||||
Line 1,Author 1
|
||||
Line 2,Author 2
|
||||
Line 3,Author 3
|
||||
Line 4,Author 4
|
||||
Line 5,Author 5
|
||||
|
BIN
Meme_Generator/_data/SimpleLines/SimpleLines.docx
Normal file
BIN
Meme_Generator/_data/SimpleLines/SimpleLines.docx
Normal file
Binary file not shown.
BIN
Meme_Generator/_data/SimpleLines/SimpleLines.pdf
Normal file
BIN
Meme_Generator/_data/SimpleLines/SimpleLines.pdf
Normal file
Binary file not shown.
7
Meme_Generator/_data/SimpleLines/SimpleLines.txt
Normal file
7
Meme_Generator/_data/SimpleLines/SimpleLines.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
"Line 1" - Author 1
|
||||
"Line 2" - Author 2
|
||||
"Line 3" - Author 3
|
||||
"Line 4" - Author 4
|
||||
"Line 5" - Author 5
|
||||
|
||||
|
||||
BIN
Meme_Generator/_data/font/calibri_regular.ttf
Normal file
BIN
Meme_Generator/_data/font/calibri_regular.ttf
Normal file
Binary file not shown.
BIN
Meme_Generator/_data/photos/dog/xander_1.jpg
Normal file
BIN
Meme_Generator/_data/photos/dog/xander_1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 MiB |
BIN
Meme_Generator/_data/photos/dog/xander_2.jpg
Normal file
BIN
Meme_Generator/_data/photos/dog/xander_2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
Meme_Generator/_data/photos/dog/xander_3.jpg
Normal file
BIN
Meme_Generator/_data/photos/dog/xander_3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
Meme_Generator/_data/photos/dog/xander_4.jpg
Normal file
BIN
Meme_Generator/_data/photos/dog/xander_4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
112
Meme_Generator/app.py
Normal file
112
Meme_Generator/app.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import random
|
||||
import os
|
||||
import requests
|
||||
from flask import Flask, render_template, abort, request
|
||||
|
||||
from QuoteEngine import Ingestor
|
||||
from MemeEngine import MemeEngine
|
||||
|
||||
# Create the Flask application object. "__name__" tells Flask where to find templates and static files.
|
||||
app = Flask(__name__)
|
||||
|
||||
# Create a global MemeEngine instance that will write generated memes into "./static"
|
||||
# so that the images can be served by the web server.
|
||||
meme = MemeEngine("./static")
|
||||
|
||||
|
||||
def setup():
|
||||
"""Load all resources"""
|
||||
|
||||
quote_files = [
|
||||
"./_data/DogQuotes/DogQuotesTXT.txt",
|
||||
"./_data/DogQuotes/DogQuotesDOCX.docx",
|
||||
"./_data/DogQuotes/DogQuotesPDF.pdf",
|
||||
"./_data/DogQuotes/DogQuotesCSV.csv",
|
||||
]
|
||||
|
||||
quotes = []
|
||||
for file in quote_files:
|
||||
if Ingestor.parse(file) is not None:
|
||||
quotes.append(Ingestor.parse(file))
|
||||
|
||||
images_path = "./_data/photos/dog/"
|
||||
|
||||
imgs = []
|
||||
for root, _, files in os.walk(images_path):
|
||||
imgs = [os.path.join(root, name) for name in files]
|
||||
|
||||
return quotes, imgs
|
||||
|
||||
|
||||
# Call setup() once at import time to preload quotes and images.
|
||||
# These will be reused on every request.
|
||||
quotes, imgs = setup()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def meme_rand():
|
||||
"""Generate a random meme
|
||||
|
||||
Steps:
|
||||
- Pick a random image from imgs
|
||||
- Pick a random "quote list" from quotes
|
||||
- Pick a random quote from that list
|
||||
- Generate a meme image with that quote and image
|
||||
- Render a template to show the meme
|
||||
"""
|
||||
img = random.choice(imgs)
|
||||
quote_list = random.choice(quotes)
|
||||
quote = random.choice(quote_list)
|
||||
path = meme.make_meme(img, quote.body, quote.author)
|
||||
return render_template("meme.html", path=path)
|
||||
|
||||
|
||||
@app.route("/create", methods=["GET"])
|
||||
def meme_form():
|
||||
"""User input for meme information
|
||||
|
||||
This route renders a form where the user can input:
|
||||
- image_url: URL of the source image
|
||||
- body: quote text
|
||||
- author: quote author
|
||||
"""
|
||||
return render_template("meme_form.html")
|
||||
|
||||
|
||||
@app.route("/create", methods=["POST"])
|
||||
def meme_post():
|
||||
"""Create a user defined meme
|
||||
|
||||
This route:
|
||||
- Reads form data sent via POST from the meme_form page
|
||||
- Downloads the image from the provided URL
|
||||
- Saves it to a temporary file
|
||||
- Passes that file to MemeEngine to generate a meme
|
||||
- Deletes the temporary file
|
||||
- Renders the final meme
|
||||
"""
|
||||
|
||||
image_url = request.form.get("image_url", "").strip()
|
||||
body = request.form.get("body", "").strip()
|
||||
author = request.form.get("author", "").strip()
|
||||
image = requests.get(image_url, timeout=5)
|
||||
|
||||
try:
|
||||
tmp_file = f"tmp/{random.randint(0, 10000)}.jpg"
|
||||
with open(tmp_file, "wb") as file:
|
||||
file.write(image.content)
|
||||
except:
|
||||
print("Failed to generate meme")
|
||||
path = None
|
||||
if os.path.exists(tmp_file):
|
||||
os.remove(tmp_file)
|
||||
else:
|
||||
path = meme.make_meme(tmp_file, body, author)
|
||||
if os.path.exists(tmp_file):
|
||||
os.remove(tmp_file)
|
||||
finally:
|
||||
return render_template("meme.html", path=path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
54
Meme_Generator/meme.py
Normal file
54
Meme_Generator/meme.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import os
|
||||
import random
|
||||
import argparse
|
||||
from MemeEngine import MemeEngine
|
||||
from QuoteEngine import QuoteModel, Ingestor
|
||||
|
||||
|
||||
def generate_meme(path=None, body=None, author=None):
|
||||
"""Generate a meme given an path and a quote"""
|
||||
img = None
|
||||
quote = None
|
||||
|
||||
if path is None:
|
||||
images = "./_data/photos/dog/"
|
||||
imgs = []
|
||||
for root, dirs, files in os.walk(images):
|
||||
imgs = [os.path.join(root, name) for name in files]
|
||||
|
||||
img = random.choice(imgs)
|
||||
else:
|
||||
img = path[0]
|
||||
|
||||
if body is None:
|
||||
quote_files = [
|
||||
"./_data/DogQuotes/DogQuotesTXT.txt",
|
||||
"./_data/DogQuotes/DogQuotesDOCX.docx",
|
||||
"./_data/DogQuotes/DogQuotesPDF.pdf",
|
||||
"./_data/DogQuotes/DogQuotesCSV.csv",
|
||||
]
|
||||
quotes = []
|
||||
for f in quote_files:
|
||||
quotes.extend(Ingestor.parse(f))
|
||||
|
||||
quote = random.choice(quotes)
|
||||
else:
|
||||
if author is None:
|
||||
raise Exception("Author Required if Body is Used")
|
||||
quote = QuoteModel(body, author)
|
||||
|
||||
meme = MemeEngine("./tmp")
|
||||
|
||||
path = meme.make_meme(img, quote.body, quote.author)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Add path, quote, and author arguments for CLI, then print meme generation."""
|
||||
parser = argparse.ArgumentParser(description="Meme Generator")
|
||||
parser.add_argument("--path", type=str, help="Image path")
|
||||
parser.add_argument("--body", type=str, help="Quote adding to meme")
|
||||
parser.add_argument("--author", type=str, help="Author adding to meme")
|
||||
args = parser.parse_args()
|
||||
print(generate_meme(args.path, args.body, args.author))
|
||||
29
Meme_Generator/requirements.txt
Normal file
29
Meme_Generator/requirements.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
black==25.12.0
|
||||
blinker==1.9.0
|
||||
certifi==2025.11.12
|
||||
charset-normalizer==3.4.4
|
||||
click==8.3.1
|
||||
Flask==3.1.2
|
||||
idna==3.11
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
lxml==6.0.2
|
||||
MarkupSafe==3.0.3
|
||||
mypy_extensions==1.1.0
|
||||
numpy==2.2.6
|
||||
packaging==25.0
|
||||
pandas==2.3.3
|
||||
pathspec==0.12.1
|
||||
pillow==12.1.0
|
||||
platformdirs==4.5.1
|
||||
python-dateutil==2.9.0.post0
|
||||
python-docx==1.2.0
|
||||
pytokens==0.3.0
|
||||
pytz==2025.2
|
||||
requests==2.32.5
|
||||
six==1.17.0
|
||||
tomli==2.3.0
|
||||
typing_extensions==4.15.0
|
||||
tzdata==2025.3
|
||||
urllib3==2.6.2
|
||||
Werkzeug==3.1.4
|
||||
37
Meme_Generator/templates/base.html
Normal file
37
Meme_Generator/templates/base.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background: #F5FCFE;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: inline;
|
||||
padding: 60px;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 1px solid rgba(0,0,0,.125);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
<div class="nav">
|
||||
<a class="btn btn-primary" href="{{url_for('meme_rand')}}">Random</a>
|
||||
<a class="btn btn-outline-primary" href="{{url_for('meme_form')}}">Creator</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
5
Meme_Generator/templates/meme.html
Normal file
5
Meme_Generator/templates/meme.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Meme Generator{% endblock %}
|
||||
{% block body %}
|
||||
<img src="{{ path }}" />
|
||||
{% endblock %}
|
||||
23
Meme_Generator/templates/meme_form.html
Normal file
23
Meme_Generator/templates/meme_form.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Meme Generator{% endblock %}
|
||||
{% block body %}
|
||||
<div class="card" style="width: 500px; max-width: 100%;">
|
||||
<div class="card-body">
|
||||
<form action="{{url_for('meme_post')}}" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="image_url">Image URL</label>
|
||||
<input type="url" class="form-control" id="image_url" aria-describedby="image url" placeholder="Enter a url for an image" name="image_url">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="body">Quote Body</label>
|
||||
<input type="text" class="form-control" id="body" aria-describedby="Quote Body" placeholder="To be or not to be" name="body">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="author">Quote Author</label>
|
||||
<input type="text" class="form-control" id="author" aria-describedby="Quote Author" placeholder="Shakespeare" name="author">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create Meme!</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user