7 Cinema Pre-show Generator¶
Hi everyone! In this chapter, we will learn how to create a cinema pre-show generator. What exactly is a cinema pre-show? Have you ever observed the advertisements, public service messages, and movie trailers which run before the actual movie in a cinema? Well, all of that comes under pre-show.
I came up with the idea for this project during a movie night with a group of my friends. We love watching movies in our dorm and we love talking about upcoming movies. The only problem is that we have to actively go out and search for new movie trailers. If we go to a cinema, we skip that part because the cinema automatically shows us trailers for upcoming movies. I wanted to replicate the same environment during our cozy movie nights. What if before the start of a movie during our private screening we can play trailers for upcoming movies that have the same genre as the movie we are currently starting?
Perfect, time to work on a delicious new project and improve our programming skills!
Normally, cinema folks use video editing software to stitch together multiple videos/trailers to generate that pre-show. But we are programmers! Surely we can do better than that?
Our project will be able to generate an automatic pre-show consisting of 3 (or more) trailers for upcoming flicks related to the current one we are going to watch. It will also add in the “put your phones on silent mode” message (have you been bothered by phones ringing during a movie? Me too…) and the pre-movie countdown timer (the timer ticks give me goosebumps).
The script side of the final product of this chapter will look something like Fig. 7.1.
7.1 Setting up the project¶
We will be using the following libraries:
Start by creating a “pre_show” folder for this project. Then, create a virtual environment:
$ python -m venv env $ source env/bin/activate
Now let’s install the required libraries using
$ touch requirements.txt $ echo "tmdbsimple\ngoogle\nmoviepy" > requirements.txt $ pip install -r requirements.txt
moviepy has extra dependencies which you might need to install as well (if the PIP installation fails). You can find the installation instructions for
Now create an
app.py file inside the
pre_show folder and import the following modules:
from moviepy.editor import (VideoFileClip, concatenate_videoclips) import tmdbsimple as tmdb from googlesearch import search
7.2 Sourcing video resources¶
Now we need to source our videos from somewhere. We will be downloading the movie trailers automatically but we still need to download the rest of the videos manually. The rest of the videos include the countdown and the “put your phones on silent” video. I will be using this free countdown video and this free “turn your cell phones off” video. Download both of these videos before you move on. The download instructions are in the video descriptions.
If for some reason these videos aren’t available, any other video will work fine as well. Just make sure that you update the script later on to reference these new videos.
7.3 Getting movie information¶
The next step is to figure out the genre of the movie that we are planning on watching. This way we can play only those upcoming movie trailers which belong to the same genre. We will be using The Movie DB for this. Before we move on, please go to tmdb, create an account and signup for an API key. It’s completely free, so don’t worry about spending a dime on this. TMDB change their website frequently so chances are that you might need to follow slightly different steps to get an API key than the steps I show below. This should not be a huge issue as the new navigation would still be fairly intuitive.
Now we can search for a movie on tmdb by using the following Python code (Replace
"YOUR API KEY" with your actual API key):
tmdb.API_KEY = "YOUR API KEY" search = tmdb.Search() response = search.movie(query="The Prestige") print(search.results['title'])
Save this code in an
app.py file. We will be making changes to this file throughout this tutorial.
Now run this code with this command:
$ python app.py
This code simply creates a
tmdb.Search() object and searches for a movie by using the
movie() method of the
tmdb.Search() object. The result is a list containing Python dictionaries. We extract the first element (movie dictionary) from the list and print value associated with the key
tmdb also makes it super easy to search for upcoming movies:
upcoming = tmdb.Movies() response = upcoming.upcoming() for movie in response['results']: print(movie['title'])
This code is also similar to the previous one. It creates a
tmdb.Movies() object and then prints the titles of the upcoming movies. When I am personally working with a JSON API, I love exploring the full response of an API call. My favourite way to do that is to copy the complete JSON output of a function call and pasting that on JSBeautifier Fig. 7.5. The auto-indentation makes it super easy to get a general feel of the data one is working with.
Almost every movie has multiple genres. “The Prestige” has three:
The numbers before the genre names are just internal IDs TMDB uses for each genre.
Let’s filter these upcoming movies based on genres. As we already know that most movies have multiple genres, we need to decide which genre we will be using to filter out the upcoming movies. It is a bit rare for two movies to share the exact same list of genres so we can not simply compare this whole list in its entirety. I personally decided to compare only the first returned genre which in this case is “drama”. This is how we can filter the upcoming movies list:
for movie in response['results']: if search.results['genre_ids'] in movie['genre_ids']: print(movie['title'])
The above code produced the following output for me:
Sicario: Day of the Soldado
The Yellow Birds
We can also make the genre selection more interesting by randomly choosing a genre:
from random import choice for movie in response['results']: if choice(search.results['genre_ids']) in movie['genre_ids']: print(movie['title'])
choice(list) randomly picks a value from a list.
Cool! now we can search for the trailers for the first three movies.
7.4 Downloading the trailers¶
Apple stores high definition trailers for all upcoming movies but does not provide an API to programmatically query its database and download the trailers. We need a creative solution. I searched around and found out that all of the trailers are stored on the
trailers.apple.com domain. Can we somehow use this information to search for trailers on that domain? The answer is a resounding yes! We need to reach out to our friend Google and use something called Google Dorks.
According to WhatIs.com:
A Google dork query, sometimes just referred to as a dork, is a search string that uses advanced search operators to find information that is not readily available on a website.
In plain words, a Google Dork allows us to limit our search based on specific parameters. For instance, we can use Google to search for some query string on a specific website. This will make sure Google does not return results containing any other website which might contain that string.
The dork which we will be using today is
site:trailers.apple.com <movie name> (replace movie name with actual movie name).
Try doing a search for The Incredibles on Google with that dork. The output should be similar to Fig. 7.6.
Congrats! We are one step closer to our final goal. Now we need to figure out two things. First, how to automate Google searches, and second, how to download trailers from Apple. The first problem can be solved by this library and the second problem can be solved by this one. Aren’t you glad that Python has a library for almost everything?
We have already installed the
apple_trailer_downloader because we can’t install it using
pip. What we have to do is that we have to save this file in our current
Now, let’s run the same Google dork using
from googlesearch import search for url in search('site:trailers.apple.com The Incredibles 2', stop=10): print(url)
The current code is going to give us 10 results. You can change the number of results returned by changing the
stop argument. The output should resemble this:
https://trailers.apple.com/trailers/disney/incredibles-2/ https://trailers.apple.com/trailers/disney/the_incredibles/ https://trailers.apple.com/ca/disney/incredibles-2/ https://trailers.apple.com/trailers/disney/the_incredibles/trailer2_small.html https://trailers.apple.com/trailers/genres/family/ https://trailers.apple.com/ https://trailers.apple.com/ca/disney/?sort=title_1 https://trailers.apple.com/trailers/disney/ https://trailers.apple.com/ca/genres/family/ https://trailers.apple.com/trailers/genres/family/index_abc5.html https://trailers.apple.com/ca/ https://trailers.apple.com/trailers/genres/comedy/?page=2
Amazing! The first URL is exactly the one we are looking for. At this point, I ran this command with a bunch of different movie names just to confirm that the first result is always the one we are looking for.
Now, let’s use
apple_trailer_downloader to download the trailer from that first URL. Instead of getting the URL from the
search method, I am going to hardcode a URL and use that as a basis to work on the download feature. This is super helpful because you reduce the dynamic nature of your code. If the download part isn’t working fine you don’t have to go back and test the search part as well.
Once we are fairly confident that the download part is working as expected, we can integrate both of these parts together. Let’s go ahead and write down the download part of the code and test it:
import os from download_trailers import (get_trailer_file_urls, download_trailer_file, get_trailer_filename) page_url = "https://trailers.apple.com/trailers/disney/incredibles-2/" destdir = os.getcwd() trailer_url = get_trailer_file_urls(page_url, "720", "single_trailer", ) trailer_file_name = get_trailer_filename( trailer_url['title'], trailer_url['type'], trailer_url['res'] ) if not os.path.exists(trailer_file_name): download_trailer_file(trailer_url['url'], destdir, trailer_file_name)
If everything is correctly set-up, this should download the trailer for “Incredibles 2” in your current project folder with the name of
The important parts in this code are
os.getcwd() stands for “get current working directory”. It is the directory from which you are running the code. If you are running the code from your project folder, it will return the path of your project folder.
os.path.exists(trailer_file_name) checks if there is a path that exists on the system or not. This essentially helps us check if there is a trailer file with the same name downloaded before or not. If there is a file with the same name downloaded in the current directory, it will skip the download. Hence, running the code second time should not do anything.
Now let’s take a look at merging these videos/trailers using
7.5 Merging trailers together¶
Moviepy makes merging videos extremely easy. The code required to merge two trailers with the names:
Incredibles 2.Trailer.720p.mov and
Woman Walks Ahead.Trailer.720p.mov is:
from moviepy.editor import (VideoFileClip, concatenate_videoclips) clip1 = VideoFileClip('Woman Walks Ahead.Trailer.720p.mov') clip2 = VideoFileClip('Incredibles 2.Trailer.720p.mov') final_clip = concatenate_videoclips([clip1, clip2]) final_clip.write_videofile("combined trailers.mp4")
Firstly, we create
VideoFileClip() objects for each video file. Then we use the
concatenate_videoclips() function to merge the two clips and finally we use the
write_videofile() method to save the merged clip in a
combined trailers.mp4 file. This will also convert the file type from
At this point your project folder should have the following files:
$ ls pre_show Woman Walks Ahead.Trailer.720p.mov Incredibles 2.Trailer.720p.mov turn-off.mkv countdown.mp4 venv requirements.txt app.py
Let’s go ahead and also merge in our “turn your cell phones off” video and the countdown video:
from moviepy.editor import ( VideoFileClip, concatenate_videoclips ) clip1 = VideoFileClip('Woman Walks Ahead.Trailer.720p.mov') clip2 = VideoFileClip('Incredibles 2.Trailer.720p.mov') clip3 = VideoFileClip('turn-off.mkv') clip4 = VideoFileClip('countdown.mp4') final_clip = concatenate_videoclips([clip1, clip2, clip3, clip4]) final_clip.write_videofile("combined trailers.mp4")
The output from the generated video will look something like Fig. 7.7.
This certainly doesn’t seem right. None of our sources contained a grainy video like this. The output
.mp4 file was also corrupted from near the end.
This issue had me pulling out my hair for a whole day. I searched around almost everywhere but couldn’t find any solution. Finally, I found a forum post somewhere where someone else was having the same problem. His issue was resolved by passing in the
method='compose' keyword argument to the
concatenate_videoclips function. It was such a simple fix that I felt super stupid and wanted to bang my head against the wall one more time.
This argument is required in this case because our separate video files are of different dimensions. The trailers are
1280x544 whereas the countdown video and the “turn your cell phones off” video is
1920x1080. Official docs have a proper explanation of this argument:
``method=”compose”``: if the clips do not have the same resolution, the final resolution will be such that no clip has to be resized. As a consequence, the final clip has the height of the highest clip and the width of the widest clip of the list. All the clips with smaller dimensions will appear centered. The border will be transparent if mask=True, else it will be of the color specified by
We can use it like this:
final_clip = concatenate_videoclips([clip1, clip2, clip3, clip4], method="compose")
This is because moviepy by default tries to preserve the biggest width and height from all the clips. What I ended up doing was that I reduced the size of my two bigger clips by 40% and then I merged all of the videos together. It resulted in something similar to Fig. 7.10 and Fig. 7.11:
The code required for doing that is this:
from moviepy.editor import (VideoFileClip, concatenate_videoclips) clip1 = VideoFileClip('Woman Walks Ahead.Trailer.720p.mov') clip2 = VideoFileClip('Incredibles 2.Trailer.720p.mov') clip3 = VideoFileClip('turn-off.mkv').resize(0.60) clip4 = VideoFileClip('countdown.mp4').resize(0.60) final_clip = concatenate_videoclips([clip1, clip2, clip3, clip4], method="compose") final_clip.write_videofile("combined trailers.mp4")
I later found out that we can pass in a video URL to
VideoFileClip as well. This way we will not have to download videos using
download_trailers.py file and
moviepy will take care of downloading automatically. It is done like this:
clip1 = VideoFileClip(trailer['url'])
Before we move on we should combine our code uptil now.
7.6 Final Code¶
This is what we have so far:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
import os import sys from moviepy.editor import (VideoFileClip, concatenate_videoclips) import tmdbsimple as tmdb from googlesearch import search as googlesearch from download_trailers import get_trailer_file_urls tmdb.API_KEY = "YOUR API KEY" query = sys.argv[-1] print("[Pre-show Generator] Movie:", query) search = tmdb.Search() response = search.movie(query=query) upcoming = tmdb.Movies() response = upcoming.upcoming() similar_movies =  for movie in response['results']: if search.results['genre_ids'] in movie['genre_ids']: similar_movies.append(movie) print('[Pre-show Generator] Which movies seem interesting?\ Type the indexes like this: 3,4,6 \n') for c, movie in enumerate(similar_movies): print(c+1, ".", movie['title']) select_movies = input('[Pre-show Generator] Ans: ') select_movies = [int(index)-1 for index in select_movies.split(',')] final_movie_list = [similar_movies[index] for index in select_movies] print('[Pre-show Generator] Searching trailers') trailer_urls =  for movie in final_movie_list: for url in googlesearch('site:trailers.apple.com ' + movie['title'], stop=10): break trailer = get_trailer_file_urls(url, "720", "single_trailer", ) trailer_urls.append(trailer['url']) print('[Pre-show Generator] Combining trailers') trailer_clips = [VideoFileClip(url) for url in trailer_urls] trailer_clips.append(VideoFileClip('turn-off.mp4').resize(0.60)) trailer_clips.append(VideoFileClip('countdown.mp4').resize(0.60)) final_clip = concatenate_videoclips(trailer_clips, method="compose") final_clip.write_videofile("combined trailers.mp4")
I made the code a bit more user friendly by adding in helpful
1,3,4 which I then split by using the
The user-provided indexes are not the same indexes for the movies in
similar_movies list so I convert the user-supplied index into an integer and subtract one from it. Then I extract the selected movies and put them in the
The rest of the code is pretty straightforward. I made excessive use of list comprehensions as well. For instance:
trailer_clips = [VideoFileClip(url) for url in trailer_urls]
List comprehensions should be second nature for you by now. Just in case, these are nothing more than a compact way to write
for loops and store the result in a list. The above code can also be written like this:
trailer_clips =  for url in trailer_urls: trailer_clips.append(VideoFileClip(url))
Get into the habit of using list comprehensions. They are Pythonic and make your code more readable in most while reducing the code size at the same time.
Don’t use too deeply nested list comprehensions, because that would just make your code ugly. Any piece of code is usually read more times than it is written. Sacrificing some screen space for more readability is a useful tradeoff in the long-term.
Save this code in the
app.py file and run it like this:
$ python app.py "The Prestige"
Replace “The Prestige” with any other movie name (The
" is important). The output should be similar to this:
[Pre-show Generator] Movie: The Prestige [Pre-show Generator] Which movies seem interesting? Type the indexes like this: 3,4,6 1 . Sicario: Day of the Soldado 2 . Terminal 3 . Hereditary 4 . Beirut 5 . Loving Pablo 6 . 12 Strong 7 . Skyscraper [Pre-show Generator] Ans:
At this point you need to pass in the index of movies which you are interested in:
[Pre-show Generator] Ans: 1,4
The rest of the output should be similar to Fig. 7.12.
You may have a couple of scenarios which will require some creativity to solve. You could end up in a situation where the
download_trailers library doesn’t work anymore. In that case either go ahead and figure out a way to scrape the .mov links from the trailers website or search for a new libray on GitHub which does work. You can also look for new sources for sourcing the trailers.
Another possibility would be to end up in a situation that googlesearch stops working. Chances are that either you ran the script too much that Google thinks you are a bot and has started returning captchas or that Google has tweaked their website slightly which requires some update to
googlesearch. In case its the former scenario, you can use some other search engine and figure out how to do targeted searching. For the latter, search GitHub for a google search related library which has recently been updated.
You can also encounter some bugs in moviepy. For instance, when I was editing this chapter I ran my script again and got this error:
AttributeError: 'NoneType' object has no attribute 'stdout'
This was resolved by downgrading my moviepy version. I found the solution by searching on Google and reading this issue on GitHub.
7.8 Next Steps¶
If you have the same output your script is working perfectly. Now you can extend this script in multiple ways.
Currently, I am downloading the trailers in 720p quality. Try downloading them in the 1080p quality. You might have to modify the input to the
You can also use
pyautogui such that your Python file will automatically run the combined trailer using
vlc and once the trailer is finished it will press
Alt + Tab (win/linux) or
Command + Tab (Mac) using
pyautogui to switch to the second “movie” window (I am thinking about Netflix running in a browser window) and start playing the movie automatically.
One other improvement can be to make use of click and automate the whole process by passing in all the arguments at run time. This way you will not have to wait for the app to return the movie names for you to choose from. It will automatically choose the indexes which you specify during run-time. This will also introduce the element of surprise because you will have no idea which trailers will be downloaded!
I am thinking of something like this:
$ python app.py "The Prestige" --indexes 3,4,7
This will search for similar movies to prestige and automatically download the movies which have the index of 3, 4, and 7.
Go ahead and try out these modifications. If you get stuck just shoot me an email and I would be more than happy to help.
See you in the next chapter!