DEV Community

David Akim
David Akim

Posted on

Creating a movie finder app with Streamlit and OMDb API

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

  1. Must be familiar with Python
  2. Must have Python (at least version 3.8) installed
  3. Sign up for Streamlit account
  4. 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
Enter fullscreen mode Exit fullscreen mode

Create a virtual environment by entering the following in the command terminal.

virtualenv venv
Enter fullscreen mode Exit fullscreen mode

Activate the virtual environment using the following command.

venv\Scripts\activate
Enter fullscreen mode Exit fullscreen mode

Install streamlit using the following command.

pip install streamlit
Enter fullscreen mode Exit fullscreen mode

We will also be plotting the ratings and votes, so we need to install plotly-express with the following command.

pip install plotly-express
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create the title for the app. This code will display the text in title formatting.

# Streamlit app title
st.title("Movie Finder App")
Enter fullscreen mode Exit fullscreen mode

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", "")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

We need a variable to store the movie search results.

# Dataframe for storing all movie data
movies_df = pd.DataFrame()
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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.")
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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"
  }
Enter fullscreen mode Exit fullscreen mode

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...')
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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("–")
Enter fullscreen mode Exit fullscreen mode

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]
  )    
):
Enter fullscreen mode Exit fullscreen mode

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']],                                    
                          })
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Run the application by typing the following in the command terminal.

streamlit run streamlit_app.py
Enter fullscreen mode Exit fullscreen mode

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.

Image description

In this example, search for the dark knight. There are multiple results. This is only one of them.

Image description

View the plots.

Image description

Image description

This concludes our lesson. For more information on Streamlit, check the documentation here.

Top comments (0)