diff --git a/ythdd_inv_tl.py b/ythdd_inv_tl.py index c23976c..7370bf6 100644 --- a/ythdd_inv_tl.py +++ b/ythdd_inv_tl.py @@ -85,22 +85,44 @@ def safeTraverse(obj: dict, path: list, default=None): result = result[x] except KeyError: result = default + print(f"error reading: {' -> '.join(path)} - returning: {default}") finally: return result +def getError(idata: dict): + unknown_error = {"status": "Unknown error", "reason": "This is a generic ythdd error."} + error = "" + + try: + playabilityStatus = safeTraverse(idata, ['stage1', 'playabilityStatus'], default=unknown_error) + print(playabilityStatus) + error += f"({playabilityStatus['status']}) {playabilityStatus['reason']}" + except: + error += f"Generic error" + + try: + errorScreen = safeTraverse(idata, ['stage1', 'playabilityStatus', 'errorScreen', 'playerErrorMessageRenderer', 'subreason', 'runs'], default=[]) + error += " - " + for x in errorScreen: + error += f"{x['text']} " + except: + pass + + return error + def genThumbs(videoId: str): result = [] thumbnails = [ #{'height': 720, 'width': 1280, 'quality': "maxres", 'url': "maxres"}, # for the time being omit the buggy maxres quality - {'height': 720, 'width': 1280, 'quality': "maxresdefault", 'url': "maxresdefault"}, - {'height': 480, 'width': 640, 'quality': "sddefault", 'url': "sddefault"}, - {'height': 360, 'width': 480, 'quality': "high", 'url': "hqdefault"}, - {'height': 180, 'width': 320, 'quality': "medium", 'url': "mqdefault"}, - {'height': 90, 'width': 120, 'quality': "default", 'url': "default"}, - {'height': 90, 'width': 120, 'quality': "start", 'url': "1"}, - {'height': 90, 'width': 120, 'quality': "middle", 'url': "2"}, - {'height': 90, 'width': 120, 'quality': "end", 'url': "3"}, + {'height': 720, 'width': 1280, 'quality': "maxresdefault", 'url': "maxresdefault"}, + {'height': 480, 'width': 640, 'quality': "sddefault", 'url': "sddefault"}, + {'height': 360, 'width': 480, 'quality': "high", 'url': "hqdefault"}, + {'height': 180, 'width': 320, 'quality': "medium", 'url': "mqdefault"}, + {'height': 90, 'width': 120, 'quality': "default", 'url': "default"}, + {'height': 90, 'width': 120, 'quality': "start", 'url': "1"}, + {'height': 90, 'width': 120, 'quality': "middle", 'url': "2"}, + {'height': 90, 'width': 120, 'quality': "end", 'url': "3"}, ] for x in thumbnails: @@ -115,7 +137,8 @@ def genThumbs(videoId: str): def rebuildFormats(data): result = [{} for x in data] formatStreams = [] - best_bitrate = 0 + best_bitrate_video = 0 + best_bitrate_audio = -1 for x in range(len(data)): @@ -142,6 +165,11 @@ def rebuildFormats(data): except: pass + if "audio" == data[x]['mimeType'][:5]: + isAudio = 1 + else: + isAudio = 0 + if isVideo: result[x]['fps'] = str(data[x]['fps']) else: @@ -164,29 +192,46 @@ def rebuildFormats(data): pass # we assume here that a stream with the highest bitrate must be a video stream- that may not be the case - if data[x]['averageBitrate'] > data[best_bitrate]['averageBitrate'] and isVideo: - best_bitrate = x + if data[x]['averageBitrate'] > data[best_bitrate_video]['averageBitrate'] and isVideo: + best_bitrate_video = x + if data[x]['averageBitrate'] > data[best_bitrate_audio]['averageBitrate'] and isAudio: + best_bitrate_audio = x # makes FreeTube work, unfortunately it's a video-only stream formatStreams = [ { - "url": data[best_bitrate]['url'], - "itag": str(data[best_bitrate]['itag']), - "type": data[best_bitrate]['mimeType'], - "quality": data[best_bitrate]['quality'], - "bitrate": str(data[best_bitrate]['averageBitrate']), - "fps": data[best_bitrate]['fps'], + "url": data[best_bitrate_video]['url'], + "itag": str(data[best_bitrate_video]['itag']), + "type": data[best_bitrate_video]['mimeType'], + "quality": data[best_bitrate_video]['quality'], + "bitrate": str(data[best_bitrate_video]['averageBitrate']), + "fps": data[best_bitrate_video]['fps'], "size": "", # workaround for clipious, which requires ANYTHING to be passed, or else it will throw and error and won't load the video - "resolution": str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['height']) + "p", - "qualityLabel": str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['height']) + "p", - "container": invidious_formats.FORMATS[data[best_bitrate]['itag']]['ext'], - "encoding": invidious_formats.FORMATS[data[best_bitrate]['itag']]['vcodec'] + "resolution": str(invidious_formats.FORMATS[data[best_bitrate_video]['itag']]['height']) + "p", + "qualityLabel": str(invidious_formats.FORMATS[data[best_bitrate_video]['itag']]['height']) + "p", + "container": invidious_formats.FORMATS[data[best_bitrate_video]['itag']]['ext'], + "encoding": invidious_formats.FORMATS[data[best_bitrate_video]['itag']]['vcodec'] + }, + { + "audioChannels": data[best_bitrate_audio]['audioChannels'], + "init": result[best_bitrate_audio]['init'], + "index": result[best_bitrate_audio]['index'], + "bitrate": str(data[best_bitrate_audio]['averageBitrate']), + "url": data[best_bitrate_audio]['url'], + "itag": str(data[best_bitrate_audio]['itag']), + "type": data[best_bitrate_audio]['mimeType'], + "clen": result[best_bitrate_audio]['clen'], + "lmt": result[best_bitrate_audio]['lmt'], + "projectionType": result[best_bitrate_audio]['projectionType'], + "audioQuality": result[best_bitrate_audio]['audioQuality'], + "audioSampleRate": result[best_bitrate_audio]['audioSampleRate'], + "qualityLabel": "audio" } ] # not all itags have width and/or height try: - formatStreams["size"] = str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['width']) + "x" + str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['height']) + formatStreams[0]["size"] = str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['width']) + "x" + str(invidious_formats.FORMATS[data[best_bitrate]['itag']]['height']) except: pass @@ -201,6 +246,13 @@ def videos(data): #print("requesting idata from IOSextract") idata = ythdd_extractor.IOSextract(data[3]) + + hls_url = safeTraverse(idata, ['stage1', 'streamingData', 'hlsManifestUrl'], default="") + adaptive_formats = safeTraverse(idata, ['stage1', 'streamingData', 'adaptiveFormats'], default=[]) + if not hls_url: + print(f"serious error: couldn't get hls_url or adaptive_formats!\ndumping idata:\n{idata}") + return send(500, {'error': getError(idata)}) + wdata = ythdd_extractor.WEBextractSinglePage(data[3]) #return send(200, {'idata': idata, 'wdata': wdata}) @@ -303,9 +355,6 @@ def videos(data): # that is - without making another request author_verified = False - - hls_url = safeTraverse(idata, ['stage1', 'streamingData', 'hlsManifestUrl'], default="") - adaptive_formats = safeTraverse(idata, ['stage1', 'streamingData', 'adaptiveFormats'], default=[]) format_streams = [] adaptive_formats, format_streams = rebuildFormats(adaptive_formats)