import configuration import datetime import json import os import re import traceback import yt_dlp import urllib.error from tools.converters import * from tools.extractors import extract_yt_initial_data, extract_yt_initial_player_response, deep_get import tools.files as files from math import floor from urllib.parse import parse_qs, urlparse, urlencode from cachetools import TTLCache video_cache = TTLCache(maxsize=50, ttl=300) ytdl_opts = { "quiet": True, "dump_single_json": True, "playlist_items": "1-100", "extract_flat": "in_playlist", "write_pages": True, "source_address": "0.0.0.0", "writesubtitles": True, "allsubtitles": True, } ytdl = yt_dlp.YoutubeDL(ytdl_opts) def format_order(format): # most significant to least significant # key, max, order, transform # asc: lower number comes first, desc: higher number comes first spec = [ ["second__height", 8000, "desc", lambda x: floor(x/96) if x else 0], ["fps", 100, "desc", lambda x: floor(x/10) if x else 0], ["type", " "*60, "asc", lambda x: len(x)], ] total = 0 for i in range(len(spec)): s = spec[i] diff = s[3](format[s[0]]) if s[2] == "asc": diff = s[3](s[1]) - diff total += diff if i+1 < len(spec): s2 = spec[i+1] total *= s2[3](s2[1]) return -total def extract_video(id): if id in video_cache: return video_cache[id] result = None try: info = ytdl.extract_info(id, download=False) year = int(info["upload_date"][:4]) month = int(info["upload_date"][4:6]) day = int(info["upload_date"][6:8]) published = int(datetime.datetime(year, month, day).timestamp()) result = { "type": "video", "title": info["title"], "videoId": info["id"], "videoThumbnails": generate_video_thumbnails(info["id"]), "storyboards": None, "description": info["description"], "descriptionHtml": add_html_links(escape_html_textcontent(info["description"])), "published": published, "publishedText": None, "keywords": None, "viewCount": info["view_count"], "second__viewCountText": None, "second__viewCountTextShort": None, "likeCount": 0, "dislikeCount": 0, "paid": None, "premium": None, "isFamilyFriendly": None, "allowedRegions": [], "genre": None, "genreUrl": None, "author": info["uploader"], "authorId": info["channel_id"], "authorUrl": info["channel_url"], "second__uploaderId": info["uploader_id"], "second__uploaderUrl": info["uploader_url"], "authorThumbnails": [], "subCountText": None, "lengthSeconds": info["duration"], "allowRatings": None, "rating": info["average_rating"], "isListed": None, "liveNow": None, "isUpcoming": None, "dashUrl": "{}/api/manifest/dash/id/{}".format(configuration.website_origin, info["id"]), "second__providedDashUrl": None, "adaptiveFormats": [], "formatStreams": [], "captions": [], "recommendedVideos": [] } # result = info["formats"] # return result for format in info["formats"]: # m3u8 playlists cannot be played. if "m3u8" in format["protocol"]: continue # Storyboard images are now included in formats, we don't want them. # Storyboards have neither audio nor video, so detect them that way. # They could also be detected with ("storyboard" in format_note), or with (protocol == "mhtml"). if format["acodec"] == "none" and format["vcodec"] == "none": continue # Adaptive formats have either audio or video, format streams have both, storyboard images have neither. is_adaptive = format["acodec"] == "none" or format["vcodec"] == "none" sense = "video" if format["vcodec"] != "none" else "audio" mime = sense + "/" + format["ext"] codecs = [] if format["vcodec"] != "none": codecs.append(format["vcodec"]) if format["acodec"] != "none": codecs.append(format["acodec"]) result_type = '{}; codecs="{}"'.format(mime, ", ".join(codecs)) if is_adaptive: url = "" if "fragment_base_url" in format: # this is http dash, which is annoying and doesn't work in