# Script for averaging the thumbnails of all the videos uploaded by a YouTube
# channel with the YouTube Data API.
# This script can also be used on phones.
#
# --- How to use it ---
#
# Part 1: Getting an API key
#
# 1. Go to https://console.cloud.google.com/ and sign in to your
# Google account.
# 2. Click the "Select a project" button at the top, on phones that.
# button can be found by pressing the hamburger button on the left.
# 3. Click "New project", on phones that button can be found by pressing the
# three dots on the right.
# 4. Type a name for your project, skip the rest of the page and click
# "Create".
# 5. Click the "Select a project" button again and click on the link for.
# your project.
# 6. Using the search bar (or button) at the to,p search for
# "YouTube Data API v3" and click on the top result.
# 7. Press the enable button.
# 8. Click on the "Credentials" tab on the left, on phones, it's in the
# "APIs and services" menu on the top.
# 9. Click the "Create credentials" menu on the top (press the three dots
# first on a phone) and select "API key".
# 10. Type a name for your API key and scroll down to the bottom and press
# "Create" (ignore all the other options).
# 11. Click the copy button.
# 12. Type the API key somewhere (it a text file or your notes app)
#
# Part 2: Setting and using up the script
#
# First, download this file using the "download" button
# From here, it depends on what device you are using,
# Windows is the easiest to use, but this script will also work on Andriod.
# Scroll down to find the operating system you are using.
#
# Method 1: Using on Windows
# 1. Go to "https://www.python.org/downloads/windows/", scroll down and click
# on "Windows installer (64-bit)"
# 2. Open the installer file and click "Install now", after it installs click
# on "Disable path length limit" if you have admin privileges.
# 3. Using the start menu, search and open the "Command prompt".
# 4. Type "python -m pip install -U --upgrade pip", then type
# "pip install -U numpy pillow requests" to install all the necessary
# software to run the script.
# 5. Move the script to a new folder.
# 6. Double click on the script to run it.
#
# Method 2: Using on Android
# 1. Install Termux from the Play Store (or wherever you get your apps).
# 2. While it's installing, go to the downloads folder in your file manager
# and create a new folder inside it.
# 3. Find the script you downloaded and move it into that folder, and rename
# it.
# 4. When the Termux app finishes installing, open it. You should see
# a prompt that says "~ $", everything before the "$" is what folder
# you're in, "~" is the home folder, the prompt also means it's
# ready to recieve commands.
# 5. Type the command "pkg update", and press enter (do it after every command)
# and once it finishes type "pkg upgrade" and your Termux install will be
# up to date.
# 6. Run the commands "pkg install python python-pip python-numpy python-pillow"
# and after that "pip install requests" to install all the things
# required to run the script.
# 7. Run the command "termux-setup-storage" to allow the app to acces the
# script and store the images it creates.
# 8. Type "cd '~/storage/downloads/[name of the folder that you created]'"
# (with 'these' quotes included) to change to the folder with the script
# inside it.
# 9. Finally, run the command "python '[name of the script file].py'"
# (also with 'these' quotes included) to run the script.
#
# To cancel the script before it's finished, press the "ctrl" and the Z key.
# To run the script again, press the up arrow and press enter again.
# On a new terminal session (if you're back to the "~ $" prompt), press the
# up arrow to run the commands from step 8 and 9 again.
from io import BytesIO
from requests import get
from PIL import Image
from numpy import asarray, uint8, zeros, ndarray, pow, float32, where, less
from pathlib import Path
thumbnail_resolution = (1280, 720) # Resolution of the thumbnail
max_thumbnails = 0 # Number of thumbnails averaged, 0 for no limit
rescaling_function = Image.BICUBIC # Method used to convert resolution
def get_api_key(root_directory: Path) -> str:
try:
with open(root_directory.joinpath("api_key.txt"), encoding="utf-8") as f:
api_key = f.read().rstrip("\r\n")
except FileNotFoundError:
api_key = input("No api_key.txt file found, please type key: ")
with open(root_directory.joinpath("api_key.txt"), mode="w", encoding="utf-8") as f:
f.write(api_key + "\n")
return api_key
def get_full_playlist(api_key: str, playlist_id: str, part: str) -> list:
max_items = 50
url = "https://www.googleapis.com/youtube/v3/playlistItems"
payload = {"key": api_key, "part": part, "playlistId": playlist_id, "maxResults": max_items}
response = get(url, params=payload)
json = response.json()
items = json["items"]
totalCount = json["pageInfo"]["totalResults"]
while len(items) < totalCount:
payload = {"key": api_key, "part": part, "pageToken": json["nextPageToken"], "playlistId": playlist_id, "maxResults": max_items}
response = get(url, params=payload)
json = response.json()
items += json["items"]
return items
def get_best_thumbnail(thumbnails: "dict[str, dict]") -> "dict":
return thumbnails.get("maxres") or \
thumbnails.get("standard") or \
thumbnails.get("high") or \
thumbnails.get("medium") or \
thumbnails.get("default")
def gamma_correct(array: ndarray, inverse=False) -> ndarray:
if inverse:
return where(less(array, 0.04045), array / 12.92, pow((array + 0.055) / 1.055, 2.4))
else:
return where(less(array, 0.0031308), array * 12.92, 1.055 * pow(array, 1 / 2.4) - 0.055)
def download_images(playlist: "list[dict]") -> ndarray:
summed_array = zeros(tuple(reversed(thumbnail_resolution)) + (3,), dtype=float32)
for index, video in enumerate(playlist):
url = get_best_thumbnail(video["snippet"]["thumbnails"])["url"]
response = get(url)
image = Image.open(BytesIO(response.content))
if image.size != thumbnail_resolution:
suffix = ", resized from " + str(image.size[0]) + "x" + str(image.size[1])
image = image.resize(thumbnail_resolution, Image.BICUBIC)
else:
suffix = ""
array = asarray(image).astype(float32) / 255
summed_array += gamma_correct(array, inverse=True)
print(
"\x1b[1G\x1b[2K" + \
"Downloading images " + \
str(index + 1) + \
"/" + str(len(playlist)) + \
suffix,
end="", flush=True
)
return summed_array
def main():
root_directory = Path(__file__).parent.absolute()
api_key = get_api_key(root_directory)
handle = input("Enter channel handle: ")
payload = {"key": api_key, "part": "snippet,contentDetails", "forHandle": handle}
response = get("https://www.googleapis.com/youtube/v3/channels", params=payload)
channel = response.json()["items"][0]
print("Retrieving thumbnails for " + channel["snippet"]["title"])
playlist = get_full_playlist(api_key, channel["contentDetails"]["relatedPlaylists"]["uploads"], "snippet")
summed_array = download_images(playlist)
filename = "average-thumbnail-" + handle.lower() + ".png"
print("\nFinished, saving image as " + filename)
average_array = (gamma_correct(summed_array / len(playlist)) * 255).astype(uint8)
image = Image.fromarray(average_array, "RGB")
image.save(str(root_directory.joinpath(filename)))
if __name__ == "__main__":
main()