Last active
February 5, 2025 16:30
-
-
Save larsenv/432ffbd58978df07dd2e43089a12f8db to your computer and use it in GitHub Desktop.
PBS Kids Video Downloader
This file contains hidden or 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
import json | |
import requests | |
import subprocess | |
import re | |
home = requests.get("https://content.services.pbskids.org/v2/kidspbsorg/home/").text | |
home = json.loads(home) | |
shows = {} | |
episodes = {} | |
print("==========PBS Kids Show Downloader by Larsenv==========\n") | |
def title(j, k, data): | |
i = 0 | |
for f in data: | |
i += 1 | |
number = str(j) + str(i).zfill(2) | |
number = number.zfill(3) | |
if k == 0: | |
shows[number] = f | |
elif k == 1: | |
episodes[number] = f | |
print(number + ". " + f["title"]) | |
print("Show List:") | |
print("\nWeekly Promo:\n") | |
promo = home["collections"]["kids-promo"] | |
shows["000"] = promo | |
print("000. " + promo["title"]) | |
print("\nSpotlight:\n") | |
spotlight = home["collections"]["kids-show-spotlight"]["content"] | |
shows["001"] = spotlight[0] | |
print("001. " + spotlight[0]["title"]) | |
for i in range(1, 4): | |
print("\nTier {}:\n".format(i)) | |
title(i, 0, home["collections"]["kids-programs-tier-{}".format(i)]["content"]) | |
selection = input("\nPlease enter the number of the show you want to download: ") | |
url = shows[selection]["URI"].replace("pbs.org", "pbskids.org") | |
showhome = json.loads(requests.get(url).text) | |
print("\nEpisode and Clips List:") | |
print("\nClips:\n") | |
title("", 1, showhome["collections"]["clips"]["content"]) | |
print("\nEpisodes:\n") | |
title(2, 1, showhome["collections"]["episodes"]["content"]) | |
print("\nPromoted Content:\n") | |
title(3, 1, showhome["collections"]["promoted_content"]["content"]) | |
print("\nWould you like to download a single episode/clip or all episodes/clips?\n") | |
print("1. Single episode/clip") | |
print("2. All clips") | |
print("3. All episodes") | |
print("4. All promoted content") | |
choice = input("\nPlease enter your choice: ") | |
if choice == "1": | |
selection2 = input("\nPlease enter the number of the episode or clip you want to download: ") | |
selections_to_download = [selection2] | |
elif choice == "2": | |
selections_to_download = [key for key in episodes.keys() if key.startswith('1')] | |
elif choice == "3": | |
selections_to_download = [key for key in episodes.keys() if key.startswith('2')] | |
elif choice == "4": | |
selections_to_download = [key for key in episodes.keys() if key.startswith('3')] | |
else: | |
print("Invalid choice. Exiting.") | |
exit() | |
for selection2 in selections_to_download: | |
print(f"\nDownloading {episodes[selection2]['title']}...") | |
# Follow the redirect to get the final episode URI | |
redirected_uri = requests.get(episodes[selection2]["URI"], allow_redirects=True).url | |
# Modify the episode URI to remove the resolution part | |
episode_uri = re.sub(r'-\d+p_', '_', redirected_uri) | |
# Fetch subtitles from 'closedCaptionsMultiLanguage' and pick the most compatible type | |
preferred_formats = ["WebVTT", "SRT", "DFXP", "Caption-SAMI"] | |
subtitle_languages = {} | |
for cc in episodes[selection2]["closedCaptionsMultiLanguage"]: | |
lang = cc["language"] | |
if lang not in subtitle_languages: | |
subtitle_languages[lang] = cc | |
else: | |
current_format = cc["format"] | |
existing_format = subtitle_languages[lang]["format"] | |
if preferred_formats.index(current_format) < preferred_formats.index(existing_format): | |
subtitle_languages[lang] = cc | |
# Collect the chosen subtitle URIs | |
all_subtitles = [subtitle_languages[lang]["URI"] for lang in subtitle_languages] | |
filename = (shows[selection]["title"] + " - " + episodes[selection2]["title"] + ".mkv").replace("/", " - ") | |
# Prepare FFmpeg command | |
ffmpeg_command = ["ffmpeg", "-i", episode_uri] | |
# Add each subtitle stream to the command as input | |
for vtt in all_subtitles: | |
ffmpeg_command.extend(["-i", vtt]) | |
# Map video, audio, and all subtitles | |
ffmpeg_command.extend(["-map", "p:0"]) | |
for i in range(len(all_subtitles)): | |
ffmpeg_command.extend(["-map", str(i + 1)]) | |
# Set codec options | |
ffmpeg_command.extend(["-c", "copy"]) | |
# Add metadata for language and title after all inputs | |
for i, (lang, cc) in enumerate(subtitle_languages.items()): | |
ffmpeg_command.extend([f"-metadata:s:s:{i}", f"language={lang}"]) | |
subtitle_name = cc.get("name", lang) | |
ffmpeg_command.extend([f"-metadata:s:s:{i}", f"title={subtitle_name}"]) | |
# Add output filename | |
ffmpeg_command.append(filename) | |
# Execute FFmpeg command | |
subprocess.call(ffmpeg_command) | |
print(f"\n{episodes[selection2]['title']} downloaded successfully!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment