- Summary
- Project Goals
- User Goals
- User Stories
- Backlog
- Features
- Requirements
- Run Locally
- Deploy to Heroku
- Testing
- References
In today's streaming landscape, users face information overload with thousands of movies and TV shows spread across multiple platforms like Netflix, Disney+, etc. Many struggle to keep track of what they want to watch, often relying on messy notes, memory, or scattered lists across different apps. A recent survey revealed that people spend an average of 110 hours a year just searching for content, while 51% feel overwhelmed by excessive recommendations. Additionally, growing privacy concerns around smart TVs and streaming services highlight the need for user-controlled, non-intrusive tracking tools.
Reel Tracker CLI is a lightweight, command-line-based personal watchlist manager designed for movie lovers, binge-watchers, film critics, and streaming enthusiasts. It helps users save and organize their watchlists, track watched content, and provide ratings and recommendationsβall without intrusive data collection or algorithmic manipulation. By offering a privacy-focused, efficient, and customizable alternative to mainstream tracking apps, Reel Tracker CLI appeals to tech-savvy, data-conscious users who prefer self-managed solutions. The project also has potential for future expansion, including a front-end interface, user authentication, and database integration, making it valuable for market researchers and content licensing professionals as well.
More details at #1
Deployed at Heroku
Titles Data available for read at Google Sheets
- Address Information Overload in Entertainment
- Develop a Personal Watchlist Tracker
- Cater to Target Users with a CLI-Based Solution
- Focus on Privacy and User-Controlled Data
- Build a Scalable and Expandable Tool
- Effortless Watchlist Management
- Track Viewing History Easily
- Make Better Viewing Choices
- Stay in Control of Their Data & Privacy
- Optimize Their Viewing Experience Across Multiple Platforms
- As a user, I want to search for a title so that I can find relevant information.
- As a user, I want to add a title to my Watchlist or Viewing History so that I can track what I plan to watch or have already watched.
- As a user, I want to view my Watchlist or Viewing History so that I can review the titles I saved or watched.
- As a user, I want to remove a title from my Watchlist or Viewing History so that my lists remain accurate and updated.
- As a user, I want to move title between different lists (e.g. from Watchlist to Viewing History), so that I can maintain accurate tracking of my viewing progress.
- As a user, I want to exit the app when Iβm done so that I can close the session properly.
- As a user, I want to update details of an item in my Viewing History so that I can maintain accurate records.
- As a user, I want to rate a watched title so that I can track how much I enjoyed it.
- As a user, I want to see trending/popular titles so that I can explore new content.
- As a user, I want to get recommendations based on my ratings so that I can discover similar movies.
- As a user, I want to receive random movie suggestions so that I can easily pick what to watch next. (Not implemented β see backlog)
- As a user, I want to categorize my watchlist into sublists (e.g. βMust Watchβ, βFor Laterβ) so that I can organize my movies better. (Not implemented β see backlog)
The following features were originally planned but were not implemented in this phase due to a shift in priorities. Instead, we focused on enhancing the user experience by:
- Introducing See title details
- Handling recommendations edge cases:
- [No title rated 3+]
- [List have only few items]
- [Tie on preferred genre by frequency]
- [No item in list matched preferred genre]
- As a user, I want to categorize my watchlist into sublists (e.g. βMust Watchβ, βFor Laterβ) so that I can organize my movies better. This feature was deprioritized in favor of See title details, which provide more information for user about the titles.
- As a user, I want to receive random movie suggestions so that I can easily pick what to watch next. This feature was deprioritized in favor of recommendations edge cases, which ensures coherent feedback to user and better recommendations.
The diagram below illustrates how users interact with the core features of the ReelTracker CLI.
Users can search for movies or TV shows by keyword. The app returns results from The Movie Database (TMDb) API, including title, type, release year, and a short overview. Related user story: #3 Search for a title
Users can add any title from the search results directly to their personal watchlist for future viewing. Related user story: #4 Add to watchlist or Viewing History
Users can mark a title as watched and give it a personal rating from 1 to 5. This allows them to track both progress and preferences. Related user story: #5 View lists, #9 Rate watched title, #14 Update title
A title can be moved from the Watchlist to the Viewing History once watched, preserving metadata such as date added. When moving a title from Viewing History to Watchlist, watched data and rating are reset. Related user story: #7 Move title between lists, #14 Update title
Users can delete a title from the lists. Related user story: #6 Remove a title from lists
The system analyzes genre patterns from previously watched content, identifies top-rated titles (ratings β₯ 3), and sorts the watchlist based on genre similarity and media type preference. This allows users to receive tailored suggestions that reflect their actual taste and not generic trends or ads. Related user stories:#9 Rate watched title, #11 Get recommendations
Allows users to explore trending titles based on real-time popularity data from The Movie Database (TMDb). This feature is especially useful when users are just getting started or looking for something new outside of their personal watchlist. Trending titles are also shown when for recommendation flows where the user has no titles in their watchlist or viewing history. Related user story:#10 See what's trending
All user data (watchlist, history, ratings) is stored in a personal Google Sheet via the Sheets API. This ensures cloud persistence and makes data accessible outside the CLI.
- Uses
gspreadandgoogle-authfor integration - Credentials are safely loaded from a
.jsonfile and hidden using.gitignore
The app can rebuild Title objects from the spreadsheet using the from_sheet_row() method. This ensures data continuity across sessions.
A custom calculate_weighted_popularity() function ensures titles are ranked based on both TMDb popularity and vote count. This prevents obscure titles with low votes from unfairly topping the list. Search output uses this function to display most relevant item on top with the use of sort_items_by_popularity.
Consulted references:
-
Analyze viewing history The system identifies titles rated 3 or higher in the watched list using
get_top_rated_titles(). It then determines a preferred genre using rating-weighted frequency viaget_preferred_genre() -
Sort the watchlist If watchlist titles exist:
- It attempts to filter by the preferred genre using
filter_list_by_genre(). - If genre matches are found, titles are ranked by genre similarity to a top-rated title and popularity using
sort_titles_by_relevance(). - Titles are then reordered to prioritize the userβs preferred media type (movie or TV) via
reorder_titles_by_media_type()
-
Show personalized list The resulting sorted list is displayed to the user for selection via
display_title_entries()anddisplay_and_select_title() -
Handle edge cases When recommendations cannot be generated due to lack of data, the system will:
- No top-rated titles: Calls
handle_no_top_rated(), which analyzes all titles (watched + watchlist) and uses TMDbβs discovery API based on inferred media type and genre. - Very few items (β€3): A warning is printed indicating limited recommendation accuracy.
- Tied genre frequency: The
get_preferred_genre()function uses rating totals as a tiebreaker. - No genre matches in watchlist: Falls back to sorting entire watchlist by genre similarity and popularity, skipping genre filtering.
- Invalid menu options prompt the user to try again (
get_menu_choice) - Malformed commands (like
w x) return a helpful message explaining the issue (handle_action_with_index) - Ratings must be numeric and between 1β10; otherwise, the user is re-prompted (
get_title_rating) - Empty search queries are rejected with clear prompts (
get_user_search_input)
When selecting items from a list:
- Out-of-range indexes (e.g. choosing item 10 in a list of 3) are caught and explained (
select_item_from_results) - Non-numeric selections result in a descriptive error, and the user is given a chance to try again
- TMDb API requests (e.g.
fetch_tmdb_results) handle network timeouts, invalid responses, and missing keys with user-friendly messages. - Google Sheets operations check for missing worksheets and initialize them as needed (
save_item_to_list,get_titles_by_watch_status). - If the Google Sheet is missing or the
creds.jsonis misconfigured, the app will not crash but instead show a clear error and exit gracefully.
Whenever possible, users will be given the option to retry or return to a safe state (e.g. re-search, go back to the main menu) rather than exiting abruptly.
The UI adapts to Heroku CLI constraints (80x24), avoiding broken formatting and wrapping issues. Multi-line outputs (like overviews) are carefully indented for clarity.
The codebase is structured in modules by responsibility. This improves readability, scalability, and maintainability.
.
βββ run.py # Entry point for the CLI application
βββ requirements.txt # Python package dependencies
βββ .gitignore # Specifies files and directories to be ignored by Git
βββ .env # Environment variables: TMDB API key, TMBD URL (ignored in Git)
βββ creds.json # Google Sheets API credentials (ignored in Git)
βββ README.md # Project overview and setup instructions
βββ models/ # Core application data classes and logic
β βββ __init__.py
β βββ title.py # Represents a media title with metadata and user-specific logic
β βββ user_data.py # Manages user-generated data like watch history and ratings
β βββ title_metadata.py # Defines the TitleMetadata dataclass for detailed metadata
βββ recommendations/ # Title recommendation system
β βββ __init__.py
β βββ display.py # Displays recommendations in the UI
β βββ filters.py # Functions for filtering titles
β βββ gemre_analysis.py # Genre preference and similarity
β βββ handlers.py # Handles recommendation logic (user actions, data routing)
β βββ recommendations.py # Generates and manages recommendations
β βββ smart_recs.py # Logic for personalized recommendations
β βββ trending.py # Handles fetching and displaying trending titles for recommendations
β βββ utils.py # Helper functions for sorting recommended titles
βββ sheets/ # Google Sheets integration
β βββ __init__.py
β βββ auth.py # Handles authentication and sheet connection setup
β βββ crud.py # Performs create, update, and delete operations on sheet rows
β βββ query.py # Retrieves and filters rows, checks for duplicates
β βββ utils.py # Converts raw sheet data into Title objects
βββ tmdb/ # TMDb API integration for fetching movie data
β βββ __init__.py
β βββ tmdb.py # Contains functions for interacting with the TMDb API
β βββ utils.py # Processes and prepares TMDB API data
βββ ui/ # CLI display components and handlers
β βββ __init__.py
β βββ handlers.py # Responds to user menu selections and triggers logic
β βββ display.py # Handles layout and terminal content display
β βββ menus.py # CLI menus and navigation
β βββ user_input.py # Captures and validates user input
βββ utils/ # General-purpose utilities
β βββ __init__.py
β βββ utils.py # Utitlity functions such as formatting and sorting
βββ documentation/ # Contains project documentation and visual assets
β βββ search_1.png # Screenshot demonstrating search functionality
β βββ watched_1.png # Screenshot demonstrating watched list feature
β βββ ... # Additional documentation files and assetsTitle
Represents a movie or TV title. Holds attributes like name, type, release date, overview, popularity, vote count.
Includes methods for:
to_sheet_row: formats the title data for saving to Google Sheetsfrom_sheets_row: reconstructs aTitleobject from saved sheet data
TitleMetadata
Stores static metadata about a title:
id (str): Unique identifier for the title (usually from TMDb)title (str): Human-readable title of the mediamedia_type (str): Type of media ('movie' or 'tv')release_date (str): Year of releasegenres (List[str]): List of genre names associated with the titlepopularity (float): Weighted popularity score for sorting or rankingoverview (str): Description or synopsis of the title
UserTitleData
Stores user-generated data about a title:
watchedstatus: edited using thetoggle_watchedmethod- Personal
rating: edited using theset_ratingmethod added_datelog: populated with timestamp when object creationwatched_datelog: populated with timestamp whenwatchedstatus is changed toTrue
Consulted references:
- Script was coded using Python version 3.12.8
- To install dependencies run
pip3 freeze > requirements.txt
To properly use the app, youβll need:
- A Google Sheets document to store your watchlist and history.
- A Service Account JSON file for API access (creds.json)
- A TMDb API key in a
.envfile.
Follow the setup instructions on the following section to:
- Enable Google APIs and generate
creds.json - Create a
.envfile with your TMDb API Key
Your project structure should look something like this:
.
βββ **creds.json**
βββ **.env**
βββ run.py
βββ ...To use ReelTracker CLI on your machine, you can clone it and run it locally following the below steps.
First, open your terminal and clone the repo to your local environment:
git clone https://github.com/larevolucia/reeltracker_cli.git
cd reeltracker_cliVerify the Python version you have installed is compatible. You can verify this with:
python3 --versionCreate and activate a virtual environment to manage dependencies:
python3 -m venv venv
source venv\Scripts\activate # Windows
source venv/bin/activate # macOS/Linuxpip install -r requirements.txtThis project uses Google Sheets to store personal viewing history and watchlist. You'll need to enable Drive and Google Sheets API on your Google Cloud to be able to configure your personal list.
- Start by navigating to Google Cloud Console. If you don't have a Google account, you'll need to create one.
- Create a new project. Check the official documentation on new project creation.
- Go to your project home and navigate to APIs and Services > Library.
- Search for Google Drive API, navigate to its page and click on Enable.
- Follow the same process to activate Google Sheets API.
- In your project view, navigate to APIs and Services > Credentials.
- Click on Create credentials button, select Help me choose.
- On the form, select Google Drive API on the dropdownlist os APIs.
- Select App Data regarding the type of data to be used.
- Fill in the name of the service account and the account ID (You'll need this to configure your script).
- Click and Create and Continue.
- You'll be redirect to a credential screen. Select the e-mail address under Service Account and click on the edit button.
- Navigate to Keys and go to Add Key > Create New Key
- Select JSON and create.
- This action will automatically trigger a download of the json file.
-
Move the downloaded file to the root folder of your project. You can name it creds.json as I did, or give it another name. Just be sure that the name is matching in your
run.pyfile. -
On your Google account, create a new Google Sheets document. You can name it
reeltracker_clias I did, or give it another unique name. Just be sure that the name is matching in yourrun.pyfile. -
On your new file, click on the share button and copy&paste the e-mail address that can be found in your creds.json.
-
If you haven't yet, install
gspreadandgoogle-authlibraries. -
Add the following code to your project to set up your Google API connection:
import gspread from google.oauth2.service_account import Credentials SCOPE = [ "https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive" ] CREDS = Credentials.from_service_account_file('<your_creds_file_name>.json') SCOPED_CREDS = CREDS.with_scopes(SCOPE) GSPREAD_CLIENT = gspread.authorize(SCOPED_CREDS) SHEET = GSPREAD_CLIENT.open('<your_google_sheet_name>')
This project uses TMDB API to fetch data of movies and TV Shows. You'll need to create an account and request an API Key.
- Go to TMDB Signup
- Once signed in, request an API key at TMDB API page.
-
If you haven't yet, install
requestsandpython-dotenvlibraries. -
Create a .env file on your root folder and add your API key
TMDB_API_KEY=your_actual_tmdb_api_key_here. -
Optionally, you can also add the TMDB API URL
TMDB_URL ='https://api.themoviedb.org/3' -
Add .env to your .gitignore file to ensure it's never pushed to GitHub.
-
Load API Key from .env file:
import os from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() # Access TMDB API key TMDB_API_KEY = os.getenv('TMDB_API_KEY') TMDB_URL = os.getenv('TMDB_URL') # if stored in .env otherwise TMDB_URL ='https://api.themoviedb.org/3' if TMDB_API_KEY is None: raise ValueError("TMDB_API_KEY not found. Check your .env file.")
- Test your configuration by sending an API request:
TMDB_URL = os.getenv('TMDB_URL') LANGUAGE ='language=en-US' TMDB_API_KEY = os.getenv('TMDB_API_KEY') url = f'{TMDB_URL}/movie/popular?api_key={TMDB_API_KEY}&{LANGUAGE}&page=1' response = requests.get(url,timeout=10) if response.status_code == 200: data = response.json() return data['results'] else: print(f"Error: {response.status_code}, {response.text}") return []
- You can install
richlibrary for an easier read of the json responseimport json from rich import print_json url = f'{TMDB_URL}/movie/popular?api_key={api_key}&{LANGUAGE}&page=1' response = requests.get(url,timeout=10) if response.status_code == 200: data = response.json() return data['results']
Once everything is configured, run the CLI tool:
python run.pyIf everything is set up correctly, you should see the ReelTracker menu in your terminal.
When you create the app, you will need to add two buildpacks from the Settings tab. The ordering is as follows:
heroku/pythonheroku/nodejs
- You must then create a Config Var called
PORT. Set this to8000 - You must then create a Config Var called
CREDS. Copy&Paste yourcreds.jsonfile contents. - You must then create a Config Var called
TMDB_API_KEY. Copy&Paste your API Key value.
Connect your GitHub repository and deploy.
ReelTracker CLI was manually tested throughout development to ensure a smooth user experience and correct behavior. Below are the key areas tested:
| Test Case | Input | Expected Outcome | Status |
|---|---|---|---|
| Launch app | python run.py |
Main menu displays correctly | β |
| Invalid menu option | z or 10 |
Prompt user to try again | β |
| Back to main menu | m |
Main menu displays correctly | β |
| Test Case | Input | Expected Outcome | Status |
|---|---|---|---|
| Valid keyword | The Office |
TMDb results displayed | β |
| Empty input | (just Enter) | Prompt for valid input | β |
| Popular title | Avatar |
Sorted by popularity (checked with info command: i 1, i 2 etc.) | β |
| New search | n |
Request new search prompt | β |
| Test Case | Input | Expected Outcome | Status |
|---|---|---|---|
| More info | i 1 | Details of title 1 | β |
| Select item | 1 | Promps watch status question for title 1 | β |
| Test Case | Action | Expected Outcome | Status |
|---|---|---|---|
| Empty lists | Open list from main menu | Feedback to user that list is empty | β |
| Add title to watchlist | Select from search results | Title saved to Watchlist (Google Sheets) | β |
| Mark as watched | From Watchlist β Mark as watched | Title moved to Viewing History with correct rating | β |
| Move back to watchlist | From Viewing History β Move to watchlist | Rating cleared, added to Watchlist | β |
| Delete title | From both lists | Item removed from Google Sheet | β |
| View lists | Watchlist/History menu | Lists load from Google Sheets | β |
| Change rating | Input new rating 1β5 | Rating saved, impacts recs | β |
| Change rating | Input same rating 1β5 | Checks for changes in data and notify that there is nothing to update | β |
| Test Case | Action | Expected Outcome | Status |
|---|---|---|---|
| Invalid rating | Input goat or 11 |
Prompt to enter number between 1β5 | β |
| Request trending titles | Request from main menu | Display TMDb trending titles | β |
| Request recs (no sheet) | List have not yet been created | Fallback to TMDb trending titles | β |
| Request recs (no items) | Empty list | Fallback to TMDb trending titles | β |
| Request recs (no watched) | Only watchlist titles | Display watchlist items in popularity order (checked in sheet) | β |
| Request recs (no watchlist) | Only watched titles | Fetch top title and fetch similar titles on TMDb | β |
| Request recs (no rating) | No ratings β₯3 / 1 watchlist | Fallback to TMDb discovery results | β |
| Request recs (no rating) | No ratings β₯3 / no watchlist | Fallback to TMDb trending results | β |
| Request recommendations | Enough data present | Shows a list of items matching preferred genre sorted by genre similarity and popularity | β |
| Request recommendations | No watchlist title in preferred genre | Shows the entire watchlist sorted by genre similarity and popularity | β |
| Test Case | Scenario | Expected Outcome | Status |
|---|---|---|---|
Invalid index (e.g. 9 in a list of 3) |
Index out of range | Error message + retry prompt | β |
| TMDb API failure | Force invalid key | Error message shown, app continues | β |
| Missing Google Sheet | No matching sheet name | Descriptive error and exit | β |
- All code passed PEP8 validation using
flake8andpylint - No syntax or runtime errors observed during normal use
- All credentials and API keys are excluded via
.gitignoreand environment variables
- Issue #15 App was crashing because
title.pyis importing functions fromutils.pyand vice versa. The modular structure of the app was modified to avoid circular imports. - Issue #17 If a user has only rated titles 2 or below, the recommendation will crash the application. Initial fix to mitigate this was to provide a feedback to the user that the data was not suficient. Later, a fallback to
show_trending_titleswas implemented. - Issue #18 Display of recommendation list for specific use case (no watched titles) was not sorting the titles by popularity. The issue was caused due to an attempt to reuse the function originally created to sort TMDb lists to sort Google Sheets list. Some refactoring was required for the function to work as expected.
- Issue #19 Edge case testing: sheet is renamed or deleted in the Drive in the middle of an action. Since the app was only handling exceptions for save item, the app would crash in other cases. Although it is an edge case, adding logic for
WorksheetNotFoundfor update and delete use cases improves overall error handling. Aget_or_create_worksheetfunction was created and reused acrosscrud.pyfile. - Issue #20 App crashed when comparing string ratings values with integers. This happened due to manual intervention directly in Google Sheets to simulate a test case. Although it is unlikely to happen via CLI usage, a conditional to only perform the task when rating is either an integer or float was added to
get_top_rated_titlesfunction atfilters.py.
- The Movie Database API documentation
- Google Sheets API Documentation
- Google Drive API Documentation
- Google Workplace Documentation
- Python documentation
- PyNative Object Orienting Programming
- My mentor
- Code Institute community
- Friends and families that helped testing and providing useful feedback