Welcome to part 4. This is where we put it all together and finally create our fully-fledged meme generator. We’ll be creating the frontend for our project using Streamlit, but before we can do so, we need to make some final updates to our get_meme.py code.
So open back up get_meme.py one last time and let’s start with the imports:
import json import os from pathlib import Path # new import from typing import TypedDict from dotenv import load_dotenv from openai import OpenAI from load_meme_data import MemeData, load_meme_data, load_meme_data_flat_string # updated import from meme_image_editor import overlay_text_on_image # new import from system_instructions import get_system_instructions
We’ve added an import for Path from pathlib, which we’ve used several times before. We’ve added the MemeData datatype and load_meme_data imports from load_meme_data.py, for both the datatype and loading the meme data as an object instead of a flat string. Last but not least, we’ve added an import for our overlay_text_on_image function we worked so hard on in the last part.
I’ll provide this for clarity, there are zero changes in the following code:
## No changes here ##
class MemeGPTOutput(TypedDict):
meme_id: int
meme_name: str
meme_text: list[str]
load_dotenv()
CLIENT = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
MEME_DATA_TEXT = load_meme_data_flat_string()
SYSTEM_MESSAGE = {
"role": "system",
"content": [
{
"type": "text",
"text": get_system_instructions(MEME_DATA_TEXT),
}
],
}
def call_chatgpt(user_message):
response = CLIENT.chat.completions.create(
model="gpt-4o",
messages=[SYSTEM_MESSAGE, user_message], # type: ignore
temperature=1,
max_tokens=2048,
response_format={"type": "json_object"},
)
return response.choices[0].message.content
So that all stays the same. The only function we’re going to be updating is the next one, generate_memes. Update the function like this:
def generate_memes(user_input: str) -> list[str] | None:
user_message = {
"role": "user",
"content": [
{
"type": "text",
"text": user_input,
}
],
}
response = call_chatgpt(user_message)
print(response) # Added a print statement for the terminal
if not response:
print("No response from the model, something went wrong.")
return
try:
meme_output: list[MemeGPTOutput] = json.loads(response)['output']
except json.JSONDecodeError:
print("Invalid response from the model.")
return
## New code starts here ##
meme_data: list[MemeData] = load_meme_data()
images = []
for ai_meme in meme_output:
chosen_meme_index = int(ai_meme["meme_id"])
print(f"MemeGPT chose the meme: {meme_data[chosen_meme_index]['name']}")
print(f"Meme text: {ai_meme['meme_text']}")
image_path: Path = overlay_text_on_image(meme_data[chosen_meme_index], ai_meme["meme_text"])
images.append(str(image_path))
return images
As you can see I’ve added a print statement to print the response from ChatGPT to the terminal. This is useful for debugging purposes. Then we added a whole new block of code at the end. The first thing we do is load the meme data as a list of MemeData objects using our load_meme_data() function, saving it to the variable meme_data.
We then create an empty list called images to store the paths of the images we generate. We loop through each ai_meme in the meme_output list, first getting the index of the meme chosen by ChatGPT. We then print the name of the meme and the text generated by ChatGPT to the terminal, as this could be helpful to see in real-time for debugging purposes.
Now we simply call our overlay_text_on_image function, passing in the chosen_meme_index of the meme_data list and the ai_meme‘s meme_text. This function will return the path of the image it creates, which we then append to the images list. Note that we convert the Path object to a string using str() before appending it to the list, as I happen to know that Streamlit wants paths in this string format.
Finally, we return the images list. This function will now return a list of paths to the 3 meme images, which we can then use to display them in our Streamlit frontend.
Testing the code
You can test-run this file. The code in the if __name__ == "__main__": block requires no changes for this:
## No changes here ##
if __name__ == "__main__":
print("Welcome to the meme generator!")
print("You can provide a situation or a topic to generate a meme.")
generate_memes(user_input=input("Please provide a topic or situation: "))
So try it out and give it a topic. Here is my output:
Welcome to the meme generator!
You can provide a situation or a topic to generate a meme.
Please provide a topic or situation: I am using AI to generate unlimited memes.
{
"output": [
{
"meme_id": 10,
"meme_name": "Roll Safe Think About It",
"meme_text": [
"Never run out of memes",
"If you use AI to generate them"
]
},
{
"meme_id": 5,
"meme_name": "Expanding Brain",
"meme_text": [
"Browsing meme websites",
"Creating memes manually",
"Using meme generators",
"Using AI to generate unlimited memes"
]
},
{
"meme_id": 2,
"meme_name": "Left Exit 12 Off Ramp",
"meme_text": [
"Searching for trending memes",
"Me",
"Using AI to make memes"
]
}
]
}
MemeGPT chose the meme: Roll Safe Think About It
Meme text: ['Never run out of memes', 'If you use AI to generate them']
Image saved to C:\Coding_Vault\Meme_Gen_Draft\output\23e36ce1-b791-42c1-a852-67cac4f62f42.png
MemeGPT chose the meme: Expanding Brain
Meme text: ['Browsing meme websites', 'Creating memes manually', 'Using meme generators', 'Using AI to generate unlimited memes']
Image saved to C:\Coding_Vault\Meme_Gen_Draft\output\69c20322-80f7-4184-ae5d-18771b34de00.png
MemeGPT chose the meme: Left Exit 12 Off Ramp
Meme text: ['Searching for trending memes', 'Me', 'Using AI to make memes']
Image saved to C:\Coding_Vault\Meme_Gen_Draft\output\21da4834-ceed-493d-801c-cef7b7fb69a7.png
And here are my three images:



Alright! All looks good and ready to go.
Streamlit frontend
Now let’s get working on a quick frontend to make this a lot more comfortable to actually use. We’ll be using Streamlit for this, which is a quick and easy way to create web apps in Python. If you don’t have Streamlit installed, you can install it using pip:
pip install streamlit
Next up create a new file in your project’s main directory. I’m going to name mine app.py:
π Meme_Gen
π fonts
π ARIAL.TTF
π ComicSansMS3.ttf
π impact.ttf
π output
π templates
πΌ (all the images in here...)
π .env
π app.py --> create this file
π get_meme.py
π load_meme_data.py
π meme_data.json
π meme_image_editor.py
π system_instructions.py
So open up your app.py file and let’s start with our imports:
import streamlit as st from get_meme import generate_memes
We import streamlit as st so that we have a short alias to use. We then import our generate_memes function from get_meme.py.
Streamlit state
We’ll start by creating some session_state to store the generated meme images:
if "images" not in st.session_state:
st.session_state.images = []
The way this works is that Streamlit has a session_state object that you can use to store data across different parts of your app. We’re checking if the key "images" is not in the session_state, and if it’s not, we’re creating it as an empty list. This way the state will be initialized to an empty list when the code runs the first time.
The reason we wrote the code like this is that Streamlit reruns the entire script every time you interact with the app. This means that if we didn’t check if the key was already in the session_state, it would reset the images list back to an empty list every time you interacted with the app.
So the way we wrote it now, the second time the script runs, the key "images" will already be in the session_state, and it will not be overwritten with an empty list, thus preserving our values between runs. Let’s add one more of these state variables for the loading state where it is currently generating memes, as we want to disable the button during this process: