# 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()