Creating a movie finder app with Streamlit and OMDb API
Tags: streamlit, python
Published: 2023-11-27 17:13:09

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](https://medium.com/r/?url=https%3A%2F%2Fwww.python.org%2Fdownloads%2F) (at least version 3.8)  installed
3. Sign up for [Streamlit account](https://share.streamlit.io/signup)
4. Sign up for [OMDb API Key](https://www.omdbapi.com/apikey.aspx?__EVENTTARGET=freeAcct&__EVENTARGUMENT=&__LASTFOCUS=&__VIEWSTATE=%2FwEPDwUKLTIwNDY4MTIzNQ9kFgYCAQ9kFgICBw8WAh4HVmlzaWJsZWhkAgIPFgIfAGhkAgMPFgIfAGhkGAEFHl9fQ29udHJvbHNSZXF1aXJlUG9zdEJhY2tLZXlfXxYDBQtwYXRyZW9uQWNjdAUIZnJlZUFjY3QFCGZyZWVBY2N0oCxKYG7xaZwy2ktIrVmWGdWzxj%2FDhHQaAqqFYTiRTDE%3D&__VIEWSTATEGENERATOR=5E550F58&__EVENTVALIDATION=%2FwEdAAU%2BO86JjTqdg0yhuGR2tBukmSzhXfnlWWVdWIamVouVTzfZJuQDpLVS6HZFWq5fYpioiDjxFjSdCQfbG0SWduXFd8BcWGH1ot0k0SO7CfuulHLL4j%2B3qCcW3ReXhfb4KKsSs3zlQ%2B48KY6Qzm7wzZbR&at=freeAcct&Email=)

**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](https://medium.com/r/?url=https%3A%2F%2Fdocs.streamlit.io%2Flibrary%2Fapi-reference%2Fcharts%2Fst.plotly_chart). 

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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t27io0o3qef4mpa78v69.jpg)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pbo6l4pirgr8qdo119m1.jpg)

View the plots.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3m37rghcbnxfwnsjn3dp.jpg)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jge5m042pa6tpgwcbhxe.jpg)

This concludes our lesson. For more information on Streamlit, check the documentation [here](https://medium.com/r/?url=https%3A%2F%2Fdocs.streamlit.io%2Flibrary%2Fget-started).