Last active
February 19, 2021 23:41
-
-
Save textbook/a453735203917ffdd908 to your computer and use it in GitHub Desktop.
Want to identify an actor from multiple movies they've been in? I've got your back.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Find the overlap of actors between multiple movies.""" | |
from __future__ import print_function | |
import argparse | |
from operator import itemgetter | |
from sys import argv, exit | |
try: | |
from imdb import IMDb | |
except ImportError: | |
print('Please "pip install IMDbPY" to use this tool.') | |
exit() | |
try: | |
input = raw_input # Python 2.x | |
except NameError: | |
pass # Python 3.x | |
INTERFACE = IMDb() | |
class MovieNotFound(ValueError): | |
pass | |
def overlapping_actors(movies): | |
"""Find the overlapping actors between multiple movies. | |
Arguments: | |
movies (Iterable): The Movie objects to process. | |
Returns: | |
set: The Actor objects in the overlap. | |
""" | |
shared_actors = set() | |
for actors in map(itemgetter('actors'), movies): | |
if shared_actors: | |
shared_actors.intersection_update(actors) | |
else: | |
shared_actors.update(actors) | |
return shared_actors | |
def input_multiple_movies(count=2, start=1, verbose=True): | |
"""Take a list of movies from the user. | |
Arguments: | |
count (int, optional): The number of movies (defaults to ``2``, | |
pass ``None`` to continue until the user enters nothing). | |
start (int, optional): The index of the first movie to enter | |
(defaults to ``1``). | |
verbose (bool, optional): Whether to ask the user to | |
disambiguate multiple search results (defaults to ``True``). | |
Returns: | |
list: The resulting Movie objects. | |
""" | |
movies = [] | |
index = start | |
template = 'Select movie {}' | |
if count is not None: | |
template = 'Select movie {{}} of {}'.format(count + start - 1) | |
while count is None or len(movies) < count: | |
print(template.format(index)) | |
term = input('Enter a term to search for [enter nothing to stop]: ') | |
if not term: | |
break | |
try: | |
movie = search_for_movie(term, verbose) | |
except MovieNotFound as err: | |
print(err.message) | |
continue | |
if movie is None: | |
break | |
movies.append(movie) | |
index += 1 | |
return movies | |
def search_for_movie(term, verbose=True): | |
"""Take movie input from the user. | |
Notes: | |
Gets only the top five hits for the specified term, and allows | |
the user to select one if there isn't a single result. | |
Arguments: | |
term (str): The term to search for. | |
verbose (bool, optional): Whether to ask the user to | |
disambiguate multiple search results (defaults to ``True``). | |
Returns: | |
Movie: The IMDB movie object. | |
Raises: | |
MovieNotFound: If no movie can be found. | |
""" | |
possibilities = INTERFACE.search_movie(term, results=5) | |
if not possibilities: | |
raise MovieNotFound('No movies found for {!r}'.format(term)) | |
elif len(possibilities) == 1 or not verbose: | |
movie = possibilities[0] | |
else: | |
movie = disambiguate(possibilities) | |
INTERFACE.update(movie) | |
return movie | |
def disambiguate(possibilities): | |
"""Allow the user to disambiguate from a list of movies. | |
Arguments: | |
possibilities (list): The possible matches. | |
Returns: | |
Movie: The IMDB movie object. | |
""" | |
print('Multiple movies found, please select one.') | |
for index, possibility in enumerate(possibilities, 1): | |
print('{}. {}'.format(index, possibility['long imdb canonical title'])) | |
index = integer_input('Select a movie: ', min_=1, max_=len(possibilities)) | |
return possibilities[index - 1] | |
# --- Helper functions | |
def print_input(movies): | |
"""Nice formatting for the input movies. | |
Arguments: | |
movies (Iterable): The Movie objects to print out. | |
""" | |
if len(movies) > 1: | |
input_ = ' and '.join(( | |
', '.join(map(itemgetter('title'), movies[:-1])), | |
movies[-1]['title']), | |
) | |
else: | |
input_ = movies[0]['title'] | |
print('Checking {}...'.format(input_)) | |
def print_output(shared_actors): | |
"""Nice formatting for the overlapping actors. | |
Arguments: | |
shared_actors (Iterable): The shared actors to output. | |
""" | |
if shared_actors: | |
print("The following actors appear in all movies: ") | |
for actor in shared_actors: | |
print(' - {} [#{}]'.format(actor['name'], actor.getID())) | |
else: | |
print("No actors appear in all movies.") | |
def integer_input(prompt, min_=None, max_=None): | |
"""Allow the user to enter an integer with validation. | |
Arguments: | |
prompt (str): The prompt to show. | |
min_ (int, optional): The minimum value to accept. | |
max_ (int, optional): The maximum value to accept. | |
Returns: | |
int: The user's validated input. | |
""" | |
while True: | |
try: | |
value = int(input(prompt)) | |
except ValueError: | |
print('Please enter an integer.') | |
else: | |
if min_ is None and value < min_: | |
print('Please enter at least {!r}'.format(min_)) | |
elif max_ is None and value > max_: | |
print('Please enter at most {!r}'.format(max_)) | |
else: | |
return value | |
# --- Command line interface | |
def cli(): | |
"""Parse arguments and call appropriate functions.""" | |
parser = argparse.ArgumentParser(description=__doc__) | |
parser.add_argument( | |
'movies', | |
help='the list of movies to find overlapping actors for', | |
nargs='*', | |
) | |
parser.add_argument( | |
'-i', '--interactive', | |
action='store_true', | |
help='enter interactive mode to input movie searches', | |
) | |
parser.add_argument( | |
'-q', '--quiet', | |
action='store_true', | |
help='suppress interaction and accept the first search result', | |
) | |
args = parser.parse_args() | |
movies = [search_for_movie(arg, not args.quiet) for arg in args.movies] | |
if args.interactive: | |
movies.extend( | |
input_multiple_movies(None, len(args.movies) + 1, not args.quiet) | |
) | |
print_input(movies) | |
print_output(overlapping_actors(movies)) | |
if __name__ == '__main__': | |
cli() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment