Skip to content

Instantly share code, notes, and snippets.

@textbook
Last active February 19, 2021 23:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save textbook/a453735203917ffdd908 to your computer and use it in GitHub Desktop.
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.
"""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