Python Forum
Best approach before adding features - Movie information script
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Best approach before adding features - Movie information script
#1
Hi everyone,

I think I've finally wrapped my mind around classes but I would like to get some feedback on my current script before adding additional features.

There are 2 classes.
- The first handles the directory logic, creates locations and empty files, and sets variable object values for these within the class as the code is executed. 
- The second queries TMDB for a variety of data and has a fallback to acquire the IMDB Id from IMDB if there is not one present on TMDB. The methods in this class also sets variable object values within the class.

I'm unsure of whether it's best to be creating individual attributes for the classes as I am, or if I should compile some of the information into methods that return dictionaries, or if I should write some of them to return more specific values like the get_moviename() method. Any advice or guidance on this would be greatly appreciated.

There are more data sources to add so I want to set a good foundation before writing them into the script. See code below. Tested print statements for class variables are all at the bottom.

To provide context to the goal of this script...
path and dest objects will be change to input() long-term.
Once I get everything written to collect and structure the data, I'll create a class to utilize the data.. This class will contain methods to write the information to either a text file, database, or both.
I'll also be creating a config file once the script reaches a level of completion.

*Ignore any commented notes you may find for now. There might be a couple things I need to check or address.
import os
import shutil
from guessit import guessit
import tmdbsimple as tmdb
import imdb

tmdb.API_KEY = 'REMOVED'

class names_locations:
    path="C:\\Users\\REMOVED\\python_apps\\my_progs\\test_folder\\FILE_OR_FOLDER_HERE"
    dest="C:\\Users\\REMOVED\\python_apps\\my_progs\\test_folder\\test"
    basename = ""
    target_file = ""
    txt_file = ""
    final_dest = ""
    ss_dir = ""
    moviename = ""
    title_guess = ""
    year_guess = ""
    release_group_guess = ""
    resolution_guess = ""
    
    def make_names_dirs(self):
        list1 = []
        if os.path.isfile(self.path):
            list1.append(os.path.basename(self.path))
            base1 = os.path.splitext(list1[0])
            self.basename = base1[0]
            self.target_file = self.path
        else:
            list2 = []
            list1.append(os.path.basename(self.path))
            self.basename = list1[0]
            for file in os.scandir(self.path):
                if "-sample" in file.name or "-Sample" in file.name:
                    continue
                if file.name.endswith(('.mp4', '.mkv', '.avi')):
                    list2.append(file.name)
            self.target_file = os.path.join(self.path, list2[0])
        self.txt_file = f"{self.basename}.txt"
        self.final_dest = os.path.join(self.dest, self.basename)
        if self.final_dest == self.path:
            print("Source and Destination directories cannot be the same.")
            self.dest = input("Please re-enter the destination directory: ")
            self.final_dest = os.path.join(self.dest, self.basename)
            if self.path == self.final_dest:
                print("Source and Destination directories cannot be the same. Ending method unsuccessfully...")
            else:
                if os.path.exists(self.final_dest):
                    shutil.rmtree(self.final_dest)
                    os.mkdir(self.final_dest)
                else:
                    os.mkdir(self.final_dest)
                self.ss_dir = os.path.join(self.final_dest, "", "screenshots")
                os.mkdir(self.ss_dir)
        else:
            if os.path.exists(self.final_dest):
                shutil.rmtree(self.final_dest)
                os.mkdir(self.final_dest)
            else:
                os.mkdir(self.final_dest)
            self.ss_dir = os.path.join(self.final_dest, "", "screenshots")
            os.mkdir(self.ss_dir)

    def get_moviename(self):
        guess = guessit(self.basename)
        self.title_guess = guess['title']
        self.year_guess = guess['year']
        self.release_group_guess = guess['release_group']
        self.resolution_guess = guess['screen_size']
        is_name = input(f"Is {self.title_guess} {self.year_guess} the correct movie? Y/N ")
        if "Yes" in is_name or "yes" in is_name or "Y" in is_name or "y" in is_name:
            self.moviename = self.title_guess
            return self.moviename
        else:
            self.moviename = input("Enter a movie name (name only!): ")
            return self.moviename

class movie_db_search:
    tmdb_id = ""
    tmdb_url = "https://www.themoviedb.org/movie/"
    release_date = ""
    release_year = ""
    imdb_url = ""
    poster_base = "https://image.tmdb.org/t/p/original/"
    poster_url = ""
    title = ""
    print_title = ""
    tagline = ""
    genres = []
    keywords = []
    actors = []
    directors = []
    writers = []
    release_dates = []
    recommendations = []
    similar_movies = []
    directors_known_for = {}
    writers_known_for = {}
    
    def get_ids(self):
        search_results = []
        inc = 1
        search = tmdb.Search()
        response = search.movie(query=moviename)
        for movie in search.results:
            print(f"{inc}. {movie['title']} {movie['release_date']}")
            search_results.append(movie['id'])
            inc += 1
            if inc > 5:
                break

        enumerate(search_results, 1)
        choice = input("Choose a movie: \n")
        self.tmdb_id = search_results[int(choice)-1]

        movie = tmdb.Movies(int(self.tmdb_id))
        response1 = movie.info()

        for movie in search.results:
            if movie['id'] == self.tmdb_id:
                title_url = "https://www.imdb.com/title/"
                self.release_date = movie['release_date']
                self.release_year = self.release_date[0:4]
                try:
                    imdb_url = f"{title_url}{movie.id}" #### Issue with movie.id?
                    return self.imdb_url
                except:
                    print("TMDB returned no IMDB id. Searching IMDB.")
                    print("Choose a movie from the list below. ")
                    ia  = imdb.IMDb()
                    movie_search = ia.search_movie(moviename)
                    imdb_search_results = []
                    imdb_id = []
                    inc = 1
                    for i in range(len(movie_search)):
                        id = movie_search[i].movieID
                        print(f"{inc}. {movie_search[i]['title']}: {title_url}{id}")
                        imdb_search_results.append(id)
                        inc += 1
                        if inc > 5:
                            break

                    enumerate(imdb_search_results, 1)
                    imdb_choice = input("Choose a movie: \n")
                    imdb_choice1 = imdb_search_results[int(imdb_choice)-1]
                    imdb_id.append(imdb_choice1)
                    self.imdb_url = f"{title_url}{imdb_id[0]}"
                    return self.imdb_url

    def get_db_info(self):
        movie = tmdb.Movies(int(self.tmdb_id))
        response = movie.info()
        for i in response:
            if i == 'title':
                self.title = response['title']
        self.print_title = f"{self.title} {self.release_year}"
        for i in response:
            if i == 'poster_path':
                self.poster_url = f"{self.poster_base}{response['poster_path']}"
        for i in response:
            if i == 'tagline':
                self.tagline = response['tagline']
        for i in response:
            if i == "genres":
                for j in response['genres']:
                    for k in j:
                        if k == "name":
                            self.genres.append(j['name'])
        keywords_query = movie.keywords()
        for kw in keywords_query['keywords']:
            self.keywords.append(kw['name'])
        credits_query = movie.credits()
        for actor in credits_query['cast']:
            if 'cast_id' in actor:
                self.actors.append(actor['name'])
        for crew in credits_query['crew']:
            if 'credit_id' in crew:
                if "Directing" in crew['department']:
                    self.directors.append(crew['name'])
        for crew in credits_query['crew']:
            if 'credit_id' in crew:
                if "Writing" in crew['department']:
                    self.writers.append(crew['name'])
        release_dates_query = movie.release_dates()
        release_dates = []
        for i in release_dates_query['results']:
            for j in i['release_dates']:
                long_date = j['release_date']
                short_date = long_date[0:10]
                self.release_dates.append(short_date)
        recommendations_query = movie.recommendations()
        for i in recommendations_query['results']:
            self.recommendations.append(f"{i['title']} {i['release_date']} {self.tmdb_url}{i['id']}")
        similar_query = movie.similar_movies()
        sim_inc = 1
        for i in similar_query['results']:
            self.similar_movies.append(f"{i['title']} {i['release_date']} {self.tmdb_url}{i['id']}")
            sim_inc += 1
            if sim_inc > 5:
                break
        search = tmdb.Search()
        for i in self.directors:
            self.directors_known_for[str(i)] = []
            director_search = search.person(query=i)
            for j in director_search['results']:
                for k in  j['known_for']:
                    self.directors_known_for[i].append(f"{k['title']} {k['release_date']} {self.tmdb_url}{k['id']}")
        for i in self.writers:
            if i in self.directors:
                continue
            else:
                writer_search = search.person(query=i)
                for j in writer_search['results']:
                    for k in j['known_for']:
                        writers_known_for[str(i)] = []
                        self.writers_known_for[i].append(f"{k['title']}{k['release_date']} {self.tmdb_url}{k['id']}")


name_loc_data = names_locations()

#print(name_loc_data.path)
#print(name_loc_data.dest)
#print(name_loc_data.basename)
#print(name_loc_data.target_file)
#print(name_loc_data.txt_file)
#print(name_loc_data.ss_dir)

names_dirs = name_loc_data.make_names_dirs()

#print(name_loc_data.title_guess)
#print(name_loc_data.moviename)
#print(name_loc_data.title_guess)
#print(name_loc_data.resolution_guess)

moviename = name_loc_data.get_moviename()

#print(moviename)

movie_db = movie_db_search()
ids = movie_db.get_ids()

#print(movie_db.tmdb_id)
#print(movie_db.release_date)
#print(movie_db.release_year)
#print(movie_db.imdb_url)

db_info = movie_db.get_db_info()

#print(movie_db.title)
#print(movie_db.print_title)
#print(movie_db.poster_url)
#print(movie_db.genres)
#print(movie_db.keywords)
#print(movie_db.actors)
#print(movie_db.directors)
#print(movie_db.writers)
#print(movie_db.release_dates)
#print(movie_db.recommendations)
#print(movie_db.similar_movies)
#print(movie_db.directors_known_for)
#print(movie_db.writers_known_for)
#print(movie_db.tagline)
Reply
#2
Hi pythonnewbie138, here's a couple ideas that might help you:

Instead of using class attributes, you might consider creating the attributes inside the __init__ method:

#try this
class movie_db_search:
    def __init__(self):
        self.tmdb_id = ""
        self.tmdb_url = "https://www.themoviedb.org/movie/" 
        ...
        self.genres = [] #instance attribute

#instead of this
class movie_db_search:
    tmdb_id = "" 
    tmdb_url = "https://www.themoviedb.org/movie/"
    ...
    genres = [] #class attribute
__init__ is one of python's "magic methods", which just means it's a method that python calls automatically. In the case of __init__, python calls it whenever you create a new instance. In your example you're adding the attributes to the class object, which means they will be shared. This is fine for anything that is "passed by copy" (C++ concept) such as strings and ints, but the attributes that are "passed by reference" such as lists and dictionaries, can be mutated by any other instance of the class. There are times you might want this behavior, for instance if your code needed to retrieve a session handle, you might choose to do that once at the class level, and let each instance use it that way.

You should also get in the practice of putting all of your calls at the end of the module under a condition like this:

if __name__ == '__main__':
    name_loc_data = names_locations()
 
    #print(name_loc_data.path)
    #print(name_loc_data.dest)
    #print(name_loc_data.basename)
    ...
This will make sure these lines only run if you're running this module directly ie:
>> python movie_db_search.py

Once you include this check, it will allow you to import this module into another module so that you can access your class without running these extra lines.

I usually like to take it one step further and put all of it in a main function like this:

def main():
    name_loc_data = names_locations()
 
    #print(name_loc_data.path)
    #print(name_loc_data.dest)
    #print(name_loc_data.basename)
    ...

if __name__ == '__main__':
    main()
Reply
#3
Thanks for the feedback MadisonAster! Sorry it's taken me to long to get back to this but life has been busy.

You're advice makes sense and I'll implement an __init__ method. I've realized that I need to put more effort into the data structure and practical application of the data as it's passed to/from the various classes I've written. I think this will be a good start to that.

When using the main() function, does it go at the end of each class or the end of the script if there are multiple classes?
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020