In this lesson, we will go through the steps of creating a movie finder app using Streamlit and the OMDb API. Streamlit is an open-source Python library that makes it easy to create custom web apps. You can easily integrate popular data visualization libraries into your Streamlit app. The OMDb API is a RESTful web service for retrieving movie information. Our movie finder app will have a feature to filter the movie results by type either movie or series, by year of release and by IMDB rating. It will also have a feature to plot the ratings and votes.
Prerequisite
- Must be familiar with Python
- Must have Python (at least version 3.8) installed
- Sign up for Streamlit account
- Sign up for OMDb API Key
Procedure
Create a folder called streamlit-movie-info.
Navigate into this folder by typing the following in the command terminal.
cd streamlit-movie-info
Create a virtual environment by entering the following in the command terminal.
virtualenv venv
Activate the virtual environment using the following command.
venv\Scripts\activate
Install streamlit using the following command.
pip install streamlit
We will also be plotting the ratings and votes, so we need to install plotly-express with the following command.
pip install plotly-express
Create a folder called .streamlit. Make sure it is called .streamlit and not streamlit.
Inside the .streamlit folder, create a file called secrets.toml. Put your OMDb API here.
omdb_api = "YOUR OMDb API"
Create a custom poster image for movies that don't have a poster. You can create this image using any software. Call this image film-solid.png.
Create a python script called streamlit_app.py. Now we are going to write code. First we need to import the required libraries.
import streamlit as st
import requests
import pandas as pd
import plotly.express as px
from datetime import datetime
Create the title for the app. This code will display the text in title formatting.
# Streamlit app title
st.title("Movie Finder App")
Create an input element for user to enter movie title. This code will display a single-line text input widget. This is the movie search field.
# User input for movie title
movie_title = st.text_input("Enter Movie Title", "")
The year filter will have a range from 1900 to the current year. The current year is obtained using the following code snippet.
# Current Year
current_year = datetime.now().year
We need a variable to store the movie search results.
# Dataframe for storing all movie data
movies_df = pd.DataFrame()
Set the filter options.
# Filter options
type_filter = st.selectbox("Filter by Type", ["movie", "series"])
year_filter = st.slider("Filter by Release Year", min_value=1900, max_value=current_year, step=1, value=(1900, current_year))
rating_filter = st.slider("Filter by IMDb Rating", min_value=0.0, max_value=10.0, step=0.1, value=(0.0, 10.0))
There are 2 slider widgets and 1 select widget. The type filter is the select widget with 2 options: movie and series. The year of release filter is the slider widget which has a range from 1900 to the current year. The IMDB rating filter is also a slider widget which has a range from 0.0 to 10.0 with increments of 0.1.
Get the results of query using the following code snippet.
# Search for the movie using the OMDB API
if movie_title:
omdb_api_url = "http://www.omdbapi.com/"
api_key = st.secrets["omdb_api"] # OMDb API key (you need to sign up for a free API key)
params = {
"apikey": api_key,
"s": movie_title,
"type": type_filter,
"y": f"{year_filter[0]}-{year_filter[1]}",
"r": "json"
}
with st.spinner('Processing...'):
response = requests.get(omdb_api_url, params=params)
data = response.json()
# Filter and display movie details
if "Search" in data:
for movie in data["Search"]:
# Additional request to get detailed information for each movie
detailed_params = {"apikey": api_key, "i": movie["imdbID"], "plot":"full", "r": "json"}
detailed_response = requests.get(omdb_api_url, params=detailed_params)
detailed_data = detailed_response.json()
detailed_data["Year"] = detailed_data["Year"].rstrip("–")
# Apply additional filters
if (
(
type_filter == 'movie' and
year_filter[0] <= int(detailed_data["Year"]) <= year_filter[1] and
detailed_data["imdbRating"] != "N/A" and
rating_filter[0] <= float(detailed_data["imdbRating"]) <= rating_filter[1]
) or
(
type_filter == 'series' and
detailed_data["imdbRating"] != "N/A" and
rating_filter[0] <= float(detailed_data["imdbRating"]) <= rating_filter[1]
)
):
# Temporarily store movie detail in this dataframe.
new_row_df = pd.DataFrame({'Poster':[detailed_data['Poster']],
'Title':[f"{detailed_data['Title']} ({detailed_data['Year']})"],
'Year':[detailed_data['Year']],
'Rated':[detailed_data['Rated']],
'Runtime':[detailed_data['Runtime']],
'Released':[detailed_data['Released']],
'Genre':[detailed_data['Genre']],
'Director':[detailed_data['Director']],
'Writer':[detailed_data['Writer']],
'Actors':[detailed_data['Actors']],
'Language':[detailed_data['Language']],
'Country':[detailed_data['Country']],
'Awards':[detailed_data['Awards']],
'Plot': [detailed_data['Plot']],
'IMDB Rating': [detailed_data['imdbRating']],
'IMDB Votes': [detailed_data['imdbVotes']],
})
# Add movie detail dataframe to the main dataframe containing all movies
movies_df = pd.concat([movies_df, new_row_df], ignore_index=True)
else:
st.warning("No movies found for the specified criteria.")
else:
st.warning("Please enter a movie title.")
Code Breakdown
First we check if the movie search is not empty. If it is, we throw a warning using st.warning. Then we set the OMDb API url and API key. The API key is stored in the secrets.toml file and is called omdb_api. We can access this key using st.secrets.
omdb_api_url = "http://www.omdbapi.com/"
api_key = st.secrets["omdb_api"]
Set the other parameters. The "s" field sets the movie title to search for. The "type" field sets the type of result to return. In this case, it is either movie or series. The "y" field sets the year of release. In this case, it is between 1900 and the current year. The "r" field sets the data type to return.
params = {
"apikey": api_key,
"s": movie_title,
"type": type_filter,
"y": f"{year_filter[0]}-{year_filter[1]}",
"r": "json"
}
We will be using a spinner to indicate that searching is in progress. This widget temporarily shows a message while a code block is being executed.
with st.spinner('Processing...')
Get the response from the query and load JSON data into a Python object.
response = requests.get(omdb_api_url, params=params)
data = response.json()
Check if there are any results. If there are no results throw a warning using st.warning. If there are results, iterate through each item in the search results.
for movie in data["Search"]:
# Additional request to get detailed information for each movie
detailed_params = {"apikey": api_key, "i": movie["imdbID"], "plot":"full", "r": "json"}
detailed_response = requests.get(omdb_api_url, params=detailed_params)
detailed_data = detailed_response.json()
Like before, set the parameters. The "i" field sets the IMDb ID. The "plot" field determines if to use the short or full plot.
Sometimes the "year" parameter in the search results may contain an invalid number. Remove the additional character.
detailed_data["Year"] = detailed_data["Year"].rstrip("–")
Apply the filters. Note: For there is no year of release for type: series. Therefore, the year filter does not affect this type.
if (
(
type_filter == 'movie' and
year_filter[0] <= int(detailed_data["Year"]) <= year_filter[1] and
detailed_data["imdbRating"] != "N/A" and
rating_filter[0] <= float(detailed_data["imdbRating"]) <= rating_filter[1]
) or
(
type_filter == 'series' and
detailed_data["imdbRating"] != "N/A" and
rating_filter[0] <= float(detailed_data["imdbRating"]) <= rating_filter[1]
)
):
Temporarily store the result in a variable called new_row_df. This variable will be concatenated with the main variable containing all results.
# Temporarily store movie detail in this dataframe.
new_row_df = pd.DataFrame({'Poster':[detailed_data['Poster']],
'Title':[f"{detailed_data['Title']} ({detailed_data['Year']})"],
'Year':[detailed_data['Year']],
'Rated':[detailed_data['Rated']],
'Runtime':[detailed_data['Runtime']],
'Released':[detailed_data['Released']],
'Genre':[detailed_data['Genre']],
'Director':[detailed_data['Director']],
'Writer':[detailed_data['Writer']],
'Actors':[detailed_data['Actors']],
'Language':[detailed_data['Language']],
'Country':[detailed_data['Country']],
'Awards':[detailed_data['Awards']],
'Plot': [detailed_data['Plot']],
'IMDB Rating': [detailed_data['imdbRating']],
'IMDB Votes': [detailed_data['imdbVotes']],
})
Concatenate the temporary variable with the main variable called movies_df.
# Add movie detail dataframe to the main dataframe containing all movies
movies_df = pd.concat([movies_df, new_row_df], ignore_index=True)
Now we have to create the visualize the data. Create two tabs. The first tab contains the search results with the movie details. The second tab contains the plots of the Ratings and Reviews.
# Setup tabs
tab1, tab2 = st.tabs(["Search Results", "Ratings and Votes"])
In the first tab print out all the movie details such as poster, title, rating, rated, runtime, released, genre, director, writer, actors, plot, language, country and awards.
with tab1:
if(len(movies_df)>0):
st.header("Search Results")
for i in range(len(movies_df)):
col1, col2 = st.columns([1,2])
with col1:
# Display movie poster
if(movies_df['Poster'][i]!="N/A"):
st.image(movies_df['Poster'][i], caption=movies_df['Title'][i], use_column_width=True)
else:
# If there is no movie poster, use custom movie poster
st.image("film-solid.png")
with col2:
# Display movie details
st.subheader(movies_df['Title'][i])
col1, col2, col3 = st.columns(3)
col1.write(f"IMDb Rating: {movies_df['IMDB Rating'][i]}")
col2.write(f"Rated: {movies_df['Rated'][i]}")
col3.write(f"Runtime: {movies_df['Runtime'][i]}")
st.write(f"Released: {movies_df['Released'][i]}")
st.write(f"Genre: {movies_df['Genre'][i]}")
st.write(f"Director: {movies_df['Director'][i]}")
st.write(f"Writer: {movies_df['Writer'][i]}")
st.write(f"Actors: {movies_df['Actors'][i]}")
st.write(f"Plot: {movies_df['Plot'][i]}")
st.write(f"Language: {movies_df['Language'][i]}")
st.write(f"Country: {movies_df['Country'][i]}")
st.write(f"Awards: {movies_df['Awards'][i]}")
st.divider()
All text content is printed using st.write. Images are displayed using st.image. st.header and st.subheader display the text in header and sub-header formats. st.columns creates columns. In this case, there are 3 columns: IMDb Rating, Rated and Runtime. st.divider displays a horizontal rule.
In the second tab plot the ratings and votes for the movies.
# Plots of Ratings and Votes
with tab2:
if(len(movies_df)>0):
fig = px.bar(movies_df, x='Title', y='IMDB Rating')
st.header("IMDB Ratings")
st.plotly_chart(fig, theme="streamlit", use_container_width=True)
fig = px.bar(movies_df, x='Title', y='IMDB Votes')
st.header("IMDB Votes")
st.plotly_chart(fig, theme="streamlit", use_container_width=True)
The plot type is a bar chart. The x axis contains the title of the movies and y axis contains IMDB Ratings or Votes. st.plotly_chart plots the chart. The theme is set to streamlit. The width of the chart is set to container width. To find out more information about st.plotly_chart click here.
The entire script should look like this.
import streamlit as st
import requests
import pandas as pd
import plotly.express as px
from datetime import datetime
# Streamlit app title
st.title("Movie Finder App")
# User input for movie title
movie_title = st.text_input("Enter Movie Title", "")
# Current Year
current_year = datetime.now().year
# Dataframe for storing all movie data
movies_df = pd.DataFrame()
# Filter options
type_filter = st.selectbox("Filter by Type", ["movie", "series"])
year_filter = st.slider("Filter by Release Year", min_value=1900, max_value=current_year, step=1, value=(1900, current_year))
rating_filter = st.slider("Filter by IMDb Rating", min_value=0.0, max_value=10.0, step=0.1, value=(0.0, 10.0))
# Search for the movie using the OMDB API
if movie_title:
omdb_api_url = "http://www.omdbapi.com/"
api_key = st.secrets["omdb_api"] # OMDb API key (you need to sign up for a free API key)
params = {
"apikey": api_key,
"s": movie_title,
"type": type_filter,
"y": f"{year_filter[0]}-{year_filter[1]}",
"r": "json"
}
with st.spinner('Processing...'):
response = requests.get(omdb_api_url, params=params)
data = response.json()
# Filter and display movie details
if "Search" in data:
for movie in data["Search"]:
# Additional request to get detailed information for each movie
detailed_params = {"apikey": api_key, "i": movie["imdbID"], "plot":"full", "r": "json"}
detailed_response = requests.get(omdb_api_url, params=detailed_params)
detailed_data = detailed_response.json()
detailed_data["Year"] = detailed_data["Year"].rstrip("–")
# Apply additional filters
if (
(
type_filter == 'movie' and
year_filter[0] <= int(detailed_data["Year"]) <= year_filter[1] and
detailed_data["imdbRating"] != "N/A" and
rating_filter[0] <= float(detailed_data["imdbRating"]) <= rating_filter[1]
) or
(
type_filter == 'series' and
detailed_data["imdbRating"] != "N/A" and
rating_filter[0] <= float(detailed_data["imdbRating"]) <= rating_filter[1]
)
):
# Temporarily store movie detail in this dataframe.
new_row_df = pd.DataFrame({'Poster':[detailed_data['Poster']],
'Title':[f"{detailed_data['Title']} ({detailed_data['Year']})"],
'Year':[detailed_data['Year']],
'Rated':[detailed_data['Rated']],
'Runtime':[detailed_data['Runtime']],
'Released':[detailed_data['Released']],
'Genre':[detailed_data['Genre']],
'Director':[detailed_data['Director']],
'Writer':[detailed_data['Writer']],
'Actors':[detailed_data['Actors']],
'Language':[detailed_data['Language']],
'Country':[detailed_data['Country']],
'Awards':[detailed_data['Awards']],
'Plot': [detailed_data['Plot']],
'IMDB Rating': [detailed_data['imdbRating']],
'IMDB Votes': [detailed_data['imdbVotes']],
})
# Add movie detail dataframe to the main dataframe containing all movies
movies_df = pd.concat([movies_df, new_row_df], ignore_index=True)
else:
st.warning("No movies found for the specified criteria.")
else:
st.warning("Please enter a movie title.")
# Setup tabs
tab1, tab2 = st.tabs(["Search Results", "Ratings and Votes"])
# Search Results: List of movie details
with tab1:
if(len(movies_df)>0):
st.header("Search Results")
for i in range(len(movies_df)):
col1, col2 = st.columns([1,2])
with col1:
# Display movie poster
if(movies_df['Poster'][i]!="N/A"):
st.image(movies_df['Poster'][i], caption=movies_df['Title'][i], use_column_width=True)
else:
# If there is no movie poster, use custom movie poster
st.image("film-solid.png")
with col2:
# Display movie details
st.subheader(movies_df['Title'][i])
col1, col2, col3 = st.columns(3)
col1.write(f"IMDb Rating: {movies_df['IMDB Rating'][i]}")
col2.write(f"Rated: {movies_df['Rated'][i]}")
col3.write(f"Runtime: {movies_df['Runtime'][i]}")
st.write(f"Released: {movies_df['Released'][i]}")
st.write(f"Genre: {movies_df['Genre'][i]}")
st.write(f"Director: {movies_df['Director'][i]}")
st.write(f"Writer: {movies_df['Writer'][i]}")
st.write(f"Actors: {movies_df['Actors'][i]}")
st.write(f"Plot: {movies_df['Plot'][i]}")
st.write(f"Language: {movies_df['Language'][i]}")
st.write(f"Country: {movies_df['Country'][i]}")
st.write(f"Awards: {movies_df['Awards'][i]}")
st.divider()
# Plots of Ratings and Votes
with tab2:
if(len(movies_df)>0):
fig = px.bar(movies_df, x='Title', y='IMDB Rating')
st.header("IMDB Ratings")
st.plotly_chart(fig, theme="streamlit", use_container_width=True)
fig = px.bar(movies_df, x='Title', y='IMDB Votes')
st.header("IMDB Votes")
st.plotly_chart(fig, theme="streamlit", use_container_width=True)
Run the application by typing the following in the command terminal.
streamlit run streamlit_app.py
streamlit_app.py is the name of the file we need to run.
On the browser go to http://localhost:8501/. You will see the following screen.
In this example, search for the dark knight. There are multiple results. This is only one of them.
View the plots.
This concludes our lesson. For more information on Streamlit, check the documentation here.
Top comments (0)