musicdl 2.1.11__py3-none-any.whl → 2.7.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- musicdl/__init__.py +5 -5
- musicdl/modules/__init__.py +10 -3
- musicdl/modules/common/__init__.py +2 -0
- musicdl/modules/common/gdstudio.py +204 -0
- musicdl/modules/js/__init__.py +1 -0
- musicdl/modules/js/youtube/__init__.py +2 -0
- musicdl/modules/js/youtube/botguard.js +1 -0
- musicdl/modules/js/youtube/jsinterp.py +902 -0
- musicdl/modules/js/youtube/runner.js +2 -0
- musicdl/modules/sources/__init__.py +41 -10
- musicdl/modules/sources/apple.py +207 -0
- musicdl/modules/sources/base.py +256 -28
- musicdl/modules/sources/bilibili.py +118 -0
- musicdl/modules/sources/buguyy.py +148 -0
- musicdl/modules/sources/fangpi.py +153 -0
- musicdl/modules/sources/fivesing.py +108 -0
- musicdl/modules/sources/gequbao.py +148 -0
- musicdl/modules/sources/jamendo.py +108 -0
- musicdl/modules/sources/joox.py +104 -68
- musicdl/modules/sources/kugou.py +129 -76
- musicdl/modules/sources/kuwo.py +188 -68
- musicdl/modules/sources/lizhi.py +107 -0
- musicdl/modules/sources/migu.py +172 -66
- musicdl/modules/sources/mitu.py +140 -0
- musicdl/modules/sources/mp3juice.py +264 -0
- musicdl/modules/sources/netease.py +163 -115
- musicdl/modules/sources/qianqian.py +125 -77
- musicdl/modules/sources/qq.py +232 -94
- musicdl/modules/sources/tidal.py +342 -0
- musicdl/modules/sources/ximalaya.py +256 -0
- musicdl/modules/sources/yinyuedao.py +144 -0
- musicdl/modules/sources/youtube.py +238 -0
- musicdl/modules/utils/__init__.py +12 -4
- musicdl/modules/utils/appleutils.py +563 -0
- musicdl/modules/utils/data.py +107 -0
- musicdl/modules/utils/logger.py +211 -58
- musicdl/modules/utils/lyric.py +73 -0
- musicdl/modules/utils/misc.py +335 -23
- musicdl/modules/utils/modulebuilder.py +75 -0
- musicdl/modules/utils/neteaseutils.py +81 -0
- musicdl/modules/utils/qqutils.py +184 -0
- musicdl/modules/utils/quarkparser.py +105 -0
- musicdl/modules/utils/songinfoutils.py +54 -0
- musicdl/modules/utils/tidalutils.py +738 -0
- musicdl/modules/utils/youtubeutils.py +3606 -0
- musicdl/musicdl.py +184 -86
- musicdl-2.7.3.dist-info/LICENSE +203 -0
- musicdl-2.7.3.dist-info/METADATA +704 -0
- musicdl-2.7.3.dist-info/RECORD +53 -0
- {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/WHEEL +5 -5
- musicdl-2.7.3.dist-info/entry_points.txt +2 -0
- musicdl/modules/sources/baiduFlac.py +0 -69
- musicdl/modules/sources/xiami.py +0 -104
- musicdl/modules/utils/downloader.py +0 -80
- musicdl-2.1.11.dist-info/LICENSE +0 -22
- musicdl-2.1.11.dist-info/METADATA +0 -82
- musicdl-2.1.11.dist-info/RECORD +0 -24
- {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/top_level.txt +0 -0
- {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/zip-safe +0 -0
|
@@ -0,0 +1,3606 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Function:
|
|
3
|
+
Implementation of YouTubeMusicClient utils, refer to https://pytubefix.readthedocs.io/en/latest/index.html
|
|
4
|
+
Author:
|
|
5
|
+
Zhenchao Jin
|
|
6
|
+
WeChat Official Account (微信公众号):
|
|
7
|
+
Charles的皮卡丘
|
|
8
|
+
'''
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import ast
|
|
13
|
+
import math
|
|
14
|
+
import enum
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
import shutil
|
|
18
|
+
import struct
|
|
19
|
+
import base64
|
|
20
|
+
import socket
|
|
21
|
+
import pathlib
|
|
22
|
+
import subprocess
|
|
23
|
+
import http.client
|
|
24
|
+
import nodejs_wheel.executable
|
|
25
|
+
from enum import Enum
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from urllib import parse
|
|
28
|
+
from functools import lru_cache
|
|
29
|
+
from collections.abc import Sequence
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
from urllib.request import Request, urlopen
|
|
32
|
+
from urllib.error import HTTPError, URLError
|
|
33
|
+
from urllib.parse import parse_qs, urlencode, urlparse
|
|
34
|
+
from ..js.youtube import JSInterpreter, extractplayerjsglobalvar
|
|
35
|
+
from typing import Callable, List, Optional, Union, Callable, BinaryIO, Dict, Any, Tuple
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
'''API_KEYS'''
|
|
39
|
+
API_KEYS = [
|
|
40
|
+
'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'AIzaSyCtkvNIR1HCEwzsqK6JuE6KqpyjusIRI30', 'AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w', 'AIzaSyC8UYZpvA2eknNex0Pjid0_eTLJoDu6los',
|
|
41
|
+
'AIzaSyCjc_pVEDi4qsv5MtC2dMXzpIaDoRFLsxw', 'AIzaSyDHQ9ipnphqTzDqZsbtd8_Ru4_kiKVQe2k'
|
|
42
|
+
]
|
|
43
|
+
'''CLIENT DATA'''
|
|
44
|
+
CLIENT_ID = '861556708454-d6dlm3lh05idd8npek18k6be8ba3oc68.apps.googleusercontent.com'
|
|
45
|
+
CLIENT_SECRET = 'SboVhoG9s0rNafixCSGGKXAT'
|
|
46
|
+
DEFAULT_CLIENTS = {
|
|
47
|
+
'WEB': {
|
|
48
|
+
'innertube_context': {'context': {'client': {'clientName': 'WEB', 'osName': 'Windows', 'osVersion': '10.0', 'clientVersion': '2.20251021.01.00', 'platform': 'DESKTOP'}}},
|
|
49
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '1', 'X-Youtube-Client-Version': '2.20251021.01.00'},
|
|
50
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': True
|
|
51
|
+
},
|
|
52
|
+
'WEB_EMBED': {
|
|
53
|
+
'innertube_context': {'context': {'client': {'clientName': 'WEB_EMBEDDED_PLAYER', 'osName': 'Windows', 'osVersion': '10.0', 'clientVersion': '2.20240530.02.00', 'clientScreen': 'EMBED'}}},
|
|
54
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '56'},
|
|
55
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': True
|
|
56
|
+
},
|
|
57
|
+
'WEB_MUSIC': {
|
|
58
|
+
'innertube_context': {'context': {'client': {'clientName': 'WEB_REMIX', 'clientVersion': '1.20251013.03.00'}}},
|
|
59
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '67'},
|
|
60
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': True
|
|
61
|
+
},
|
|
62
|
+
'WEB_CREATOR': {
|
|
63
|
+
'innertube_context': {'context': {'client': {'clientName': 'WEB_CREATOR', 'clientVersion': '1.20220726.00.00'}}},
|
|
64
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '62'},
|
|
65
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': False
|
|
66
|
+
},
|
|
67
|
+
'WEB_SAFARI': {
|
|
68
|
+
'innertube_context': {'context': {'client': {'clientName': 'WEB', 'clientVersion': '2.20240726.00.00'}}},
|
|
69
|
+
'header': {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15,gzip(gfe)', 'X-Youtube-Client-Name': '1'},
|
|
70
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': True
|
|
71
|
+
},
|
|
72
|
+
'MWEB': {
|
|
73
|
+
'innertube_context': {'context': {'client': {'clientName': 'MWEB', 'clientVersion': '2.20251014.06.00'}}},
|
|
74
|
+
'header': {'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1,gzip(gfe)', 'X-Youtube-Client-Name': '2'},
|
|
75
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': True
|
|
76
|
+
},
|
|
77
|
+
'WEB_KIDS': {
|
|
78
|
+
'innertube_context': {'context': {'client': {'clientName': 'WEB_KIDS', 'osName': 'Windows', 'osVersion': '10.0', 'clientVersion': '2.20241125.00.00', 'platform': 'DESKTOP'}}},
|
|
79
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '76', 'X-Youtube-Client-Version': '2.20241125.00.00'},
|
|
80
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': False
|
|
81
|
+
},
|
|
82
|
+
'ANDROID': {
|
|
83
|
+
'innertube_context': {'context': {'client': {'clientName': 'ANDROID', 'clientVersion': '19.44.38', 'platform': 'MOBILE', 'osName': 'Android', 'osVersion': '14', 'androidSdkVersion': '34'}}},
|
|
84
|
+
'header': {'User-Agent': 'com.google.android.youtube/', 'X-Youtube-Client-Name': '3'},
|
|
85
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': True
|
|
86
|
+
},
|
|
87
|
+
'ANDROID_VR': {
|
|
88
|
+
'innertube_context': {'context': {'client': {'clientName': 'ANDROID_VR', 'clientVersion': '1.60.19', 'deviceMake': 'Oculus', 'deviceModel': 'Quest 3', 'osName': 'Android', 'osVersion': '12L', 'androidSdkVersion': '32'}}},
|
|
89
|
+
'header': {'User-Agent': 'com.google.android.apps.youtube.vr.oculus/1.60.19 (Linux; U; Android 12L; eureka-user Build/SQ3A.220605.009.A1) gzip', 'X-Youtube-Client-Name': '28'},
|
|
90
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
91
|
+
},
|
|
92
|
+
'ANDROID_MUSIC': {
|
|
93
|
+
'innertube_context': {'context': {'client': {'clientName': 'ANDROID_MUSIC', 'clientVersion': '7.27.52', 'androidSdkVersion': '30', 'osName': 'Android', 'osVersion': '11'}}},
|
|
94
|
+
'header': {'User-Agent': 'com.google.android.apps.youtube.music/7.27.52 (Linux; U; Android 11) gzip', 'X-Youtube-Client-Name': '21'},
|
|
95
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
96
|
+
},
|
|
97
|
+
'ANDROID_CREATOR': {
|
|
98
|
+
'innertube_context': {'context': {'client': {'clientName': 'ANDROID_CREATOR', 'clientVersion': '24.45.100', 'androidSdkVersion': '30', 'osName': 'Android', 'osVersion': '11'}}},
|
|
99
|
+
'header': {'User-Agent': 'com.google.android.apps.youtube.creator/24.45.100 (Linux; U; Android 11) gzip', 'X-Youtube-Client-Name': '14'},
|
|
100
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
101
|
+
},
|
|
102
|
+
'ANDROID_TESTSUITE': {
|
|
103
|
+
'innertube_context': {'context': {'client': {'clientName': 'ANDROID_TESTSUITE', 'clientVersion': '1.9', 'platform': 'MOBILE', 'osName': 'Android', 'osVersion': '14', 'androidSdkVersion': '34'}}},
|
|
104
|
+
'header': {'User-Agent': 'com.google.android.youtube/', 'X-Youtube-Client-Name': '30', 'X-Youtube-Client-Version': '1.9'},
|
|
105
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
106
|
+
},
|
|
107
|
+
'ANDROID_PRODUCER': {
|
|
108
|
+
'innertube_context': {'context': {'client': {'clientName': 'ANDROID_PRODUCER', 'clientVersion': '0.111.1', 'androidSdkVersion': '30', 'osName': 'Android', 'osVersion': '11'}}},
|
|
109
|
+
'header': {'User-Agent': 'com.google.android.apps.youtube.producer/0.111.1 (Linux; U; Android 11) gzip', 'X-Youtube-Client-Name': '91'},
|
|
110
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
111
|
+
},
|
|
112
|
+
'ANDROID_KIDS': {
|
|
113
|
+
'innertube_context': {'context': {'client': {'clientName': 'ANDROID_KIDS', 'clientVersion': '7.36.1', 'androidSdkVersion': '30', 'osName': 'Android', 'osVersion': '11'}}},
|
|
114
|
+
'header': {'User-Agent': 'com.google.android.apps.youtube.music/7.27.52 (Linux; U; Android 11) gzip'},
|
|
115
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
116
|
+
},
|
|
117
|
+
'IOS': {
|
|
118
|
+
'innertube_context': {'context': {'client': {'clientName': 'IOS', 'clientVersion': '19.45.4', 'deviceMake': 'Apple', 'platform': 'MOBILE', 'osName': 'iPhone', 'osVersion': '18.1.0.22B83', 'deviceModel': 'iPhone16,2'}}},
|
|
119
|
+
'header': {'User-Agent': 'com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X;)', 'X-Youtube-Client-Name': '5'},
|
|
120
|
+
'api_key': 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc', 'require_js_player': False, 'require_po_token': False
|
|
121
|
+
},
|
|
122
|
+
'IOS_MUSIC': {
|
|
123
|
+
'innertube_context': {'context': {'client': {'clientName': 'IOS_MUSIC', 'clientVersion': '7.27.0', 'deviceMake': 'Apple', 'platform': 'MOBILE', 'osName': 'iPhone', 'osVersion': '18.1.0.22B83', 'deviceModel': 'iPhone16,2'}}},
|
|
124
|
+
'header': {'User-Agent': 'com.google.ios.youtubemusic/7.27.0 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X;)', 'X-Youtube-Client-Name': '26'},
|
|
125
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
126
|
+
},
|
|
127
|
+
'IOS_CREATOR': {
|
|
128
|
+
'innertube_context': {'context': {'client': {'clientName': 'IOS_CREATOR', 'clientVersion': '24.45.100', 'deviceMake': 'Apple', 'deviceModel': 'iPhone16,2', 'osName': 'iPhone', 'osVersion': '18.1.0.22B83'}}},
|
|
129
|
+
'header': {'User-Agent': 'com.google.ios.ytcreator/24.45.100 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X;)', 'X-Youtube-Client-Name': '15'},
|
|
130
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
131
|
+
},
|
|
132
|
+
'IOS_KIDS': {
|
|
133
|
+
'innertube_context': {'context': {'client': {'clientName': 'IOS_KIDS', 'clientVersion': '7.36.1', 'deviceMake': 'Apple', 'platform': 'MOBILE', 'osName': 'iPhone', 'osVersion': '18.1.0.22B83', 'deviceModel': 'iPhone16,2'}}},
|
|
134
|
+
'header': {'User-Agent': 'com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X;)'},
|
|
135
|
+
'api_key': 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc', 'require_js_player': False, 'require_po_token': False
|
|
136
|
+
},
|
|
137
|
+
'TV': {
|
|
138
|
+
'innertube_context': {'context': {'client': {'clientName': 'TVHTML5', 'clientVersion': '7.20240813.07.00', 'platform': 'TV'}}},
|
|
139
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '7'},
|
|
140
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': False
|
|
141
|
+
},
|
|
142
|
+
'TV_EMBED': {
|
|
143
|
+
'innertube_context': {'context': {'client': {'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER', 'clientVersion': '2.0', 'clientScreen': 'EMBED', 'platform': 'TV'}}},
|
|
144
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '85'},
|
|
145
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': True, 'require_po_token': False
|
|
146
|
+
},
|
|
147
|
+
'MEDIA_CONNECT': {
|
|
148
|
+
'innertube_context': {'context': {'client': {'clientName': 'MEDIA_CONNECT_FRONTEND', 'clientVersion': '0.1'}}},
|
|
149
|
+
'header': {'User-Agent': 'Mozilla/5.0', 'X-Youtube-Client-Name': '95'},
|
|
150
|
+
'api_key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', 'require_js_player': False, 'require_po_token': False
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
'''assertuint32'''
|
|
156
|
+
def assertuint32(value):
|
|
157
|
+
if not (0 <= value <= 0xFFFFFFFF): raise ValueError("Value is not a valid uint32")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
'''assertint32'''
|
|
161
|
+
def assertint32(value):
|
|
162
|
+
if not (-0x80000000 <= value <= 0x7FFFFFFF): raise ValueError("Value is not a valid int32")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
'''varint32write'''
|
|
166
|
+
def varint32write(value, buf: list):
|
|
167
|
+
while value > 0x7F:
|
|
168
|
+
buf.append((value & 0x7F) | 0x80)
|
|
169
|
+
value >>= 7
|
|
170
|
+
buf.append(value)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
'''varint64write'''
|
|
174
|
+
def varint64write(lo, hi, buf: list):
|
|
175
|
+
for _ in range(9):
|
|
176
|
+
if hi == 0 and lo < 0x80:
|
|
177
|
+
buf.append(lo)
|
|
178
|
+
return
|
|
179
|
+
buf.append((lo & 0x7F) | 0x80)
|
|
180
|
+
lo = ((hi << 25) | (lo >> 7)) & 0xFFFFFFFF
|
|
181
|
+
hi = hi >> 7
|
|
182
|
+
buf.append(lo)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
'''readvarint32'''
|
|
186
|
+
def readvarint32(buf: bytes, pos: int):
|
|
187
|
+
result = shift = 0
|
|
188
|
+
while True:
|
|
189
|
+
if pos >= len(buf): raise EOFError("Unexpected end of buffer while reading varint32")
|
|
190
|
+
b = buf[pos]
|
|
191
|
+
pos += 1
|
|
192
|
+
result |= (b & 0x7F) << shift
|
|
193
|
+
if not (b & 0x80): break
|
|
194
|
+
shift += 7
|
|
195
|
+
if shift > 35: raise ValueError("Varint32 too long")
|
|
196
|
+
return result, pos
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
'''readvarint64'''
|
|
200
|
+
def readvarint64(buf, pos):
|
|
201
|
+
low_bits, high_bits = 0, 0
|
|
202
|
+
for shift in range(0, 28, 7):
|
|
203
|
+
b = buf[pos]
|
|
204
|
+
pos += 1
|
|
205
|
+
low_bits |= (b & 0x7F) << shift
|
|
206
|
+
if (b & 0x80) == 0: return low_bits, high_bits, pos
|
|
207
|
+
middle_byte = buf[pos]
|
|
208
|
+
pos += 1
|
|
209
|
+
low_bits |= (middle_byte & 0x0F) << 28
|
|
210
|
+
high_bits = (middle_byte & 0x70) >> 4
|
|
211
|
+
if (middle_byte & 0x80) == 0: return low_bits, high_bits, pos
|
|
212
|
+
for shift in range(3, 32, 7):
|
|
213
|
+
b = buf[pos]
|
|
214
|
+
pos += 1
|
|
215
|
+
high_bits |= (b & 0x7F) << shift
|
|
216
|
+
if (b & 0x80) == 0: return low_bits, high_bits, pos
|
|
217
|
+
raise ValueError("invalid varint")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
'''decodeint64'''
|
|
221
|
+
def decodeint64(lo: int, hi: int):
|
|
222
|
+
value = (hi << 32) | lo
|
|
223
|
+
if hi & 0x80000000: value -= 1 << 64
|
|
224
|
+
return value
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
'''decodeuint64'''
|
|
228
|
+
def decodeuint64(lo: int, hi: int):
|
|
229
|
+
return (hi << 32) | lo
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
'''longtonumber'''
|
|
233
|
+
def longtonumber(int64_value):
|
|
234
|
+
value = int(str(int64_value))
|
|
235
|
+
if value > (2 ** 53 - 1): raise OverflowError("Value is larger than 9007199254740991")
|
|
236
|
+
if value < -(2 ** 53 - 1): raise OverflowError("Value is smaller than -9007199254740991")
|
|
237
|
+
return value
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
'''targetdirectory'''
|
|
241
|
+
def targetdirectory(output_path: Optional[str] = None):
|
|
242
|
+
if output_path:
|
|
243
|
+
if not os.path.isabs(output_path): output_path = os.path.join(os.getcwd(), output_path)
|
|
244
|
+
else:
|
|
245
|
+
output_path = os.getcwd()
|
|
246
|
+
os.makedirs(output_path, exist_ok=True)
|
|
247
|
+
return output_path
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
'''regexsearch'''
|
|
251
|
+
def regexsearch(pattern: str, string: str, group: int):
|
|
252
|
+
regex = re.compile(pattern)
|
|
253
|
+
results = regex.search(string)
|
|
254
|
+
return results.group(group)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
'''filesystemverify'''
|
|
258
|
+
def filesystemverify(file_type):
|
|
259
|
+
# systems
|
|
260
|
+
bsd_unix = ['BSD', 'UFS']
|
|
261
|
+
mac_os = ['macOS', 'APFS', 'HFS+']
|
|
262
|
+
network_filesystems = ['CIFS', 'SMB']
|
|
263
|
+
windows = ['Windows', 'NTFS', 'FAT32', 'exFAT', 'ReFS']
|
|
264
|
+
linux = ['Linux', 'ext2', 'ext3', 'ext4', 'Btrfs', 'XFS', 'ZFS']
|
|
265
|
+
# return translations
|
|
266
|
+
if file_type in windows: return str.maketrans({'\\': '', '/': '', '?': '', ':': '', '*': '', '"': '', '<': '', '>': '', '|': ''})
|
|
267
|
+
elif file_type in linux: return str.maketrans({'/': ''})
|
|
268
|
+
elif file_type in mac_os: return str.maketrans({'/': ''})
|
|
269
|
+
elif file_type in bsd_unix: return str.maketrans({'/': ''})
|
|
270
|
+
elif file_type in network_filesystems: return str.maketrans({'\\': '', '/': '', '?': '', ':': '', '*': '', '"': '', '<': '', '>': '', '|': ''})
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
'''mimetypecodec'''
|
|
274
|
+
def mimetypecodec(mime_type_codec: str):
|
|
275
|
+
pattern = r"(\w+\/\w+)\;\scodecs=\"([a-zA-Z-0-9.,\s]*)\""
|
|
276
|
+
regex = re.compile(pattern)
|
|
277
|
+
results = regex.search(mime_type_codec)
|
|
278
|
+
mime_type, codecs = results.groups()
|
|
279
|
+
return mime_type, [c.strip() for c in codecs.split(",")]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
'''getformatprofile'''
|
|
283
|
+
def getformatprofile(itag: str):
|
|
284
|
+
# constants
|
|
285
|
+
PROGRESSIVE_VIDEO = {
|
|
286
|
+
5: ("240p", "64kbps"), 6: ("270p", "64kbps"), 13: ("144p", None), 17: ("144p", "24kbps"), 18: ("360p", "96kbps"), 22: ("720p", "192kbps"),
|
|
287
|
+
34: ("360p", "128kbps"), 35: ("480p", "128kbps"), 36: ("240p", None), 37: ("1080p", "192kbps"), 38: ("3072p", "192kbps"), 43: ("360p", "128kbps"),
|
|
288
|
+
44: ("480p", "128kbps"), 45: ("720p", "192kbps"), 46: ("1080p", "192kbps"), 59: ("480p", "128kbps"), 78: ("480p", "128kbps"), 82: ("360p", "128kbps"),
|
|
289
|
+
83: ("480p", "128kbps"), 84: ("720p", "192kbps"), 85: ("1080p", "192kbps"), 91: ("144p", "48kbps"), 92: ("240p", "48kbps"), 93: ("360p", "128kbps"),
|
|
290
|
+
94: ("480p", "128kbps"), 95: ("720p", "256kbps"), 96: ("1080p", "256kbps"), 100: ("360p", "128kbps"), 101: ("480p", "192kbps"), 102: ("720p", "192kbps"),
|
|
291
|
+
132: ("240p", "48kbps"), 151: ("720p", "24kbps"), 300: ("720p", "128kbps"), 301: ("1080p", "128kbps"),
|
|
292
|
+
}
|
|
293
|
+
DASH_VIDEO = {
|
|
294
|
+
133: ("240p", None), 134: ("360p", None), 135: ("480p", None), 136: ("720p", None), 137: ("1080p", None), 138: ("2160p", None), 160: ("144p", None),
|
|
295
|
+
167: ("360p", None), 168: ("480p", None), 169: ("720p", None), 170: ("1080p", None), 212: ("480p", None), 218: ("480p", None), 219: ("480p", None),
|
|
296
|
+
242: ("240p", None), 243: ("360p", None), 244: ("480p", None), 245: ("480p", None), 246: ("480p", None), 247: ("720p", None), 248: ("1080p", None),
|
|
297
|
+
264: ("1440p", None), 266: ("2160p", None), 271: ("1440p", None), 272: ("4320p", None), 278: ("144p", None), 298: ("720p", None), 299: ("1080p", None),
|
|
298
|
+
302: ("720p", None), 303: ("1080p", None), 308: ("1440p", None), 313: ("2160p", None), 315: ("2160p", None), 330: ("144p", None), 331: ("240p", None),
|
|
299
|
+
332: ("360p", None), 333: ("480p", None), 334: ("720p", None), 335: ("1080p", None), 336: ("1440p", None), 337: ("2160p", None), 394: ("144p", None),
|
|
300
|
+
395: ("240p", None), 396: ("360p", None), 397: ("480p", None), 398: ("720p", None), 399: ("1080p", None), 400: ("1440p", None), 401: ("2160p", None),
|
|
301
|
+
402: ("4320p", None), 571: ("4320p", None), 694: ("144p", None), 695: ("240p", None), 696: ("360p", None), 697: ("480p", None), 698: ("720p", None),
|
|
302
|
+
699: ("1080p", None), 700: ("1440p", None), 701: ("2160p", None), 702: ("4320p", None),
|
|
303
|
+
}
|
|
304
|
+
DASH_AUDIO = {
|
|
305
|
+
139: (None, "48kbps"), 140: (None, "128kbps"), 141: (None, "256kbps"), 171: (None, "128kbps"), 172: (None, "256kbps"), 249: (None, "50kbps"),
|
|
306
|
+
250: (None, "70kbps"), 251: (None, "160kbps"), 256: (None, "192kbps"), 258: (None, "384kbps"), 325: (None, None), 328: (None, None),
|
|
307
|
+
}
|
|
308
|
+
ITAGS = {**PROGRESSIVE_VIDEO, **DASH_VIDEO, **DASH_AUDIO}
|
|
309
|
+
HDR = [330, 331, 332, 333, 334, 335, 336, 337]
|
|
310
|
+
_3D = [82, 83, 84, 85, 100, 101, 102]
|
|
311
|
+
LIVE = [91, 92, 93, 94, 95, 96, 132, 151]
|
|
312
|
+
# parse
|
|
313
|
+
itag = int(itag)
|
|
314
|
+
res, bitrate = ITAGS[itag] if itag in ITAGS else (None, None)
|
|
315
|
+
return {"resolution": res, "abr": bitrate, "is_live": itag in LIVE, "is_3d": itag in _3D, "is_hdr": itag in HDR, "is_dash": (itag in DASH_AUDIO or itag in DASH_VIDEO)}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
'''defaultoauthverifier'''
|
|
319
|
+
def defaultoauthverifier(verification_url: str, user_code: str):
|
|
320
|
+
print(f'Please open {verification_url} and input code {user_code}')
|
|
321
|
+
input('Press enter when you have completed this step.')
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
'''defaultpotokenverifier'''
|
|
325
|
+
def defaultpotokenverifier():
|
|
326
|
+
print('You can use the tool: https://github.com/YunzheZJU/youtube-po-token-generator, to get the token')
|
|
327
|
+
visitor_data = str(input("Enter with your visitorData: "))
|
|
328
|
+
po_token = str(input("Enter with your po_token: "))
|
|
329
|
+
return visitor_data, po_token
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
'''isagerestricted'''
|
|
333
|
+
def isagerestricted(watch_html: str):
|
|
334
|
+
try:
|
|
335
|
+
regexsearch(r"og:restrictions:age", watch_html, group=0)
|
|
336
|
+
except:
|
|
337
|
+
return False
|
|
338
|
+
return True
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
'''getytplayerjs'''
|
|
342
|
+
def getytplayerjs(html: str):
|
|
343
|
+
js_url_patterns = [r"(/s/player/[\w\d]+/[\w\d_/.]+/base\.js)"]
|
|
344
|
+
for pattern in js_url_patterns:
|
|
345
|
+
regex = re.compile(pattern)
|
|
346
|
+
function_match = regex.search(html)
|
|
347
|
+
if function_match:
|
|
348
|
+
yt_player_js = function_match.group(1)
|
|
349
|
+
return yt_player_js
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
'''findobjectfromstartpoint'''
|
|
353
|
+
def findobjectfromstartpoint(html, start_point):
|
|
354
|
+
html, last_char, curr_char = html[start_point:], '{', None
|
|
355
|
+
stack, i, context_closers = [html[0]], 1, {'{': '}', '[': ']', '"': '"', '\'': '\'', '/': '/'}
|
|
356
|
+
while i < len(html):
|
|
357
|
+
if not stack: break
|
|
358
|
+
if curr_char not in [' ', '\n']: last_char = curr_char
|
|
359
|
+
curr_char = html[i]
|
|
360
|
+
curr_context = stack[-1]
|
|
361
|
+
if curr_char == context_closers[curr_context]:
|
|
362
|
+
stack.pop()
|
|
363
|
+
i += 1
|
|
364
|
+
continue
|
|
365
|
+
if curr_context in ['"', '\'', '/']:
|
|
366
|
+
if curr_char == '\\':
|
|
367
|
+
i += 2
|
|
368
|
+
continue
|
|
369
|
+
else:
|
|
370
|
+
if curr_char in context_closers.keys():
|
|
371
|
+
if not (curr_char == '/' and last_char not in ['(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';']):
|
|
372
|
+
stack.append(curr_char)
|
|
373
|
+
i += 1
|
|
374
|
+
full_obj = html[:i]
|
|
375
|
+
return full_obj
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
'''parseforobjectfromstartpoint'''
|
|
379
|
+
def parseforobjectfromstartpoint(html, start_point):
|
|
380
|
+
full_obj = findobjectfromstartpoint(html, start_point)
|
|
381
|
+
try:
|
|
382
|
+
return json.loads(full_obj)
|
|
383
|
+
except:
|
|
384
|
+
try:
|
|
385
|
+
return ast.literal_eval(full_obj)
|
|
386
|
+
except:
|
|
387
|
+
raise
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
'''parseforobject'''
|
|
391
|
+
def parseforobject(html, preceding_regex):
|
|
392
|
+
regex = re.compile(preceding_regex)
|
|
393
|
+
result = regex.search(html)
|
|
394
|
+
start_index = result.end()
|
|
395
|
+
return parseforobjectfromstartpoint(html, start_index)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
'''getytplayerconfig'''
|
|
399
|
+
def getytplayerconfig(html: str):
|
|
400
|
+
config_patterns = [r"ytplayer\.config\s*=\s*", r"ytInitialPlayerResponse\s*=\s*"]
|
|
401
|
+
for pattern in config_patterns:
|
|
402
|
+
try:
|
|
403
|
+
return parseforobject(html, pattern)
|
|
404
|
+
except:
|
|
405
|
+
continue
|
|
406
|
+
setconfig_patterns = [r"yt\.setConfig\(.*['\"]PLAYER_CONFIG['\"]:\s*"]
|
|
407
|
+
for pattern in setconfig_patterns:
|
|
408
|
+
try:
|
|
409
|
+
return parseforobject(html, pattern)
|
|
410
|
+
except:
|
|
411
|
+
continue
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
'''extractjsurl'''
|
|
415
|
+
def extractjsurl(html: str):
|
|
416
|
+
try:
|
|
417
|
+
base_js = getytplayerconfig(html)['assets']['js']
|
|
418
|
+
except:
|
|
419
|
+
base_js = getytplayerjs(html)
|
|
420
|
+
return f"https://youtube.com{base_js}"
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
'''extractsignaturetimestamp'''
|
|
424
|
+
def extractsignaturetimestamp(js: str):
|
|
425
|
+
return regexsearch(r"signatureTimestamp:(\d*)", js, group=1)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
'''extractvisitordata'''
|
|
429
|
+
def extractvisitordata(resp_context: str):
|
|
430
|
+
return regexsearch(r"visitor_data[',\"\s]+value['\"]:\s?['\"]([a-zA-Z0-9_%-]+)['\"]", resp_context, group=1)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
'''extractinitialdata'''
|
|
434
|
+
def extractinitialdata(watch_html: str):
|
|
435
|
+
patterns = [r"window\[['\"]ytInitialData['\"]]\s*=\s*", r"ytInitialData\s*=\s*"]
|
|
436
|
+
for pattern in patterns:
|
|
437
|
+
try:
|
|
438
|
+
return parseforobject(watch_html, pattern)
|
|
439
|
+
except:
|
|
440
|
+
pass
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
'''extractmetadata'''
|
|
444
|
+
def extractmetadata(initial_data):
|
|
445
|
+
try:
|
|
446
|
+
metadata_rows = initial_data["contents"]["twoColumnWatchNextResults"]["results"]["results"]["contents"][1]["videoSecondaryInfoRenderer"]["metadataRowContainer"]["metadataRowContainerRenderer"]["rows"]
|
|
447
|
+
except:
|
|
448
|
+
YouTubeMetadata([])
|
|
449
|
+
metadata_rows = filter(lambda x: "metadataRowRenderer" in x.keys(), metadata_rows)
|
|
450
|
+
metadata_rows = [x["metadataRowRenderer"] for x in metadata_rows]
|
|
451
|
+
return YouTubeMetadata(metadata_rows)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
'''applydescrambler'''
|
|
455
|
+
def applydescrambler(stream_data: dict):
|
|
456
|
+
if 'url' in stream_data: return None
|
|
457
|
+
formats = []
|
|
458
|
+
if 'formats' in stream_data.keys(): formats.extend(stream_data['formats'])
|
|
459
|
+
if 'adaptiveFormats' in stream_data.keys(): formats.extend(stream_data['adaptiveFormats'])
|
|
460
|
+
for data in formats:
|
|
461
|
+
if 'url' not in data and 'signatureCipher' in data:
|
|
462
|
+
cipher_url = parse_qs(data['signatureCipher'])
|
|
463
|
+
data['url'] = cipher_url['url'][0]
|
|
464
|
+
data['s'] = cipher_url['s'][0]
|
|
465
|
+
data['is_sabr'] = False
|
|
466
|
+
elif 'url' not in data and 'signatureCipher' not in data:
|
|
467
|
+
data['url'] = stream_data['serverAbrStreamingUrl']
|
|
468
|
+
data['is_sabr'] = True
|
|
469
|
+
data['is_otf'] = data.get('type') == 'FORMAT_STREAM_TYPE_OTF'
|
|
470
|
+
return formats
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
'''applypotoken'''
|
|
474
|
+
def applypotoken(stream_manifest, vid_info: dict, po_token: str):
|
|
475
|
+
for i, stream in enumerate(stream_manifest):
|
|
476
|
+
url: str = stream["url"]
|
|
477
|
+
parsed_url = urlparse(url)
|
|
478
|
+
query_params = parse_qs(urlparse(url).query)
|
|
479
|
+
query_params = {k: v[0] for k, v in query_params.items()}
|
|
480
|
+
query_params['pot'] = po_token
|
|
481
|
+
url = f'{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}?{urlencode(query_params)}'
|
|
482
|
+
stream_manifest[i]["url"] = url
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
'''generatepotoken'''
|
|
486
|
+
def generatepotoken(video_id: str):
|
|
487
|
+
suffix = ".exe" if os.name == "nt" else ""
|
|
488
|
+
bin_dir = nodejs_wheel.executable.ROOT_DIR if os.name == "nt" else os.path.join(nodejs_wheel.executable.ROOT_DIR, "bin")
|
|
489
|
+
try:
|
|
490
|
+
result = subprocess.check_output([os.path.join(bin_dir, 'node' + suffix), str(Path(__file__).resolve().parent.parent / "js" / "youtube" / "botguard.js"), video_id], stderr=subprocess.PIPE).decode()
|
|
491
|
+
return result.replace("\n", "")
|
|
492
|
+
except Exception as err:
|
|
493
|
+
raise RuntimeError(err)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
'''extractsignaturetimestamp'''
|
|
497
|
+
def extractsignaturetimestamp(js: str):
|
|
498
|
+
return regexsearch(r"signatureTimestamp:(\d*)", js, group=1)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
'''applysignature'''
|
|
502
|
+
def applysignature(stream_manifest: Dict, vid_info: Dict, js: str, url_js: str):
|
|
503
|
+
cipher = Cipher(js=js, js_url=url_js)
|
|
504
|
+
discovered_n = dict()
|
|
505
|
+
for i, stream in enumerate(stream_manifest):
|
|
506
|
+
try:
|
|
507
|
+
url: str = stream["url"]
|
|
508
|
+
except KeyError:
|
|
509
|
+
live_stream = (vid_info.get("playabilityStatus", {}, ).get("liveStreamability"))
|
|
510
|
+
if live_stream: raise
|
|
511
|
+
parsed_url = urlparse(url)
|
|
512
|
+
query_params = parse_qs(urlparse(url).query)
|
|
513
|
+
query_params = {k: v[0] for k, v in query_params.items()}
|
|
514
|
+
if "signature" in url or ("s" not in stream and ("&sig=" in url or "&lsig=" in url)):
|
|
515
|
+
pass
|
|
516
|
+
else:
|
|
517
|
+
signature = cipher.getsig(ciphered_signature=stream["s"])
|
|
518
|
+
query_params['sig'] = signature
|
|
519
|
+
if 'n' in query_params.keys():
|
|
520
|
+
initial_n = query_params['n']
|
|
521
|
+
if initial_n not in discovered_n: discovered_n[initial_n] = cipher.getnsig(initial_n)
|
|
522
|
+
else: pass
|
|
523
|
+
new_n = discovered_n[initial_n]
|
|
524
|
+
query_params['n'] = new_n
|
|
525
|
+
url = f'{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}?{urlencode(query_params)}'
|
|
526
|
+
stream_manifest[i]["url"] = url
|
|
527
|
+
cipher.runner_sig.close()
|
|
528
|
+
cipher.runner_nsig.close()
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
'''ProtoInt64'''
|
|
532
|
+
class ProtoInt64:
|
|
533
|
+
'''enc'''
|
|
534
|
+
@staticmethod
|
|
535
|
+
def enc(value: int):
|
|
536
|
+
if value < 0: value += 1 << 64
|
|
537
|
+
lo = value & 0xFFFFFFFF
|
|
538
|
+
hi = (value >> 32) & 0xFFFFFFFF
|
|
539
|
+
return {'lo': lo, 'hi': hi}
|
|
540
|
+
'''uenc'''
|
|
541
|
+
@staticmethod
|
|
542
|
+
def uenc(value: int):
|
|
543
|
+
lo = value & 0xFFFFFFFF
|
|
544
|
+
hi = (value >> 32) & 0xFFFFFFFF
|
|
545
|
+
return {'lo': lo, 'hi': hi}
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
'''RequestWrapper'''
|
|
549
|
+
class RequestWrapper:
|
|
550
|
+
default_range_size = 9437184
|
|
551
|
+
'''_executerequest'''
|
|
552
|
+
@staticmethod
|
|
553
|
+
def _executerequest(url: str, method=None, headers=None, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
|
|
554
|
+
base_headers = {"User-Agent": "Mozilla/5.0", "accept-language": "en-US,en"}
|
|
555
|
+
if headers: base_headers.update(headers)
|
|
556
|
+
if data and not isinstance(data, bytes): data = bytes(json.dumps(data), encoding="utf-8")
|
|
557
|
+
if url.lower().startswith("http"): request = Request(url, headers=base_headers, method=method, data=data)
|
|
558
|
+
else: raise ValueError("Invalid URL")
|
|
559
|
+
return urlopen(request, timeout=timeout)
|
|
560
|
+
'''get'''
|
|
561
|
+
@staticmethod
|
|
562
|
+
def get(url, extra_headers=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
|
|
563
|
+
if extra_headers is None: extra_headers = {}
|
|
564
|
+
resp = RequestWrapper._executerequest(url, headers=extra_headers, timeout=timeout)
|
|
565
|
+
return resp.read().decode("utf-8")
|
|
566
|
+
'''post'''
|
|
567
|
+
@staticmethod
|
|
568
|
+
def post(url, extra_headers=None, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
|
|
569
|
+
if extra_headers is None: extra_headers = {}
|
|
570
|
+
if data is None: data = {}
|
|
571
|
+
extra_headers.update({"Content-Type": "application/json"})
|
|
572
|
+
resp = RequestWrapper._executerequest(url, headers=extra_headers, data=data, timeout=timeout)
|
|
573
|
+
return resp.read().decode("utf-8")
|
|
574
|
+
'''seqstream'''
|
|
575
|
+
@staticmethod
|
|
576
|
+
def seqstream(url, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, max_retries=0):
|
|
577
|
+
split_url = parse.urlsplit(url)
|
|
578
|
+
base_url = f'{split_url.scheme}://{split_url.netloc}/{split_url.path}?'
|
|
579
|
+
querys = dict(parse.parse_qsl(split_url.query))
|
|
580
|
+
querys['sq'] = 0
|
|
581
|
+
url = base_url + parse.urlencode(querys)
|
|
582
|
+
segment_data = b''
|
|
583
|
+
for chunk in RequestWrapper.stream(url, timeout=timeout, max_retries=max_retries):
|
|
584
|
+
yield chunk
|
|
585
|
+
segment_data += chunk
|
|
586
|
+
stream_info = segment_data.split(b'\r\n')
|
|
587
|
+
segment_count_pattern = re.compile(b'Segment-Count: (\\d+)')
|
|
588
|
+
for line in stream_info:
|
|
589
|
+
match = segment_count_pattern.search(line)
|
|
590
|
+
if match: segment_count = int(match.group(1).decode('utf-8'))
|
|
591
|
+
seq_num = 1
|
|
592
|
+
while seq_num <= segment_count:
|
|
593
|
+
querys['sq'] = seq_num
|
|
594
|
+
url = base_url + parse.urlencode(querys)
|
|
595
|
+
yield from RequestWrapper.stream(url, timeout=timeout, max_retries=max_retries)
|
|
596
|
+
seq_num += 1
|
|
597
|
+
return
|
|
598
|
+
'''stream'''
|
|
599
|
+
@staticmethod
|
|
600
|
+
def stream(url, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, max_retries=0):
|
|
601
|
+
downloaded = 0
|
|
602
|
+
file_size: int = RequestWrapper.default_range_size
|
|
603
|
+
while downloaded < file_size:
|
|
604
|
+
stop_pos, tries = min(downloaded + RequestWrapper.default_range_size, file_size) - 1, 0
|
|
605
|
+
while True:
|
|
606
|
+
if tries >= 1 + max_retries: raise HTTPError()
|
|
607
|
+
try:
|
|
608
|
+
resp = RequestWrapper._executerequest(f"{url}&range={downloaded}-{stop_pos}", method="GET", timeout=timeout)
|
|
609
|
+
except URLError as e:
|
|
610
|
+
if not isinstance(e.reason, (socket.timeout, OSError)): raise
|
|
611
|
+
except http.client.IncompleteRead: pass
|
|
612
|
+
else: break
|
|
613
|
+
tries += 1
|
|
614
|
+
if file_size == RequestWrapper.default_range_size:
|
|
615
|
+
try:
|
|
616
|
+
content_range = RequestWrapper._executerequest(f"{url}&range=0-99999999999", method="GET", timeout=timeout).info()["Content-Length"]
|
|
617
|
+
file_size = int(content_range)
|
|
618
|
+
except (KeyError, IndexError, ValueError) as e:
|
|
619
|
+
pass
|
|
620
|
+
while True:
|
|
621
|
+
try: chunk = resp.read()
|
|
622
|
+
except StopIteration: return
|
|
623
|
+
except http.client.IncompleteRead as e: chunk = e.partial
|
|
624
|
+
if not chunk: break
|
|
625
|
+
if chunk: downloaded += len(chunk)
|
|
626
|
+
yield chunk
|
|
627
|
+
return
|
|
628
|
+
'''filesize'''
|
|
629
|
+
@staticmethod
|
|
630
|
+
@lru_cache()
|
|
631
|
+
def filesize(url): return int(RequestWrapper.head(url)["content-length"])
|
|
632
|
+
'''seqfilesize'''
|
|
633
|
+
@staticmethod
|
|
634
|
+
@lru_cache()
|
|
635
|
+
def seqfilesize(url):
|
|
636
|
+
total_filesize = 0
|
|
637
|
+
split_url = parse.urlsplit(url)
|
|
638
|
+
base_url = f'{split_url.scheme}://{split_url.netloc}/{split_url.path}?'
|
|
639
|
+
querys = dict(parse.parse_qsl(split_url.query))
|
|
640
|
+
querys['sq'] = 0
|
|
641
|
+
url = base_url + parse.urlencode(querys)
|
|
642
|
+
resp = RequestWrapper._executerequest(url, method="GET")
|
|
643
|
+
resp_value = resp.read()
|
|
644
|
+
total_filesize += len(resp_value)
|
|
645
|
+
segment_count = 0
|
|
646
|
+
stream_info = resp_value.split(b'\r\n')
|
|
647
|
+
segment_regex = b'Segment-Count: (\\d+)'
|
|
648
|
+
for line in stream_info:
|
|
649
|
+
try: segment_count = int(regexsearch(segment_regex, line, 1))
|
|
650
|
+
except: pass
|
|
651
|
+
if segment_count == 0: raise
|
|
652
|
+
seq_num = 1
|
|
653
|
+
while seq_num <= segment_count:
|
|
654
|
+
querys['sq'] = seq_num
|
|
655
|
+
url = base_url + parse.urlencode(querys)
|
|
656
|
+
total_filesize += int(RequestWrapper.head(url)['content-length'])
|
|
657
|
+
seq_num += 1
|
|
658
|
+
return total_filesize
|
|
659
|
+
'''head'''
|
|
660
|
+
@staticmethod
|
|
661
|
+
def head(url: str):
|
|
662
|
+
resp_headers: dict = RequestWrapper._executerequest(url, method="HEAD").info()
|
|
663
|
+
return {k.lower(): v for k, v in resp_headers.items()}
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
'''NodeRunner'''
|
|
667
|
+
class NodeRunner:
|
|
668
|
+
def __init__(self, code: str):
|
|
669
|
+
self.code = code
|
|
670
|
+
self.function_name = None
|
|
671
|
+
self.proc = subprocess.Popen([self._nodepath(), str(Path(__file__).resolve().parent.parent / "js" / "youtube" / "runner.js")], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
|
|
672
|
+
'''_nodepath'''
|
|
673
|
+
@staticmethod
|
|
674
|
+
def _nodepath():
|
|
675
|
+
suffix = ".exe" if os.name == "nt" else ""
|
|
676
|
+
bin_dir = nodejs_wheel.executable.ROOT_DIR if os.name == "nt" else os.path.join(nodejs_wheel.executable.ROOT_DIR, "bin")
|
|
677
|
+
return os.path.join(bin_dir, 'node' + suffix)
|
|
678
|
+
'''_exposed'''
|
|
679
|
+
@staticmethod
|
|
680
|
+
def _exposed(code: str, fun_name: str):
|
|
681
|
+
exposed = f"_exposed['{fun_name}']={fun_name};" + "})(_yt_player);"
|
|
682
|
+
return code.replace("})(_yt_player);", exposed)
|
|
683
|
+
'''_send'''
|
|
684
|
+
def _send(self, data):
|
|
685
|
+
self.proc.stdin.write(json.dumps(data) + "\n")
|
|
686
|
+
self.proc.stdin.flush()
|
|
687
|
+
return json.loads(self.proc.stdout.readline())
|
|
688
|
+
'''loadfunction'''
|
|
689
|
+
def loadfunction(self, function_name: str):
|
|
690
|
+
self.function_name = function_name
|
|
691
|
+
return self._send({"type": "load", "code": self._exposed(self.code, function_name)})
|
|
692
|
+
'''call'''
|
|
693
|
+
def call(self, args: list):
|
|
694
|
+
return self._send({"type": "call", "fun": self.function_name, "args": args or []})
|
|
695
|
+
'''close'''
|
|
696
|
+
def close(self):
|
|
697
|
+
self.proc.stdin.close()
|
|
698
|
+
self.proc.terminate()
|
|
699
|
+
self.proc.wait()
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
'''PART'''
|
|
703
|
+
class PART(Enum):
|
|
704
|
+
ONESIE_HEADER = 10
|
|
705
|
+
ONESIE_DATA = 11
|
|
706
|
+
MEDIA_HEADER = 20
|
|
707
|
+
MEDIA = 21
|
|
708
|
+
MEDIA_END = 22
|
|
709
|
+
LIVE_METADATA = 31
|
|
710
|
+
HOSTNAME_CHANGE_HINT = 32
|
|
711
|
+
LIVE_METADATA_PROMISE = 33
|
|
712
|
+
LIVE_METADATA_PROMISE_CANCELLATION = 34
|
|
713
|
+
NEXT_REQUEST_POLICY = 35
|
|
714
|
+
USTREAMER_VIDEO_AND_FORMAT_DATA = 36
|
|
715
|
+
FORMAT_SELECTION_CONFIG = 37
|
|
716
|
+
USTREAMER_SELECTED_MEDIA_STREAM = 38
|
|
717
|
+
FORMAT_INITIALIZATION_METADATA = 42
|
|
718
|
+
SABR_REDIRECT = 43
|
|
719
|
+
SABR_ERROR = 44
|
|
720
|
+
SABR_SEEK = 45
|
|
721
|
+
RELOAD_PLAYER_RESPONSE = 46
|
|
722
|
+
PLAYBACK_START_POLICY = 47
|
|
723
|
+
ALLOWED_CACHED_FORMATS = 48
|
|
724
|
+
START_BW_SAMPLING_HINT = 49
|
|
725
|
+
PAUSE_BW_SAMPLING_HINT = 50
|
|
726
|
+
SELECTABLE_FORMATS = 51
|
|
727
|
+
REQUEST_IDENTIFIER = 52
|
|
728
|
+
REQUEST_CANCELLATION_POLICY = 53
|
|
729
|
+
ONESIE_PREFETCH_REJECTION = 54
|
|
730
|
+
TIMELINE_CONTEXT = 55
|
|
731
|
+
REQUEST_PIPELINING = 56
|
|
732
|
+
SABR_CONTEXT_UPDATE = 57
|
|
733
|
+
STREAM_PROTECTION_STATUS = 58
|
|
734
|
+
SABR_CONTEXT_SENDING_POLICY = 59
|
|
735
|
+
LAWNMOWER_POLICY = 60
|
|
736
|
+
SABR_ACK = 61
|
|
737
|
+
END_OF_TRACK = 62
|
|
738
|
+
CACHE_LOAD_POLICY = 63
|
|
739
|
+
LAWNMOWER_MESSAGING_POLICY = 64
|
|
740
|
+
PREWARM_CONNECTION = 65
|
|
741
|
+
PLAYBACK_DEBUG_INFO = 66
|
|
742
|
+
SNACKBAR_MESSAGE = 67
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
'''PoTokenStatus'''
|
|
746
|
+
class PoTokenStatus(Enum):
|
|
747
|
+
UNKNOWN = -1
|
|
748
|
+
OK = enum.auto()
|
|
749
|
+
MISSING = enum.auto()
|
|
750
|
+
INVALID = enum.auto()
|
|
751
|
+
PENDING = enum.auto()
|
|
752
|
+
NOT_REQUIRED = enum.auto()
|
|
753
|
+
PENDING_MISSING = enum.auto()
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
'''Monostate'''
|
|
757
|
+
class Monostate:
|
|
758
|
+
def __init__(self, on_progress: Optional[Callable[[Any, bytes, int], None]], on_complete: Optional[Callable[[Any, Optional[str]], None]],
|
|
759
|
+
title: Optional[str] = None, duration: Optional[int] = None, youtube = None):
|
|
760
|
+
self.on_progress = on_progress
|
|
761
|
+
self.on_complete = on_complete
|
|
762
|
+
self.title = title
|
|
763
|
+
self.duration = duration
|
|
764
|
+
self.youtube = youtube
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
'''ChunkedDataBuffer'''
|
|
768
|
+
class ChunkedDataBuffer:
|
|
769
|
+
def __init__(self, chunks=None):
|
|
770
|
+
self.chunks = []
|
|
771
|
+
self.current_chunk_index = 0
|
|
772
|
+
self.current_chunk_offset = 0
|
|
773
|
+
self.current_data_view = None
|
|
774
|
+
self.total_length = 0
|
|
775
|
+
chunks = chunks or []
|
|
776
|
+
for chunk in chunks: self.append(chunk)
|
|
777
|
+
'''getlength'''
|
|
778
|
+
def getlength(self):
|
|
779
|
+
return self.total_length
|
|
780
|
+
'''append'''
|
|
781
|
+
def append(self, chunk):
|
|
782
|
+
if self.canmergewithlastchunk(chunk):
|
|
783
|
+
last_chunk = self.chunks[-1]
|
|
784
|
+
merged = bytearray(last_chunk)
|
|
785
|
+
merged.extend(chunk)
|
|
786
|
+
self.chunks[-1] = bytes(merged)
|
|
787
|
+
self.resetfocus()
|
|
788
|
+
else:
|
|
789
|
+
self.chunks.append(chunk)
|
|
790
|
+
self.total_length += len(chunk)
|
|
791
|
+
'''split'''
|
|
792
|
+
def split(self, position):
|
|
793
|
+
extracted_buffer, remaining_buffer, remaining_pos = ChunkedDataBuffer(), ChunkedDataBuffer(), position
|
|
794
|
+
for chunk in self.chunks:
|
|
795
|
+
chunk_len = len(chunk)
|
|
796
|
+
if remaining_pos >= chunk_len:
|
|
797
|
+
extracted_buffer.append(chunk)
|
|
798
|
+
remaining_pos -= chunk_len
|
|
799
|
+
elif remaining_pos > 0:
|
|
800
|
+
extracted_buffer.append(chunk[:remaining_pos])
|
|
801
|
+
remaining_buffer.append(chunk[remaining_pos:])
|
|
802
|
+
remaining_pos = 0
|
|
803
|
+
else:
|
|
804
|
+
remaining_buffer.append(chunk)
|
|
805
|
+
return {"extracted_buffer": extracted_buffer, "remaining_buffer": remaining_buffer}
|
|
806
|
+
'''isfocused'''
|
|
807
|
+
def isfocused(self, position):
|
|
808
|
+
chunk = self.chunks[self.current_chunk_index]
|
|
809
|
+
return self.current_chunk_offset <= position < self.current_chunk_offset + len(chunk)
|
|
810
|
+
'''focus'''
|
|
811
|
+
def focus(self, position):
|
|
812
|
+
if not self.isfocused(position):
|
|
813
|
+
if position < self.current_chunk_offset: self.resetfocus()
|
|
814
|
+
while (self.current_chunk_offset + len(self.chunks[self.current_chunk_index]) <= position and self.current_chunk_index < len(self.chunks) - 1):
|
|
815
|
+
self.current_chunk_offset += len(self.chunks[self.current_chunk_index])
|
|
816
|
+
self.current_chunk_index += 1
|
|
817
|
+
self.current_data_view = None
|
|
818
|
+
'''canreadbytes'''
|
|
819
|
+
def canreadbytes(self, position, length):
|
|
820
|
+
return position + length <= self.total_length
|
|
821
|
+
'''getuint8'''
|
|
822
|
+
def getuint8(self, position):
|
|
823
|
+
self.focus(position)
|
|
824
|
+
chunk = self.chunks[self.current_chunk_index]
|
|
825
|
+
return chunk[position - self.current_chunk_offset]
|
|
826
|
+
'''canmergewithlastchunk'''
|
|
827
|
+
def canmergewithlastchunk(self, chunk):
|
|
828
|
+
if not self.chunks: return False
|
|
829
|
+
last_chunk = self.chunks[-1]
|
|
830
|
+
return (last_chunk is not None and isinstance(last_chunk, (bytes, bytearray)) and isinstance(chunk, (bytes, bytearray)))
|
|
831
|
+
'''resetfocus'''
|
|
832
|
+
def resetfocus(self):
|
|
833
|
+
self.current_data_view = None
|
|
834
|
+
self.current_chunk_index = 0
|
|
835
|
+
self.current_chunk_offset = 0
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
'''UMP'''
|
|
839
|
+
class UMP:
|
|
840
|
+
def __init__(self, chunked_data_buffer: ChunkedDataBuffer):
|
|
841
|
+
self.chunked_data_buffer = chunked_data_buffer
|
|
842
|
+
'''parse'''
|
|
843
|
+
def parse(self, handle_part):
|
|
844
|
+
while True:
|
|
845
|
+
offset = 0
|
|
846
|
+
part_type, new_offset = self.readvarint(offset)
|
|
847
|
+
offset = new_offset
|
|
848
|
+
part_size, final_offset = self.readvarint(offset)
|
|
849
|
+
offset = final_offset
|
|
850
|
+
if part_type < 0 or part_size < 0: break
|
|
851
|
+
if not self.chunked_data_buffer.canreadbytes(offset, part_size):
|
|
852
|
+
if not self.chunked_data_buffer.canreadbytes(offset, 1): break
|
|
853
|
+
return {"type": part_type, "size": part_size, "data": self.chunked_data_buffer}
|
|
854
|
+
split_result = self.chunked_data_buffer.split(offset)['remaining_buffer'].split(part_size)
|
|
855
|
+
offset = 0
|
|
856
|
+
handle_part({"type": part_type, "size": part_size, "data": split_result['extracted_buffer']})
|
|
857
|
+
self.chunked_data_buffer = split_result['remaining_buffer']
|
|
858
|
+
'''readvarint'''
|
|
859
|
+
def readvarint(self, offset):
|
|
860
|
+
if self.chunked_data_buffer.canreadbytes(offset, 1):
|
|
861
|
+
first_byte = self.chunked_data_buffer.getuint8(offset)
|
|
862
|
+
if first_byte < 128: byte_length = 1
|
|
863
|
+
elif first_byte < 192: byte_length = 2
|
|
864
|
+
elif first_byte < 224: byte_length = 3
|
|
865
|
+
elif first_byte < 240: byte_length = 4
|
|
866
|
+
else: byte_length = 5
|
|
867
|
+
else:
|
|
868
|
+
byte_length = 0
|
|
869
|
+
if byte_length < 1 or not self.chunked_data_buffer.canreadbytes(offset, byte_length): return -1, offset
|
|
870
|
+
if byte_length == 1:
|
|
871
|
+
value = self.chunked_data_buffer.getuint8(offset)
|
|
872
|
+
offset += 1
|
|
873
|
+
elif byte_length == 2:
|
|
874
|
+
byte1 = self.chunked_data_buffer.getuint8(offset)
|
|
875
|
+
byte2 = self.chunked_data_buffer.getuint8(offset + 1)
|
|
876
|
+
value = (byte1 & 0x3F) + 64 * byte2
|
|
877
|
+
offset += 2
|
|
878
|
+
elif byte_length == 3:
|
|
879
|
+
byte1 = self.chunked_data_buffer.getuint8(offset)
|
|
880
|
+
byte2 = self.chunked_data_buffer.getuint8(offset + 1)
|
|
881
|
+
byte3 = self.chunked_data_buffer.getuint8(offset + 2)
|
|
882
|
+
value = (byte1 & 0x1F) + 32 * (byte2 + 256 * byte3)
|
|
883
|
+
offset += 3
|
|
884
|
+
elif byte_length == 4:
|
|
885
|
+
byte1 = self.chunked_data_buffer.getuint8(offset)
|
|
886
|
+
byte2 = self.chunked_data_buffer.getuint8(offset + 1)
|
|
887
|
+
byte3 = self.chunked_data_buffer.getuint8(offset + 2)
|
|
888
|
+
byte4 = self.chunked_data_buffer.getuint8(offset + 3)
|
|
889
|
+
value = (byte1 & 0x0F) + 16 * (byte2 + 256 * (byte3 + 256 * byte4))
|
|
890
|
+
offset += 4
|
|
891
|
+
else:
|
|
892
|
+
temp_offset = offset + 1
|
|
893
|
+
self.chunked_data_buffer.focus(temp_offset)
|
|
894
|
+
if self.canreadfromcurrentchunk(temp_offset, 4):
|
|
895
|
+
view = self.getcurrentdataview()
|
|
896
|
+
offset_in_chunk = temp_offset - self.chunked_data_buffer.current_chunk_offset
|
|
897
|
+
value = int.from_bytes(view[offset_in_chunk:offset_in_chunk + 4], byteorder='little')
|
|
898
|
+
else:
|
|
899
|
+
byte3 = (self.chunked_data_buffer.getuint8(temp_offset + 2) + 256 * self.chunked_data_buffer.getuint8(temp_offset + 3))
|
|
900
|
+
value = (self.chunked_data_buffer.getuint8(temp_offset) + 256 * (self.chunked_data_buffer.getuint8(temp_offset + 1) + 256 * byte3))
|
|
901
|
+
offset += 5
|
|
902
|
+
return value, offset
|
|
903
|
+
'''canreadfromcurrentchunk'''
|
|
904
|
+
def canreadfromcurrentchunk(self, offset, length):
|
|
905
|
+
index = self.chunked_data_buffer.current_chunk_index
|
|
906
|
+
current_chunk = self.chunked_data_buffer.chunks[index]
|
|
907
|
+
return (offset - self.chunked_data_buffer.current_chunk_offset + length <= len(current_chunk))
|
|
908
|
+
'''getcurrentdataview'''
|
|
909
|
+
def getcurrentdataview(self):
|
|
910
|
+
if self.chunked_data_buffer.current_data_view is None:
|
|
911
|
+
chunk = self.chunked_data_buffer.chunks[self.chunked_data_buffer.current_chunk_index]
|
|
912
|
+
self.chunked_data_buffer.current_data_view = memoryview(chunk)
|
|
913
|
+
return self.chunked_data_buffer.current_data_view
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
'''Stream'''
|
|
917
|
+
class Stream:
|
|
918
|
+
def __init__(self, stream: Dict, monostate: Monostate, po_token: str, video_playback_ustreamer_config: str):
|
|
919
|
+
self._monostate = monostate
|
|
920
|
+
self.url = stream["url"]
|
|
921
|
+
self.itag = int(stream["itag"])
|
|
922
|
+
self.xtags = stream["xtags"] if "xtags" in stream else None
|
|
923
|
+
self.mime_type, self.codecs = mimetypecodec(stream["mimeType"])
|
|
924
|
+
self.type, self.subtype = self.mime_type.split("/")
|
|
925
|
+
self.video_codec, self.audio_codec = self.parsecodecs()
|
|
926
|
+
self.is_otf: bool = stream["is_otf"]
|
|
927
|
+
self.bitrate: Optional[int] = stream["bitrate"]
|
|
928
|
+
self._filesize: Optional[int] = int(stream.get('contentLength', 0))
|
|
929
|
+
self._filesize_kb: Optional[float] = float(math.ceil(float(stream.get('contentLength', 0)) / 1024 * 1000) / 1000)
|
|
930
|
+
self._filesize_mb: Optional[float] = float(math.ceil(float(stream.get('contentLength', 0)) / 1024 / 1024 * 1000) / 1000)
|
|
931
|
+
self._filesize_gb: Optional[float] = float(math.ceil(float(stream.get('contentLength', 0)) / 1024 / 1024 / 1024 * 1000) / 1000)
|
|
932
|
+
itag_profile = getformatprofile(self.itag)
|
|
933
|
+
self.is_dash = itag_profile["is_dash"]
|
|
934
|
+
self.abr = itag_profile["abr"]
|
|
935
|
+
if 'fps' in stream: self.fps = stream['fps']
|
|
936
|
+
self.resolution = itag_profile["resolution"]
|
|
937
|
+
self._width = stream["width"] if 'width' in stream else None
|
|
938
|
+
self._height = stream["height"] if 'height' in stream else None
|
|
939
|
+
self.is_3d = itag_profile["is_3d"]
|
|
940
|
+
self.is_hdr = itag_profile["is_hdr"]
|
|
941
|
+
self.is_live = itag_profile["is_live"]
|
|
942
|
+
self.is_drc = stream.get('isDrc', False)
|
|
943
|
+
self._is_sabr = stream.get('is_sabr', False)
|
|
944
|
+
self.durationMs = stream['approxDurationMs']
|
|
945
|
+
self.last_Modified = stream['lastModified']
|
|
946
|
+
self.po_token = po_token
|
|
947
|
+
self.video_playback_ustreamer_config = video_playback_ustreamer_config
|
|
948
|
+
self.includes_multiple_audio_tracks: bool = 'audioTrack' in stream
|
|
949
|
+
if self.includes_multiple_audio_tracks:
|
|
950
|
+
self.is_default_audio_track = "original" in stream['audioTrack']['displayName']
|
|
951
|
+
self.audio_track_name_regionalized = str(stream['audioTrack']['displayName']).replace(" original", "")
|
|
952
|
+
self.audio_track_name = self.audio_track_name_regionalized.split(" ")[0]
|
|
953
|
+
self.audio_track_language_id_regionalized= str(stream['audioTrack']['id']).split(".")[0]
|
|
954
|
+
self.audio_track_language_id= self.audio_track_language_id_regionalized.split("-")[0]
|
|
955
|
+
else:
|
|
956
|
+
self.is_default_audio_track = self.includesaudiotrack and not self.includesvideotrack
|
|
957
|
+
self.audio_track_name_regionalized = None
|
|
958
|
+
self.audio_track_name = None
|
|
959
|
+
self.audio_track_language_id_regionalized = None
|
|
960
|
+
self.audio_track_language_id= None
|
|
961
|
+
'''isadaptive'''
|
|
962
|
+
@property
|
|
963
|
+
def isadaptive(self): return bool(len(self.codecs) % 2)
|
|
964
|
+
'''isprogressive'''
|
|
965
|
+
@property
|
|
966
|
+
def isprogressive(self): return not self.isadaptive
|
|
967
|
+
'''issabr'''
|
|
968
|
+
@property
|
|
969
|
+
def issabr(self): return self._is_sabr
|
|
970
|
+
@issabr.setter
|
|
971
|
+
def issabr(self, value): self._is_sabr = value
|
|
972
|
+
'''includesaudiotrack'''
|
|
973
|
+
@property
|
|
974
|
+
def includesaudiotrack(self): return self.isprogressive or self.type == "audio"
|
|
975
|
+
'''includesvideotrack'''
|
|
976
|
+
@property
|
|
977
|
+
def includesvideotrack(self): return self.isprogressive or self.type == "video"
|
|
978
|
+
'''parsecodecs'''
|
|
979
|
+
def parsecodecs(self):
|
|
980
|
+
video, audio = None, None
|
|
981
|
+
if not self.isadaptive: video, audio = self.codecs
|
|
982
|
+
elif self.includesvideotrack: video = self.codecs[0]
|
|
983
|
+
elif self.includesaudiotrack: audio = self.codecs[0]
|
|
984
|
+
return video, audio
|
|
985
|
+
'''width'''
|
|
986
|
+
@property
|
|
987
|
+
def width(self): return self._width
|
|
988
|
+
'''height'''
|
|
989
|
+
@property
|
|
990
|
+
def height(self): return self._height
|
|
991
|
+
'''filesize'''
|
|
992
|
+
@property
|
|
993
|
+
def filesize(self):
|
|
994
|
+
if self._filesize == 0:
|
|
995
|
+
try:
|
|
996
|
+
self._filesize = RequestWrapper.filesize(self.url)
|
|
997
|
+
except HTTPError as e:
|
|
998
|
+
if e.code != 404: raise
|
|
999
|
+
self._filesize = RequestWrapper.seqfilesize(self.url)
|
|
1000
|
+
return self._filesize
|
|
1001
|
+
'''filesizekb'''
|
|
1002
|
+
@property
|
|
1003
|
+
def filesizekb(self):
|
|
1004
|
+
if self._filesize_kb == 0:
|
|
1005
|
+
try:
|
|
1006
|
+
self._filesize_kb = float(math.ceil(RequestWrapper.filesize(self.url) / 1024 * 1000) / 1000)
|
|
1007
|
+
except HTTPError as e:
|
|
1008
|
+
if e.code != 404: raise
|
|
1009
|
+
self._filesize_kb = float(math.ceil(RequestWrapper.seqfilesize(self.url) / 1024 * 1000) / 1000)
|
|
1010
|
+
return self._filesize_kb
|
|
1011
|
+
'''filesizemb'''
|
|
1012
|
+
@property
|
|
1013
|
+
def filesizemb(self):
|
|
1014
|
+
if self._filesize_mb == 0:
|
|
1015
|
+
try:
|
|
1016
|
+
self._filesize_mb = float(math.ceil(RequestWrapper.filesize(self.url) / 1024 / 1024 * 1000) / 1000)
|
|
1017
|
+
except HTTPError as e:
|
|
1018
|
+
if e.code != 404: raise
|
|
1019
|
+
self._filesize_mb = float(math.ceil(RequestWrapper.seqfilesize(self.url) / 1024 / 1024 * 1000) / 1000)
|
|
1020
|
+
return self._filesize_mb
|
|
1021
|
+
'''filesizegb'''
|
|
1022
|
+
@property
|
|
1023
|
+
def filesizegb(self):
|
|
1024
|
+
if self._filesize_gb == 0:
|
|
1025
|
+
try:
|
|
1026
|
+
self._filesize_gb = float(math.ceil(RequestWrapper.filesize(self.url) / 1024 / 1024 / 1024 * 1000) / 1000)
|
|
1027
|
+
except HTTPError as e:
|
|
1028
|
+
if e.code != 404: raise
|
|
1029
|
+
self._filesize_gb = float(math.ceil(RequestWrapper.seqfilesize(self.url) / 1024 / 1024 / 1024 * 1000) / 1000)
|
|
1030
|
+
return self._filesize_gb
|
|
1031
|
+
'''title'''
|
|
1032
|
+
@property
|
|
1033
|
+
def title(self):
|
|
1034
|
+
return self._monostate.title or "Unknown YouTube Video Title"
|
|
1035
|
+
'''filesizeapprox'''
|
|
1036
|
+
@property
|
|
1037
|
+
def filesizeapprox(self):
|
|
1038
|
+
if self._monostate.duration and self.bitrate:
|
|
1039
|
+
bits_in_byte = 8
|
|
1040
|
+
return int((self._monostate.duration * self.bitrate) / bits_in_byte)
|
|
1041
|
+
return self.filesize
|
|
1042
|
+
'''expiration'''
|
|
1043
|
+
@property
|
|
1044
|
+
def expiration(self):
|
|
1045
|
+
expire = parse_qs(self.url.split("?")[1])["expire"][0]
|
|
1046
|
+
return datetime.fromtimestamp(int(expire), timezone.utc)
|
|
1047
|
+
'''defaultfilename'''
|
|
1048
|
+
@property
|
|
1049
|
+
def defaultfilename(self):
|
|
1050
|
+
if 'audio' in self.mime_type and 'video' not in self.mime_type:
|
|
1051
|
+
self.subtype = "m4a"
|
|
1052
|
+
return f"{self.title}.{self.subtype}"
|
|
1053
|
+
'''download'''
|
|
1054
|
+
def download(self, output_path: Optional[str] = None, filename: Optional[str] = None, filename_prefix: Optional[str] = None, skip_existing: bool = True, timeout: Optional[int] = None, max_retries: int = 0, interrupt_checker: Optional[Callable[[], bool]] = None):
|
|
1055
|
+
kernel = sys.platform
|
|
1056
|
+
if kernel == "linux": file_system = "ext4"
|
|
1057
|
+
elif kernel == "darwin": file_system = "APFS"
|
|
1058
|
+
else: file_system = "NTFS"
|
|
1059
|
+
translation_table = filesystemverify(file_system)
|
|
1060
|
+
if filename is None: filename = self.defaultfilename.translate(translation_table)
|
|
1061
|
+
if filename: filename = filename.translate(translation_table)
|
|
1062
|
+
file_path = self.getfilepath(filename=filename, output_path=output_path, filename_prefix=filename_prefix, file_system=file_system)
|
|
1063
|
+
if skip_existing and self.existsatpath(file_path):
|
|
1064
|
+
self.oncomplete(file_path)
|
|
1065
|
+
return file_path
|
|
1066
|
+
bytes_remaining = self.filesize
|
|
1067
|
+
def _writechunk(chunk_, bytes_remaining_): self.onprogress(chunk_, fh, bytes_remaining_)
|
|
1068
|
+
with open(file_path, "wb") as fh:
|
|
1069
|
+
try:
|
|
1070
|
+
if not self.issabr:
|
|
1071
|
+
for chunk in RequestWrapper.stream(self.url, timeout=timeout, max_retries=max_retries):
|
|
1072
|
+
if interrupt_checker is not None and interrupt_checker() == True: return
|
|
1073
|
+
bytes_remaining -= len(chunk)
|
|
1074
|
+
_writechunk(chunk, bytes_remaining)
|
|
1075
|
+
else:
|
|
1076
|
+
ServerAbrStream(stream=self, write_chunk=_writechunk, monostate=self._monostate).start()
|
|
1077
|
+
except HTTPError as e:
|
|
1078
|
+
if e.code != 404: raise
|
|
1079
|
+
except StopIteration:
|
|
1080
|
+
if not self.issabr:
|
|
1081
|
+
for chunk in RequestWrapper.seqstream(self.url, timeout=timeout, max_retries=max_retries):
|
|
1082
|
+
if interrupt_checker is not None and interrupt_checker() == True: return
|
|
1083
|
+
bytes_remaining -= len(chunk)
|
|
1084
|
+
_writechunk(chunk, bytes_remaining)
|
|
1085
|
+
else:
|
|
1086
|
+
ServerAbrStream(stream=self, write_chunk=_writechunk, monostate=self._monostate).start()
|
|
1087
|
+
self.oncomplete(file_path)
|
|
1088
|
+
return file_path
|
|
1089
|
+
'''getfilepath'''
|
|
1090
|
+
def getfilepath(self, filename: Optional[str] = None, output_path: Optional[str] = None, filename_prefix: Optional[str] = None, file_system: str = 'NTFS'):
|
|
1091
|
+
if not filename:
|
|
1092
|
+
translation_table = filesystemverify(file_system)
|
|
1093
|
+
filename = self.defaultfilename.translate(translation_table)
|
|
1094
|
+
if filename:
|
|
1095
|
+
translation_table = filesystemverify(file_system)
|
|
1096
|
+
if not ('audio' in self.mime_type and 'video' not in self.mime_type): filename = filename.translate(translation_table)
|
|
1097
|
+
else: filename = filename.translate(translation_table)
|
|
1098
|
+
if filename_prefix: filename = f"{filename_prefix}{filename}"
|
|
1099
|
+
return str(Path(targetdirectory(output_path)) / filename)
|
|
1100
|
+
'''existsatpath'''
|
|
1101
|
+
def existsatpath(self, file_path: str):
|
|
1102
|
+
return (os.path.isfile(file_path) and os.path.getsize(file_path) == self.filesize)
|
|
1103
|
+
'''streamtobuffer'''
|
|
1104
|
+
def streamtobuffer(self, buffer: BinaryIO):
|
|
1105
|
+
bytes_remaining = self.filesize
|
|
1106
|
+
for chunk in RequestWrapper.stream(self.url):
|
|
1107
|
+
bytes_remaining -= len(chunk)
|
|
1108
|
+
self.onprogress(chunk, buffer, bytes_remaining)
|
|
1109
|
+
self.oncomplete(None)
|
|
1110
|
+
'''onprogress'''
|
|
1111
|
+
def onprogress(self, chunk: bytes, file_handler: BinaryIO, bytes_remaining: int):
|
|
1112
|
+
file_handler.write(chunk)
|
|
1113
|
+
if self._monostate.on_progress: self._monostate.on_progress(self, chunk, bytes_remaining)
|
|
1114
|
+
'''oncomplete'''
|
|
1115
|
+
def oncomplete(self, file_path: Optional[str]):
|
|
1116
|
+
on_complete = self._monostate.on_complete
|
|
1117
|
+
if on_complete: on_complete(self, file_path)
|
|
1118
|
+
'''onprogressforchunks'''
|
|
1119
|
+
def onprogressforchunks(self, chunk: bytes, bytes_remaining: int):
|
|
1120
|
+
if self._monostate.on_progress: self._monostate.on_progress(self, chunk, bytes_remaining)
|
|
1121
|
+
'''iterchunks'''
|
|
1122
|
+
def iterchunks(self, chunk_size: Optional[int] = None):
|
|
1123
|
+
bytes_remaining = self.filesize
|
|
1124
|
+
if chunk_size: RequestWrapper.default_range_size = chunk_size
|
|
1125
|
+
try:
|
|
1126
|
+
stream = RequestWrapper.stream(self.url)
|
|
1127
|
+
except HTTPError as e:
|
|
1128
|
+
if e.code != 404: raise
|
|
1129
|
+
stream = RequestWrapper.seqstream(self.url)
|
|
1130
|
+
for chunk in stream:
|
|
1131
|
+
bytes_remaining -= len(chunk)
|
|
1132
|
+
self.onprogressforchunks(chunk, bytes_remaining)
|
|
1133
|
+
yield chunk
|
|
1134
|
+
self.oncomplete(None)
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
'''BinaryWriter'''
|
|
1138
|
+
class BinaryWriter:
|
|
1139
|
+
def __init__(self, encode_utf8: Callable[[str], bytes] = lambda s: s.encode('utf-8')):
|
|
1140
|
+
self.encode_utf8 = encode_utf8
|
|
1141
|
+
self.stack = []
|
|
1142
|
+
self.chunks = []
|
|
1143
|
+
self.buf = bytearray()
|
|
1144
|
+
'''finish'''
|
|
1145
|
+
def finish(self):
|
|
1146
|
+
if self.buf:
|
|
1147
|
+
self.chunks.append(bytes(self.buf))
|
|
1148
|
+
self.buf.clear()
|
|
1149
|
+
return b''.join(self.chunks)
|
|
1150
|
+
'''fork'''
|
|
1151
|
+
def fork(self):
|
|
1152
|
+
self.stack.append((self.chunks, self.buf))
|
|
1153
|
+
self.chunks = []
|
|
1154
|
+
self.buf = bytearray()
|
|
1155
|
+
return self
|
|
1156
|
+
'''join'''
|
|
1157
|
+
def join(self):
|
|
1158
|
+
chunk = self.finish()
|
|
1159
|
+
if not self.stack: raise RuntimeError("Invalid state, fork stack empty")
|
|
1160
|
+
self.chunks, self.buf = self.stack.pop()
|
|
1161
|
+
self.uint32(len(chunk))
|
|
1162
|
+
return self.raw(chunk)
|
|
1163
|
+
'''tag'''
|
|
1164
|
+
def tag(self, field_no: int, wire_type: int):
|
|
1165
|
+
return self.uint32((field_no << 3) | wire_type)
|
|
1166
|
+
'''raw'''
|
|
1167
|
+
def raw(self, chunk: bytes):
|
|
1168
|
+
if self.buf:
|
|
1169
|
+
self.chunks.append(bytes(self.buf))
|
|
1170
|
+
self.buf.clear()
|
|
1171
|
+
self.chunks.append(chunk)
|
|
1172
|
+
return self
|
|
1173
|
+
'''uint32'''
|
|
1174
|
+
def uint32(self, value: int):
|
|
1175
|
+
assertuint32(value)
|
|
1176
|
+
varint32write(value, self.buf)
|
|
1177
|
+
return self
|
|
1178
|
+
'''int32'''
|
|
1179
|
+
def int32(self, value: int):
|
|
1180
|
+
assertint32(value)
|
|
1181
|
+
varint32write(value & 0xFFFFFFFF, self.buf)
|
|
1182
|
+
return self
|
|
1183
|
+
'''bool'''
|
|
1184
|
+
def bool(self, value: bool):
|
|
1185
|
+
self.buf.append(1 if value else 0)
|
|
1186
|
+
return self
|
|
1187
|
+
'''bytes'''
|
|
1188
|
+
def bytes(self, value: bytes):
|
|
1189
|
+
self.uint32(len(value))
|
|
1190
|
+
return self.raw(value)
|
|
1191
|
+
'''string'''
|
|
1192
|
+
def string(self, value: str):
|
|
1193
|
+
encoded = self.encode_utf8(value)
|
|
1194
|
+
self.uint32(len(encoded))
|
|
1195
|
+
return self.raw(encoded)
|
|
1196
|
+
'''float'''
|
|
1197
|
+
def float(self, value: float):
|
|
1198
|
+
self.raw(struct.pack('<f', value))
|
|
1199
|
+
return self
|
|
1200
|
+
'''double'''
|
|
1201
|
+
def double(self, value: float):
|
|
1202
|
+
self.raw(struct.pack('<d', value))
|
|
1203
|
+
return self
|
|
1204
|
+
'''fixed32'''
|
|
1205
|
+
def fixed32(self, value: int):
|
|
1206
|
+
assertuint32(value)
|
|
1207
|
+
self.raw(struct.pack('<I', value))
|
|
1208
|
+
return self
|
|
1209
|
+
'''sfixed32'''
|
|
1210
|
+
def sfixed32(self, value: int):
|
|
1211
|
+
assertint32(value)
|
|
1212
|
+
self.raw(struct.pack('<i', value))
|
|
1213
|
+
return self
|
|
1214
|
+
'''sint32'''
|
|
1215
|
+
def sint32(self, value: int):
|
|
1216
|
+
assertint32(value)
|
|
1217
|
+
encoded = (value << 1) ^ (value >> 31)
|
|
1218
|
+
varint32write(encoded, self.buf)
|
|
1219
|
+
return self
|
|
1220
|
+
'''sfixed64'''
|
|
1221
|
+
def sfixed64(self, value: int):
|
|
1222
|
+
tc = ProtoInt64.enc(value)
|
|
1223
|
+
self.raw(struct.pack('<ii', tc['lo'], tc['hi']))
|
|
1224
|
+
return self
|
|
1225
|
+
'''fixed64'''
|
|
1226
|
+
def fixed64(self, value: int):
|
|
1227
|
+
tc = ProtoInt64.uenc(value)
|
|
1228
|
+
self.raw(struct.pack('<II', tc['lo'], tc['hi']))
|
|
1229
|
+
return self
|
|
1230
|
+
'''int64'''
|
|
1231
|
+
def int64(self, value: int):
|
|
1232
|
+
tc = ProtoInt64.enc(value)
|
|
1233
|
+
varint64write(tc['lo'], tc['hi'], self.buf)
|
|
1234
|
+
return self
|
|
1235
|
+
'''sint64'''
|
|
1236
|
+
def sint64(self, value: int):
|
|
1237
|
+
tc = ProtoInt64.enc(value)
|
|
1238
|
+
sign = tc['hi'] >> 31
|
|
1239
|
+
lo = (tc['lo'] << 1) ^ sign
|
|
1240
|
+
hi = ((tc['hi'] << 1) | (tc['lo'] >> 31)) ^ sign
|
|
1241
|
+
varint64write(lo, hi, self.buf)
|
|
1242
|
+
return self
|
|
1243
|
+
'''uint64'''
|
|
1244
|
+
def uint64(self, value: int):
|
|
1245
|
+
tc = ProtoInt64.uenc(value)
|
|
1246
|
+
varint64write(tc['lo'], tc['hi'], self.buf)
|
|
1247
|
+
return self
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
'''BinaryReader'''
|
|
1251
|
+
class BinaryReader:
|
|
1252
|
+
def __init__(self, buf, decode_utf8: Callable[[bytes], str] = lambda b: b.decode('utf-8')):
|
|
1253
|
+
if isinstance(buf, list): buf = bytes(buf)
|
|
1254
|
+
elif isinstance(buf, bytearray): buf = bytes(buf)
|
|
1255
|
+
elif not isinstance(buf, bytes): raise TypeError(f"Unsupported buffer type: {type(buf)}")
|
|
1256
|
+
self.decode_utf8 = decode_utf8
|
|
1257
|
+
self.buf = buf
|
|
1258
|
+
self.len = len(buf)
|
|
1259
|
+
self.pos = 0
|
|
1260
|
+
'''tag'''
|
|
1261
|
+
def tag(self):
|
|
1262
|
+
tag, self.pos = readvarint32(self.buf, self.pos)
|
|
1263
|
+
field_no = tag >> 3
|
|
1264
|
+
wire_type = tag & 0x7
|
|
1265
|
+
if field_no <= 0 or wire_type < 0 or wire_type > 5: raise ValueError(f"Illegal tag: field no {field_no} wire type {wire_type}")
|
|
1266
|
+
return field_no, wire_type
|
|
1267
|
+
'''skip'''
|
|
1268
|
+
def skip(self, wire_type: int, field_no=None):
|
|
1269
|
+
start = self.pos
|
|
1270
|
+
if wire_type == 0:
|
|
1271
|
+
while self.buf[self.pos] & 0x80: self.pos += 1
|
|
1272
|
+
self.pos += 1
|
|
1273
|
+
elif wire_type == 1:
|
|
1274
|
+
self.pos += 8
|
|
1275
|
+
elif wire_type == 2:
|
|
1276
|
+
length, self.pos = readvarint32(self.buf, self.pos)
|
|
1277
|
+
self.pos += length
|
|
1278
|
+
elif wire_type == 3:
|
|
1279
|
+
while True:
|
|
1280
|
+
fn, wt = self.tag()
|
|
1281
|
+
if wt == 4:
|
|
1282
|
+
if field_no is not None and fn != field_no: raise ValueError("Invalid end group tag")
|
|
1283
|
+
break
|
|
1284
|
+
self.skip(wt, fn)
|
|
1285
|
+
elif wire_type == 5:
|
|
1286
|
+
self.pos += 4
|
|
1287
|
+
else:
|
|
1288
|
+
raise ValueError(f"Can't skip unknown wire type {wire_type}")
|
|
1289
|
+
self.assertbounds()
|
|
1290
|
+
return self.buf[start:self.pos]
|
|
1291
|
+
'''assertbounds'''
|
|
1292
|
+
def assertbounds(self):
|
|
1293
|
+
if self.pos > self.len: raise EOFError("Premature EOF")
|
|
1294
|
+
'''uint32'''
|
|
1295
|
+
def uint32(self):
|
|
1296
|
+
value, self.pos = readvarint32(self.buf, self.pos)
|
|
1297
|
+
return value
|
|
1298
|
+
'''int32'''
|
|
1299
|
+
def int32(self):
|
|
1300
|
+
return self.uint32() | 0
|
|
1301
|
+
'''sint32'''
|
|
1302
|
+
def sint32(self):
|
|
1303
|
+
value = self.uint32()
|
|
1304
|
+
return (value >> 1) ^ -(value & 1)
|
|
1305
|
+
'''varint64'''
|
|
1306
|
+
def varint64(self):
|
|
1307
|
+
lo, hi, self.pos = readvarint64(self.buf, self.pos)
|
|
1308
|
+
return lo, hi
|
|
1309
|
+
'''int64'''
|
|
1310
|
+
def int64(self):
|
|
1311
|
+
return decodeint64(*self.varint64())
|
|
1312
|
+
'''uint64'''
|
|
1313
|
+
def uint64(self):
|
|
1314
|
+
return decodeuint64(*self.varint64())
|
|
1315
|
+
'''sint64'''
|
|
1316
|
+
def sint64(self):
|
|
1317
|
+
lo, hi = self.varint64()
|
|
1318
|
+
sign = -(lo & 1)
|
|
1319
|
+
lo = ((lo >> 1) | ((hi & 1) << 31)) ^ sign
|
|
1320
|
+
hi = (hi >> 1) ^ sign
|
|
1321
|
+
return decodeint64(lo, hi)
|
|
1322
|
+
'''bool'''
|
|
1323
|
+
def bool(self):
|
|
1324
|
+
lo, hi = self.varint64()
|
|
1325
|
+
return lo != 0 or hi != 0
|
|
1326
|
+
'''fixed32'''
|
|
1327
|
+
def fixed32(self):
|
|
1328
|
+
value = struct.unpack_from('<I', self.buf, self.pos)[0]
|
|
1329
|
+
self.pos += 4
|
|
1330
|
+
return value
|
|
1331
|
+
'''sfixed32'''
|
|
1332
|
+
def sfixed32(self):
|
|
1333
|
+
value = struct.unpack_from('<i', self.buf, self.pos)[0]
|
|
1334
|
+
self.pos += 4
|
|
1335
|
+
return value
|
|
1336
|
+
'''fixed64'''
|
|
1337
|
+
def fixed64(self):
|
|
1338
|
+
lo = self.sfixed32()
|
|
1339
|
+
hi = self.sfixed32()
|
|
1340
|
+
return decodeuint64(lo, hi)
|
|
1341
|
+
'''sfixed64'''
|
|
1342
|
+
def sfixed64(self):
|
|
1343
|
+
lo = self.sfixed32()
|
|
1344
|
+
hi = self.sfixed32()
|
|
1345
|
+
return decodeint64(lo, hi)
|
|
1346
|
+
'''float'''
|
|
1347
|
+
def float(self):
|
|
1348
|
+
value = struct.unpack_from('<f', self.buf, self.pos)[0]
|
|
1349
|
+
self.pos += 4
|
|
1350
|
+
return value
|
|
1351
|
+
'''double'''
|
|
1352
|
+
def double(self):
|
|
1353
|
+
value = struct.unpack_from('<d', self.buf, self.pos)[0]
|
|
1354
|
+
self.pos += 8
|
|
1355
|
+
return value
|
|
1356
|
+
'''bytes'''
|
|
1357
|
+
def bytes(self):
|
|
1358
|
+
length = self.uint32()
|
|
1359
|
+
start = self.pos
|
|
1360
|
+
self.pos += length
|
|
1361
|
+
self.assertbounds()
|
|
1362
|
+
return self.buf[start:self.pos]
|
|
1363
|
+
'''string'''
|
|
1364
|
+
def string(self):
|
|
1365
|
+
return self.decode_utf8(self.bytes())
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
'''ClientAbrState'''
|
|
1369
|
+
class ClientAbrState:
|
|
1370
|
+
'''createbaseclientabrstate'''
|
|
1371
|
+
@staticmethod
|
|
1372
|
+
def createbaseclientabrstate():
|
|
1373
|
+
return {
|
|
1374
|
+
"timeSinceLastManualFormatSelectionMs": 0, "lastManualDirection": 0, "lastManualSelectedResolution": 0, "detailedNetworkType": 0, "clientViewportWidth": 0,
|
|
1375
|
+
"clientViewportHeight": 0, "clientBitrateCapBytesPerSec": 0, "stickyResolution": 0, "clientViewportIsFlexible": False, "bandwidthEstimate": 0, "minAudioQuality": 0,
|
|
1376
|
+
"maxAudioQuality": 0, "videoQualitySetting": 0, "audioRoute": 0, "playerTimeMs": 0, "timeSinceLastSeek": 0, "dataSaverMode": False, "networkMeteredState": 0,
|
|
1377
|
+
"visibility": 0, "playbackRate": 0, "elapsedWallTimeMs": 0, "mediaCapabilities": bytearray(), "timeSinceLastActionMs": 0, "enabledTrackTypesBitfield": 0,
|
|
1378
|
+
"maxPacingRate": 0, "playerState": 0, "drcEnabled": False, "Jda": 0, "qw": 0, "Ky": 0, "sabrReportRequestCancellationInfo": 0, "l": False, "G7": 0, "preferVp9": False,
|
|
1379
|
+
"qj": 0, "Hx": 0, "isPrefetch": False, "sabrSupportQualityConstraints": 0, "sabrLicenseConstraint": bytearray(), "allowProximaLiveLatency": 0, "sabrForceProxima": 0,
|
|
1380
|
+
"Tqb": 0, "sabrForceMaxNetworkInterruptionDurationMs": 0, "audioTrackId": ""
|
|
1381
|
+
}
|
|
1382
|
+
'''encode'''
|
|
1383
|
+
@staticmethod
|
|
1384
|
+
def encode(message: dict, writer=None):
|
|
1385
|
+
if writer is None: writer = BinaryWriter()
|
|
1386
|
+
if message.get("timeSinceLastManualFormatSelectionMs", 0):
|
|
1387
|
+
writer.uint32(104).int64(message["timeSinceLastManualFormatSelectionMs"])
|
|
1388
|
+
if message.get("lastManualDirection", 0):
|
|
1389
|
+
writer.uint32(112).sint32(message["lastManualDirection"])
|
|
1390
|
+
if message.get("lastManualSelectedResolution", 0):
|
|
1391
|
+
writer.uint32(128).int32(message["lastManualSelectedResolution"])
|
|
1392
|
+
if message.get("detailedNetworkType", 0):
|
|
1393
|
+
writer.uint32(136).int32(message["detailedNetworkType"])
|
|
1394
|
+
if message.get("clientViewportWidth", 0):
|
|
1395
|
+
writer.uint32(144).int32(message["clientViewportWidth"])
|
|
1396
|
+
if message.get("clientViewportHeight", 0):
|
|
1397
|
+
writer.uint32(152).int32(message["clientViewportHeight"])
|
|
1398
|
+
if message.get("clientBitrateCapBytesPerSec", 0):
|
|
1399
|
+
writer.uint32(160).int64(message["clientBitrateCapBytesPerSec"])
|
|
1400
|
+
if message.get("stickyResolution", 0):
|
|
1401
|
+
writer.uint32(168).int32(message["stickyResolution"])
|
|
1402
|
+
if message.get("clientViewportIsFlexible", False):
|
|
1403
|
+
writer.uint32(176).bool(message["clientViewportIsFlexible"])
|
|
1404
|
+
if message.get("bandwidthEstimate", 0):
|
|
1405
|
+
writer.uint32(184).int64(message["bandwidthEstimate"])
|
|
1406
|
+
if message.get("minAudioQuality", 0):
|
|
1407
|
+
writer.uint32(192).int32(message["minAudioQuality"])
|
|
1408
|
+
if message.get("maxAudioQuality", 0):
|
|
1409
|
+
writer.uint32(200).int32(message["maxAudioQuality"])
|
|
1410
|
+
if message.get("videoQualitySetting", 0):
|
|
1411
|
+
writer.uint32(208).int32(message["videoQualitySetting"])
|
|
1412
|
+
if message.get("audioRoute", 0):
|
|
1413
|
+
writer.uint32(216).int32(message["audioRoute"])
|
|
1414
|
+
if message.get("playerTimeMs", 0):
|
|
1415
|
+
writer.uint32(224).int64(message["playerTimeMs"])
|
|
1416
|
+
if message.get("timeSinceLastSeek", 0):
|
|
1417
|
+
writer.uint32(232).int64(message["timeSinceLastSeek"])
|
|
1418
|
+
if message.get("dataSaverMode", False):
|
|
1419
|
+
writer.uint32(240).bool(message["dataSaverMode"])
|
|
1420
|
+
if message.get("networkMeteredState", 0):
|
|
1421
|
+
writer.uint32(256).int32(message["networkMeteredState"])
|
|
1422
|
+
if message.get("visibility", 0):
|
|
1423
|
+
writer.uint32(272).int32(message["visibility"])
|
|
1424
|
+
if message.get("playbackRate", 0):
|
|
1425
|
+
writer.uint32(285).float(message["playbackRate"])
|
|
1426
|
+
if message.get("elapsedWallTimeMs", 0):
|
|
1427
|
+
writer.uint32(288).int64(message["elapsedWallTimeMs"])
|
|
1428
|
+
if message.get("mediaCapabilities", b''):
|
|
1429
|
+
writer.uint32(306).bytes(message["mediaCapabilities"])
|
|
1430
|
+
if message.get("timeSinceLastActionMs", 0):
|
|
1431
|
+
writer.uint32(312).int64(message["timeSinceLastActionMs"])
|
|
1432
|
+
if message.get("enabledTrackTypesBitfield", 0):
|
|
1433
|
+
writer.uint32(320).int32(message["enabledTrackTypesBitfield"])
|
|
1434
|
+
if message.get("maxPacingRate", 0):
|
|
1435
|
+
writer.uint32(344).int32(message["maxPacingRate"])
|
|
1436
|
+
if message.get("playerState", 0):
|
|
1437
|
+
writer.uint32(352).int64(message["playerState"])
|
|
1438
|
+
if message.get("drcEnabled", False):
|
|
1439
|
+
writer.uint32(368).bool(message["drcEnabled"])
|
|
1440
|
+
if message.get("Jda", 0):
|
|
1441
|
+
writer.uint32(384).int32(message["Jda"])
|
|
1442
|
+
if message.get("qw", 0):
|
|
1443
|
+
writer.uint32(400).int32(message["qw"])
|
|
1444
|
+
if message.get("Ky", 0):
|
|
1445
|
+
writer.uint32(408).int32(message["Ky"])
|
|
1446
|
+
if message.get("sabrReportRequestCancellationInfo", 0):
|
|
1447
|
+
writer.uint32(432).int32(message["sabrReportRequestCancellationInfo"])
|
|
1448
|
+
if message.get("l", False):
|
|
1449
|
+
writer.uint32(448).bool(message["l"])
|
|
1450
|
+
if message.get("G7", 0):
|
|
1451
|
+
writer.uint32(456).int64(message["G7"])
|
|
1452
|
+
if message.get("preferVp9", False):
|
|
1453
|
+
writer.uint32(464).bool(message["preferVp9"])
|
|
1454
|
+
if message.get("qj", 0):
|
|
1455
|
+
writer.uint32(472).int32(message["qj"])
|
|
1456
|
+
if message.get("Hx", 0):
|
|
1457
|
+
writer.uint32(480).int32(message["Hx"])
|
|
1458
|
+
if message.get("isPrefetch", False):
|
|
1459
|
+
writer.uint32(488).bool(message["isPrefetch"])
|
|
1460
|
+
if message.get("sabrSupportQualityConstraints", 0):
|
|
1461
|
+
writer.uint32(496).int32(message["sabrSupportQualityConstraints"])
|
|
1462
|
+
if message.get("sabrLicenseConstraint", b''):
|
|
1463
|
+
writer.uint32(506).bytes(message["sabrLicenseConstraint"])
|
|
1464
|
+
if message.get("allowProximaLiveLatency", 0):
|
|
1465
|
+
writer.uint32(512).int32(message["allowProximaLiveLatency"])
|
|
1466
|
+
if message.get("sabrForceProxima", 0):
|
|
1467
|
+
writer.uint32(528).int32(message["sabrForceProxima"])
|
|
1468
|
+
if message.get("Tqb", 0):
|
|
1469
|
+
writer.uint32(536).int32(message["Tqb"])
|
|
1470
|
+
if message.get("sabrForceMaxNetworkInterruptionDurationMs", 0):
|
|
1471
|
+
writer.uint32(544).int64(message["sabrForceMaxNetworkInterruptionDurationMs"])
|
|
1472
|
+
if message.get("audioTrackId", ""):
|
|
1473
|
+
writer.uint32(554).string(message["audioTrackId"])
|
|
1474
|
+
return writer
|
|
1475
|
+
'''decode'''
|
|
1476
|
+
@staticmethod
|
|
1477
|
+
def decode(input_data, length=None):
|
|
1478
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1479
|
+
end = reader.len if length is None else reader.pos + length
|
|
1480
|
+
message = ClientAbrState.createbaseclientabrstate()
|
|
1481
|
+
while reader.pos < end:
|
|
1482
|
+
tag = reader.uint32()
|
|
1483
|
+
field_number = tag >> 3
|
|
1484
|
+
if field_number == 13 and tag == 104:
|
|
1485
|
+
message['timeSinceLastManualFormatSelectionMs'] = longtonumber(reader.int64())
|
|
1486
|
+
continue
|
|
1487
|
+
elif field_number == 14 and tag == 112:
|
|
1488
|
+
message['lastManualDirection'] = reader.sint32()
|
|
1489
|
+
continue
|
|
1490
|
+
elif field_number == 16 and tag == 128:
|
|
1491
|
+
message['lastManualSelectedResolution'] = reader.int32()
|
|
1492
|
+
continue
|
|
1493
|
+
elif field_number == 17 and tag == 136:
|
|
1494
|
+
message['detailedNetworkType'] = reader.int32()
|
|
1495
|
+
continue
|
|
1496
|
+
elif field_number == 18 and tag == 144:
|
|
1497
|
+
message['clientViewportWidth'] = reader.int32()
|
|
1498
|
+
continue
|
|
1499
|
+
elif field_number == 19 and tag == 152:
|
|
1500
|
+
message['clientViewportHeight'] = reader.int32()
|
|
1501
|
+
continue
|
|
1502
|
+
elif field_number == 20 and tag == 160:
|
|
1503
|
+
message['clientBitrateCapBytesPerSec'] = longtonumber(reader.int64())
|
|
1504
|
+
continue
|
|
1505
|
+
elif field_number == 21 and tag == 168:
|
|
1506
|
+
message['stickyResolution'] = reader.int32()
|
|
1507
|
+
continue
|
|
1508
|
+
elif field_number == 22 and tag == 176:
|
|
1509
|
+
message['clientViewportIsFlexible'] = reader.bool()
|
|
1510
|
+
continue
|
|
1511
|
+
elif field_number == 23 and tag == 184:
|
|
1512
|
+
message['bandwidthEstimate'] = longtonumber(reader.int64())
|
|
1513
|
+
continue
|
|
1514
|
+
elif field_number == 24 and tag == 192:
|
|
1515
|
+
message['minAudioQuality'] = reader.int32()
|
|
1516
|
+
continue
|
|
1517
|
+
elif field_number == 25 and tag == 200:
|
|
1518
|
+
message['maxAudioQuality'] = reader.int32()
|
|
1519
|
+
continue
|
|
1520
|
+
elif field_number == 26 and tag == 208:
|
|
1521
|
+
message['videoQualitySetting'] = reader.int32()
|
|
1522
|
+
continue
|
|
1523
|
+
elif field_number == 27 and tag == 216:
|
|
1524
|
+
message['audioRoute'] = reader.int32()
|
|
1525
|
+
continue
|
|
1526
|
+
elif field_number == 28 and tag == 224:
|
|
1527
|
+
message['playerTimeMs'] = longtonumber(reader.int64())
|
|
1528
|
+
continue
|
|
1529
|
+
elif field_number == 29 and tag == 232:
|
|
1530
|
+
message['timeSinceLastSeek'] = longtonumber(reader.int64())
|
|
1531
|
+
continue
|
|
1532
|
+
elif field_number == 30 and tag == 240:
|
|
1533
|
+
message['dataSaverMode'] = reader.bool()
|
|
1534
|
+
continue
|
|
1535
|
+
elif field_number == 32 and tag == 256:
|
|
1536
|
+
message['networkMeteredState'] = reader.int32()
|
|
1537
|
+
continue
|
|
1538
|
+
elif field_number == 34 and tag == 272:
|
|
1539
|
+
message['visibility'] = reader.int32()
|
|
1540
|
+
continue
|
|
1541
|
+
elif field_number == 35 and tag == 285:
|
|
1542
|
+
message['playbackRate'] = reader.float()
|
|
1543
|
+
continue
|
|
1544
|
+
elif field_number == 36 and tag == 288:
|
|
1545
|
+
message['elapsedWallTimeMs'] = longtonumber(reader.int64())
|
|
1546
|
+
continue
|
|
1547
|
+
elif field_number == 38 and tag == 306:
|
|
1548
|
+
message['mediaCapabilities'] = reader.bytes()
|
|
1549
|
+
continue
|
|
1550
|
+
elif field_number == 39 and tag == 312:
|
|
1551
|
+
message['timeSinceLastActionMs'] = longtonumber(reader.int64())
|
|
1552
|
+
continue
|
|
1553
|
+
elif field_number == 40 and tag == 320:
|
|
1554
|
+
message['enabledTrackTypesBitfield'] = reader.int32()
|
|
1555
|
+
continue
|
|
1556
|
+
elif field_number == 43 and tag == 344:
|
|
1557
|
+
message['maxPacingRate'] = reader.int32()
|
|
1558
|
+
continue
|
|
1559
|
+
elif field_number == 44 and tag == 352:
|
|
1560
|
+
message['playerState'] = longtonumber(reader.int64())
|
|
1561
|
+
continue
|
|
1562
|
+
elif field_number == 46 and tag == 368:
|
|
1563
|
+
message['drcEnabled'] = reader.bool()
|
|
1564
|
+
continue
|
|
1565
|
+
elif field_number == 48 and tag == 384:
|
|
1566
|
+
message['Jda'] = reader.int32()
|
|
1567
|
+
continue
|
|
1568
|
+
elif field_number == 50 and tag == 400:
|
|
1569
|
+
message['qw'] = reader.int32()
|
|
1570
|
+
continue
|
|
1571
|
+
elif field_number == 51 and tag == 408:
|
|
1572
|
+
message['Ky'] = reader.int32()
|
|
1573
|
+
continue
|
|
1574
|
+
elif field_number == 54 and tag == 432:
|
|
1575
|
+
message['sabrReportRequestCancellationInfo'] = reader.int32()
|
|
1576
|
+
continue
|
|
1577
|
+
elif field_number == 56 and tag == 448:
|
|
1578
|
+
message['l'] = reader.bool()
|
|
1579
|
+
continue
|
|
1580
|
+
elif field_number == 57 and tag == 456:
|
|
1581
|
+
message['G7'] = longtonumber(reader.int64())
|
|
1582
|
+
continue
|
|
1583
|
+
elif field_number == 58 and tag == 464:
|
|
1584
|
+
message['preferVp9'] = reader.bool()
|
|
1585
|
+
continue
|
|
1586
|
+
elif field_number == 59 and tag == 472:
|
|
1587
|
+
message['qj'] = reader.int32()
|
|
1588
|
+
continue
|
|
1589
|
+
elif field_number == 60 and tag == 480:
|
|
1590
|
+
message['Hx'] = reader.int32()
|
|
1591
|
+
continue
|
|
1592
|
+
elif field_number == 61 and tag == 488:
|
|
1593
|
+
message['isPrefetch'] = reader.bool()
|
|
1594
|
+
continue
|
|
1595
|
+
elif field_number == 62 and tag == 496:
|
|
1596
|
+
message['sabrSupportQualityConstraints'] = reader.int32()
|
|
1597
|
+
continue
|
|
1598
|
+
elif field_number == 63 and tag == 506:
|
|
1599
|
+
message['sabrLicenseConstraint'] = reader.bytes()
|
|
1600
|
+
continue
|
|
1601
|
+
elif field_number == 64 and tag == 512:
|
|
1602
|
+
message['allowProximaLiveLatency'] = reader.int32()
|
|
1603
|
+
continue
|
|
1604
|
+
elif field_number == 66 and tag == 528:
|
|
1605
|
+
message['sabrForceProxima'] = reader.int32()
|
|
1606
|
+
continue
|
|
1607
|
+
elif field_number == 67 and tag == 536:
|
|
1608
|
+
message['Tqb'] = reader.int32()
|
|
1609
|
+
continue
|
|
1610
|
+
elif field_number == 68 and tag == 544:
|
|
1611
|
+
message['sabrForceMaxNetworkInterruptionDurationMs'] = longtonumber(reader.int64())
|
|
1612
|
+
continue
|
|
1613
|
+
elif field_number == 69 and tag == 554:
|
|
1614
|
+
message['audioTrackId'] = reader.string()
|
|
1615
|
+
continue
|
|
1616
|
+
else:
|
|
1617
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
1618
|
+
reader.skip(tag & 7)
|
|
1619
|
+
return message
|
|
1620
|
+
|
|
1621
|
+
|
|
1622
|
+
'''FormatId'''
|
|
1623
|
+
class FormatId:
|
|
1624
|
+
'''encode'''
|
|
1625
|
+
@staticmethod
|
|
1626
|
+
def encode(message: dict, writer=None):
|
|
1627
|
+
if writer is None: writer = BinaryWriter()
|
|
1628
|
+
if message.get("itag", 0) != 0: writer.uint32(8).int32(message["itag"])
|
|
1629
|
+
if message.get("lastModified", 0) != 0: writer.uint32(16).uint64(message["lastModified"])
|
|
1630
|
+
if message.get("xtags", None) is not None: writer.uint32(26).string(message["xtags"])
|
|
1631
|
+
return writer
|
|
1632
|
+
'''decode'''
|
|
1633
|
+
@staticmethod
|
|
1634
|
+
def decode(input_data, length=None):
|
|
1635
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1636
|
+
end = reader.len if length is None else reader.pos + length
|
|
1637
|
+
message = {"itag": 0, "lastModified": 0, "xtags": None}
|
|
1638
|
+
while reader.pos < end:
|
|
1639
|
+
tag = reader.uint32()
|
|
1640
|
+
field_no = tag >> 3
|
|
1641
|
+
if field_no == 1 and tag == 8: message["itag"] = reader.int32(); continue
|
|
1642
|
+
elif field_no == 2 and tag == 16: message["lastModified"] = reader.uint64(); continue
|
|
1643
|
+
elif field_no == 3 and tag == 26: message["xtags"] = reader.string(); continue
|
|
1644
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
1645
|
+
reader.skip(tag & 7)
|
|
1646
|
+
return message
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
'''InitRange'''
|
|
1650
|
+
class InitRange:
|
|
1651
|
+
def __init__(self, start=0, end=0):
|
|
1652
|
+
self.start = start
|
|
1653
|
+
self.end = end
|
|
1654
|
+
'''encode'''
|
|
1655
|
+
@staticmethod
|
|
1656
|
+
def encode(message, writer=None):
|
|
1657
|
+
if writer is None: writer = BinaryWriter()
|
|
1658
|
+
if message.start != 0:
|
|
1659
|
+
writer.uint32(8)
|
|
1660
|
+
writer.int32(message.start)
|
|
1661
|
+
if message.end != 0:
|
|
1662
|
+
writer.uint32(16)
|
|
1663
|
+
writer.int32(message.end)
|
|
1664
|
+
return writer
|
|
1665
|
+
'''decode'''
|
|
1666
|
+
@staticmethod
|
|
1667
|
+
def decode(input_data, length=None):
|
|
1668
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1669
|
+
end = reader.len if length is None else reader.pos + length
|
|
1670
|
+
message = InitRange()
|
|
1671
|
+
while reader.pos < end:
|
|
1672
|
+
tag = reader.uint32()
|
|
1673
|
+
field_no = tag >> 3
|
|
1674
|
+
if field_no == 1 and tag == 8: message.start = reader.int32(); continue
|
|
1675
|
+
elif field_no == 2 and tag == 16: message.end = reader.int32(); continue
|
|
1676
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
1677
|
+
reader.skip(tag & 7)
|
|
1678
|
+
return message
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
'''IndexRange'''
|
|
1682
|
+
class IndexRange:
|
|
1683
|
+
'''encode'''
|
|
1684
|
+
@staticmethod
|
|
1685
|
+
def encode(message: dict, writer=None):
|
|
1686
|
+
if writer is None: writer = BinaryWriter()
|
|
1687
|
+
if message.get("start", 0) != 0: writer.uint32(8).int32(message["start"])
|
|
1688
|
+
if message.get("end", 0) != 0: writer.uint32(16).int32(message["end"])
|
|
1689
|
+
return writer
|
|
1690
|
+
'''decode'''
|
|
1691
|
+
@staticmethod
|
|
1692
|
+
def decode(input_data, length=None):
|
|
1693
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1694
|
+
end = reader.len if length is None else reader.pos + length
|
|
1695
|
+
message = {"start": 0, "end": 0}
|
|
1696
|
+
while reader.pos < end:
|
|
1697
|
+
tag = reader.uint32()
|
|
1698
|
+
field_no = tag >> 3
|
|
1699
|
+
if field_no == 1 and tag == 8: message["start"] = reader.int32(); continue
|
|
1700
|
+
elif field_no == 2 and tag == 16: message["end"] = reader.int32(); continue
|
|
1701
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
1702
|
+
reader.skip(tag & 7)
|
|
1703
|
+
return message
|
|
1704
|
+
|
|
1705
|
+
|
|
1706
|
+
'''Lo'''
|
|
1707
|
+
class Lo:
|
|
1708
|
+
def __init__(self):
|
|
1709
|
+
self.format_id: Optional[FormatId] = None
|
|
1710
|
+
self.Lj: int = 0
|
|
1711
|
+
self.sequenceNumber: int = 0
|
|
1712
|
+
self.field4: Optional[LoField4] = None
|
|
1713
|
+
self.MZ: int = 0
|
|
1714
|
+
'''encode'''
|
|
1715
|
+
@staticmethod
|
|
1716
|
+
def encode(message: dict, writer=None):
|
|
1717
|
+
if writer is None: writer = BinaryWriter()
|
|
1718
|
+
for v in message.get("field1", []): writer.uint32(10).string(v)
|
|
1719
|
+
if "field2" in message and message["field2"] is not None: writer.uint32(18).int32(message["field2"])
|
|
1720
|
+
if "field3" in message and message["field3"] is not None: writer.uint32(26).int32(message["field3"])
|
|
1721
|
+
if "field4" in message and message["field4"] is not None: writer.uint32(32).int32(message["field4"])
|
|
1722
|
+
if "field5" in message and message["field5"] is not None: writer.uint32(40).int32(message["field5"])
|
|
1723
|
+
if "field6" in message and message["field6"] is not None: writer.uint32(50).int32(message["field6"])
|
|
1724
|
+
return writer
|
|
1725
|
+
'''decode'''
|
|
1726
|
+
@staticmethod
|
|
1727
|
+
def decode(input_data, length=None):
|
|
1728
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1729
|
+
end = reader.len if length is None else reader.pos + length
|
|
1730
|
+
message = Lo()
|
|
1731
|
+
while reader.pos < end:
|
|
1732
|
+
tag = reader.uint32()
|
|
1733
|
+
field_number = tag >> 3
|
|
1734
|
+
if field_number == 1 and tag == 10: message.format_id = FormatId.decode(reader, reader.uint32()); continue
|
|
1735
|
+
elif field_number == 2 and tag == 16: message.Lj = reader.int32(); continue
|
|
1736
|
+
elif field_number == 3 and tag == 24: message.sequenceNumber = reader.int32(); continue
|
|
1737
|
+
elif field_number == 4 and tag == 34: message.field4 = LoField4.decode(reader, reader.uint32()); continue
|
|
1738
|
+
elif field_number == 5 and tag == 40: message.MZ = reader.int32(); continue
|
|
1739
|
+
return message
|
|
1740
|
+
|
|
1741
|
+
|
|
1742
|
+
'''LoField4'''
|
|
1743
|
+
class LoField4:
|
|
1744
|
+
def __init__(self):
|
|
1745
|
+
self.field1: int = 0
|
|
1746
|
+
self.field2: int = 0
|
|
1747
|
+
self.field3: int = 0
|
|
1748
|
+
'''encode'''
|
|
1749
|
+
@staticmethod
|
|
1750
|
+
def encode(message: dict, writer=None):
|
|
1751
|
+
if writer is None: writer = BinaryWriter()
|
|
1752
|
+
if "field1" in message and message["field1"] is not None: writer.uint32(8).int32(message["field1"])
|
|
1753
|
+
if "field2" in message and message["field2"] is not None: writer.uint32(16).int32(message["field2"])
|
|
1754
|
+
if "field3" in message and message["field3"] is not None: writer.uint32(24).int32(message["field3"])
|
|
1755
|
+
return writer
|
|
1756
|
+
'''decode'''
|
|
1757
|
+
@staticmethod
|
|
1758
|
+
def decode(input_data, length=None):
|
|
1759
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1760
|
+
end = reader.len if length is None else reader.pos + length
|
|
1761
|
+
message = LoField4()
|
|
1762
|
+
while reader.pos < end:
|
|
1763
|
+
tag = reader.uint32()
|
|
1764
|
+
field_number = tag >> 3
|
|
1765
|
+
if field_number == 1 and tag == 8: message.field1 = reader.int32(); continue
|
|
1766
|
+
elif field_number == 2 and tag == 16: message.field2 = reader.int32(); continue
|
|
1767
|
+
elif field_number == 3 and tag == 24: message.field3 = reader.int32(); continue
|
|
1768
|
+
return message
|
|
1769
|
+
|
|
1770
|
+
|
|
1771
|
+
'''OQa'''
|
|
1772
|
+
class OQa:
|
|
1773
|
+
def __init__(self):
|
|
1774
|
+
self.field1: List[str] = []
|
|
1775
|
+
self.field2: bytes = bytes()
|
|
1776
|
+
self.field3: str = ""
|
|
1777
|
+
self.field4: int = 0
|
|
1778
|
+
self.field5: int = 0
|
|
1779
|
+
self.field6: str = ""
|
|
1780
|
+
'''encode'''
|
|
1781
|
+
@staticmethod
|
|
1782
|
+
def encode(message: dict, writer=None):
|
|
1783
|
+
if writer is None: writer = BinaryWriter()
|
|
1784
|
+
if "field1" in message and message["field1"] is not None: writer.uint32(8).int32(message["field1"])
|
|
1785
|
+
if "field2" in message and message["field2"] is not None: writer.uint32(16).int32(message["field2"])
|
|
1786
|
+
if "field3" in message and message["field3"] is not None: writer.uint32(24).int32(message["field3"])
|
|
1787
|
+
return writer
|
|
1788
|
+
'''decode'''
|
|
1789
|
+
@staticmethod
|
|
1790
|
+
def decode(input_data, length=None):
|
|
1791
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1792
|
+
end = reader.len if length is None else reader.pos + length
|
|
1793
|
+
message = OQa()
|
|
1794
|
+
while reader.pos < end:
|
|
1795
|
+
tag = reader.uint32()
|
|
1796
|
+
field_number = tag >> 3
|
|
1797
|
+
if field_number == 1 and tag == 10: message.field1.append(reader.string()); continue
|
|
1798
|
+
elif field_number == 2 and tag == 18: message.field2 = reader.bytes(); continue
|
|
1799
|
+
elif field_number == 3 and tag == 26: message.field3 = reader.string(); continue
|
|
1800
|
+
elif field_number == 4 and tag == 32: message.field4 = reader.int32(); continue
|
|
1801
|
+
elif field_number == 5 and tag == 40: message.field5 = reader.int32(); continue
|
|
1802
|
+
elif field_number == 6 and tag == 50: message.field6 = reader.string(); continue
|
|
1803
|
+
return message
|
|
1804
|
+
|
|
1805
|
+
|
|
1806
|
+
'''KobPa'''
|
|
1807
|
+
class KobPa:
|
|
1808
|
+
def __init__(self):
|
|
1809
|
+
self.videoId = ""
|
|
1810
|
+
self.lmt = 0
|
|
1811
|
+
'''encode'''
|
|
1812
|
+
@staticmethod
|
|
1813
|
+
def encode(message: dict, writer=None):
|
|
1814
|
+
writer = writer or BinaryWriter()
|
|
1815
|
+
if message.get("videoId"): writer.uint32(10).string(message["videoId"])
|
|
1816
|
+
if message.get("lmt", 0) != 0: writer.uint32(16).uint64(message["lmt"])
|
|
1817
|
+
return writer
|
|
1818
|
+
'''decode'''
|
|
1819
|
+
@staticmethod
|
|
1820
|
+
def decode(input_data, length=None):
|
|
1821
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1822
|
+
end = reader.len if length is None else reader.pos + length
|
|
1823
|
+
message = KobPa()
|
|
1824
|
+
while reader.pos < end:
|
|
1825
|
+
tag = reader.uint32()
|
|
1826
|
+
field_num = tag >> 3
|
|
1827
|
+
if field_num == 1 and tag == 10: message.videoId = reader.string(); continue
|
|
1828
|
+
elif field_num == 2 and tag == 16: message.lmt = longtonumber(reader.uint64())
|
|
1829
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
1830
|
+
else: reader.skip(tag & 7)
|
|
1831
|
+
return message
|
|
1832
|
+
|
|
1833
|
+
|
|
1834
|
+
'''Kob'''
|
|
1835
|
+
class Kob:
|
|
1836
|
+
def __init__(self):
|
|
1837
|
+
self.EW = []
|
|
1838
|
+
'''encode'''
|
|
1839
|
+
@staticmethod
|
|
1840
|
+
def encode(message: dict, writer=None):
|
|
1841
|
+
writer = writer or BinaryWriter()
|
|
1842
|
+
for v in message.get("EW", []): KobPa.encode(v, writer.uint32(10).fork()).join()
|
|
1843
|
+
return writer
|
|
1844
|
+
'''decode'''
|
|
1845
|
+
@staticmethod
|
|
1846
|
+
def decode(input_data, length=None):
|
|
1847
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1848
|
+
end = reader.len if length is None else reader.pos + length
|
|
1849
|
+
message = Kob()
|
|
1850
|
+
while reader.pos < end:
|
|
1851
|
+
tag = reader.uint32()
|
|
1852
|
+
field_num = tag >> 3
|
|
1853
|
+
if field_num == 1 and tag == 10: message.EW.append(KobPa.decode(reader, reader.uint32())); continue
|
|
1854
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
1855
|
+
else: reader.skip(tag & 7)
|
|
1856
|
+
return message
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
'''YPa'''
|
|
1860
|
+
class YPa:
|
|
1861
|
+
def __init__(self):
|
|
1862
|
+
self.field1 = 0
|
|
1863
|
+
self.field2 = 0
|
|
1864
|
+
self.field3 = 0
|
|
1865
|
+
'''encode'''
|
|
1866
|
+
@staticmethod
|
|
1867
|
+
def encode(message: dict, writer=None):
|
|
1868
|
+
writer = writer or BinaryWriter()
|
|
1869
|
+
if message.get("field1", 0) != 0: writer.uint32(8).int32(message["field1"])
|
|
1870
|
+
if message.get("field2", 0) != 0: writer.uint32(16).int32(message["field2"])
|
|
1871
|
+
if message.get("field3", 0) != 0: writer.uint32(24).int32(message["field3"])
|
|
1872
|
+
return writer
|
|
1873
|
+
'''decode'''
|
|
1874
|
+
@staticmethod
|
|
1875
|
+
def decode(input_data, length=None):
|
|
1876
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1877
|
+
end = reader.len if length is None else reader.pos + length
|
|
1878
|
+
message = YPa()
|
|
1879
|
+
while reader.pos < end:
|
|
1880
|
+
tag = reader.uint32()
|
|
1881
|
+
field_num = tag >> 3
|
|
1882
|
+
if field_num == 1 and tag == 8: message.field1 = reader.int32(); continue
|
|
1883
|
+
elif field_num == 2 and tag == 16: message.field2 = reader.int32(); continue
|
|
1884
|
+
elif field_num == 3 and tag == 24: message.field3 = reader.int32(); continue
|
|
1885
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
1886
|
+
else: reader.skip(tag & 7)
|
|
1887
|
+
return message
|
|
1888
|
+
|
|
1889
|
+
|
|
1890
|
+
'''TimeRange'''
|
|
1891
|
+
class TimeRange:
|
|
1892
|
+
def __init__(self):
|
|
1893
|
+
self.start: int = 0
|
|
1894
|
+
self.duration: int = 0
|
|
1895
|
+
self.timescale: int = 0
|
|
1896
|
+
'''decode'''
|
|
1897
|
+
@staticmethod
|
|
1898
|
+
def decode(input_data, length=None):
|
|
1899
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1900
|
+
end = reader.len if length is None else reader.pos + length
|
|
1901
|
+
message = TimeRange
|
|
1902
|
+
while reader.pos < end:
|
|
1903
|
+
tag = reader.uint32()
|
|
1904
|
+
field = tag >> 3
|
|
1905
|
+
if field == 1 and tag == 8: message.start = longtonumber(reader.int64()); continue
|
|
1906
|
+
elif field == 2 and tag == 16: message.duration = longtonumber(reader.int64()); continue
|
|
1907
|
+
elif field == 3 and tag == 24: message.timescale = reader.int32(); continue
|
|
1908
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
1909
|
+
else: reader.skip(tag & 7)
|
|
1910
|
+
return message
|
|
1911
|
+
'''encode'''
|
|
1912
|
+
def encode(self, writer: Optional[BinaryWriter] = None):
|
|
1913
|
+
writer = writer or BinaryWriter()
|
|
1914
|
+
if self.start != 0: writer.uint32(8).int64(self.start)
|
|
1915
|
+
if self.duration != 0: writer.uint32(16).int64(self.duration)
|
|
1916
|
+
if self.timescale != 0: writer.uint32(24).int32(self.timescale)
|
|
1917
|
+
return writer
|
|
1918
|
+
|
|
1919
|
+
|
|
1920
|
+
'''BufferedRange'''
|
|
1921
|
+
class BufferedRange:
|
|
1922
|
+
'''encode'''
|
|
1923
|
+
@staticmethod
|
|
1924
|
+
def encode(message: dict, writer=None):
|
|
1925
|
+
writer = writer or BinaryWriter()
|
|
1926
|
+
if message.get("formatId") is not None: FormatId.encode(message["formatId"], writer.uint32(10).fork()).join()
|
|
1927
|
+
if message.get("startTimeMs", 0) != 0: writer.uint32(16).int64(message["startTimeMs"])
|
|
1928
|
+
if message.get("durationMs", 0) != 0: writer.uint32(24).int64(message["durationMs"])
|
|
1929
|
+
if message.get("startSegmentIndex", 0) != 0: writer.uint32(32).int32(message["startSegmentIndex"])
|
|
1930
|
+
if message.get("endSegmentIndex", 0) != 0: writer.uint32(40).int32(message["endSegmentIndex"])
|
|
1931
|
+
if message.get("timeRange") is not None: TimeRange.encode(message["timeRange"], writer.uint32(50).fork()).join()
|
|
1932
|
+
if message.get("field9") is not None: Kob.encode(message["field9"], writer.uint32(74).fork()).join()
|
|
1933
|
+
if message.get("field11") is not None: YPa.encode(message["field11"], writer.uint32(90).fork()).join()
|
|
1934
|
+
if message.get("field12") is not None: YPa.encode(message["field12"], writer.uint32(98).fork()).join()
|
|
1935
|
+
return writer
|
|
1936
|
+
'''decode'''
|
|
1937
|
+
@staticmethod
|
|
1938
|
+
def decode(input_data, length=None):
|
|
1939
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1940
|
+
end = reader.len if length is None else reader.pos + length
|
|
1941
|
+
message = {"formatId": None, "startTimeMs": 0, "durationMs": 0, "startSegmentIndex": 0, "endSegmentIndex": 0, "timeRange": None, "field9": None, "field11": None, "field12": None}
|
|
1942
|
+
while reader.pos < end:
|
|
1943
|
+
tag = reader.uint32()
|
|
1944
|
+
field_num = tag >> 3
|
|
1945
|
+
if field_num == 1 and tag == 10: message["formatId"] = FormatId.decode(reader, reader.uint32()); continue
|
|
1946
|
+
elif field_num == 2 and tag == 16: message["startTimeMs"] = longtonumber(reader.int64()); continue
|
|
1947
|
+
elif field_num == 3 and tag == 24: message["durationMs"] = longtonumber(reader.int64()); continue
|
|
1948
|
+
elif field_num == 4 and tag == 32: message["startSegmentIndex"] = reader.int32(); continue
|
|
1949
|
+
elif field_num == 5 and tag == 40: message["endSegmentIndex"] = reader.int32(); continue
|
|
1950
|
+
elif field_num == 6 and tag == 50: message["timeRange"] = TimeRange.decode(reader, reader.uint32()); continue
|
|
1951
|
+
elif field_num == 9 and tag == 74: message["field9"] = Kob.decode(reader, reader.uint32()); continue
|
|
1952
|
+
elif field_num == 11 and tag == 90: message["field11"] = YPa.decode(reader, reader.uint32()); continue
|
|
1953
|
+
elif field_num == 12 and tag == 98: message["field12"] = YPa.decode(reader, reader.uint32()); continue
|
|
1954
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
1955
|
+
else: reader.skip(tag & 7)
|
|
1956
|
+
return message
|
|
1957
|
+
|
|
1958
|
+
|
|
1959
|
+
'''Pqa'''
|
|
1960
|
+
class Pqa:
|
|
1961
|
+
def __init__(self):
|
|
1962
|
+
self.formats: List[FormatId] = []
|
|
1963
|
+
self.ud: List[BufferedRange] = []
|
|
1964
|
+
self.clip_id: str = ""
|
|
1965
|
+
'''encode'''
|
|
1966
|
+
@staticmethod
|
|
1967
|
+
def encode(message: dict, writer=None):
|
|
1968
|
+
if writer is None: writer = BinaryWriter()
|
|
1969
|
+
for v in message.get("formats", []): FormatId.encode(v, writer.uint32(10).fork()).join()
|
|
1970
|
+
for v in message.get("ud", []): BufferedRange.encode(v, writer.uint32(18).fork()).join()
|
|
1971
|
+
if "clipId" in message and message["clipId"] is not None: writer.uint32(26).int32(message["clipId"])
|
|
1972
|
+
return writer
|
|
1973
|
+
'''decode'''
|
|
1974
|
+
@staticmethod
|
|
1975
|
+
def decode(input_data, length=None):
|
|
1976
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
1977
|
+
message = Pqa()
|
|
1978
|
+
end = reader.len if length is None else reader.pos + length
|
|
1979
|
+
while reader.pos < end:
|
|
1980
|
+
tag = reader.uint32()
|
|
1981
|
+
field_number = tag >> 3
|
|
1982
|
+
if field_number == 1 and tag == 10: message.formats.append(FormatId.decode(reader, reader.uint32())); continue
|
|
1983
|
+
elif field_number == 2 and tag == 18: message.ud.append(BufferedRange.decode(reader, reader.uint32())); continue
|
|
1984
|
+
elif field_number == 3 and tag == 26: message.clip_id = reader.string(); continue
|
|
1985
|
+
return message
|
|
1986
|
+
|
|
1987
|
+
|
|
1988
|
+
'''PlaybackCookie'''
|
|
1989
|
+
class PlaybackCookie:
|
|
1990
|
+
'''createbase'''
|
|
1991
|
+
@staticmethod
|
|
1992
|
+
def createbase():
|
|
1993
|
+
return {"field1": 0, "field2": 0, "videoFmt": None, "audioFmt": None}
|
|
1994
|
+
'''encode'''
|
|
1995
|
+
@staticmethod
|
|
1996
|
+
def encode(message: dict, writer=None):
|
|
1997
|
+
if writer is None: writer = BinaryWriter()
|
|
1998
|
+
if message.get("field1", 0) != 0: writer.uint32(8).int32(message["field1"])
|
|
1999
|
+
if message.get("field2", 0) != 0: writer.uint32(16).int32(message["field2"])
|
|
2000
|
+
if message.get("videoFmt") is not None: FormatId.encode(message["videoFmt"], writer.uint32(58).fork()).join()
|
|
2001
|
+
if message.get("audioFmt") is not None: FormatId.encode(message["audioFmt"], writer.uint32(66).fork()).join()
|
|
2002
|
+
return writer
|
|
2003
|
+
'''decode'''
|
|
2004
|
+
@staticmethod
|
|
2005
|
+
def decode(data, length=None):
|
|
2006
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2007
|
+
end = reader.len if length is None else reader.pos + length
|
|
2008
|
+
message = PlaybackCookie.createbase()
|
|
2009
|
+
while reader.pos < end:
|
|
2010
|
+
tag = reader.uint32()
|
|
2011
|
+
field = tag >> 3
|
|
2012
|
+
if field == 1 and tag == 8: message["field1"] = reader.int32(); continue
|
|
2013
|
+
elif field == 2 and tag == 16: message["field2"] = reader.int32(); continue
|
|
2014
|
+
elif field == 7 and tag == 58: message["videoFmt"] = FormatId.decode(reader, reader.uint32()); continue
|
|
2015
|
+
elif field == 8 and tag == 66: message["audioFmt"] = FormatId.decode(reader, reader.uint32()); continue
|
|
2016
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2017
|
+
else: reader.skip(tag & 7)
|
|
2018
|
+
return message
|
|
2019
|
+
|
|
2020
|
+
|
|
2021
|
+
'''StreamerContextClientInfo'''
|
|
2022
|
+
class StreamerContextClientInfo:
|
|
2023
|
+
def __init__(self):
|
|
2024
|
+
self.locale = None
|
|
2025
|
+
self.deviceMake = None
|
|
2026
|
+
self.deviceModel = None
|
|
2027
|
+
self.clientName = None
|
|
2028
|
+
self.clientVersion = None
|
|
2029
|
+
self.osName = None
|
|
2030
|
+
self.osVersion = None
|
|
2031
|
+
self.acceptLanguage = None
|
|
2032
|
+
self.acceptRegion = None
|
|
2033
|
+
self.screenWidthPoints = None
|
|
2034
|
+
self.screenHeightPoints = None
|
|
2035
|
+
self.screenWidthInches = None
|
|
2036
|
+
self.screenHeightInches = None
|
|
2037
|
+
self.screenPixelDensity = None
|
|
2038
|
+
self.clientFormFactor = None
|
|
2039
|
+
self.gmscoreVersionCode = None
|
|
2040
|
+
self.windowWidthPoints = None
|
|
2041
|
+
self.windowHeightPoints = None
|
|
2042
|
+
self.androidSdkVersion = None
|
|
2043
|
+
self.screenDensityFloat = None
|
|
2044
|
+
self.utcOffsetMinutes = None
|
|
2045
|
+
self.timeZone = None
|
|
2046
|
+
self.chipset = None
|
|
2047
|
+
self.glDeviceInfo = None
|
|
2048
|
+
'''encode'''
|
|
2049
|
+
@staticmethod
|
|
2050
|
+
def encode(message: dict, writer=None):
|
|
2051
|
+
if writer is None: writer = BinaryWriter()
|
|
2052
|
+
if message.get("deviceMake", ""): writer.uint32(98).string(message["deviceMake"])
|
|
2053
|
+
if message.get("deviceModel", ""): writer.uint32(106).string(message["deviceModel"])
|
|
2054
|
+
if message.get("clientName", 0): writer.uint32(128).int32(message["clientName"])
|
|
2055
|
+
if message.get("clientVersion", ""): writer.uint32(138).string(message["clientVersion"])
|
|
2056
|
+
if message.get("osName", ""): writer.uint32(146).string(message["osName"])
|
|
2057
|
+
if message.get("osVersion", ""): writer.uint32(154).string(message["osVersion"])
|
|
2058
|
+
if message.get("acceptLanguage", ""): writer.uint32(170).string(message["acceptLanguage"])
|
|
2059
|
+
if message.get("acceptRegion", ""): writer.uint32(178).string(message["acceptRegion"])
|
|
2060
|
+
if message.get("screenWidthPoints", 0): writer.uint32(296).int32(message["screenWidthPoints"])
|
|
2061
|
+
if message.get("screenHeightPoints", 0): writer.uint32(304).int32(message["screenHeightPoints"])
|
|
2062
|
+
if message.get("screenWidthInches", 0): writer.uint32(317).float(message["screenWidthInches"])
|
|
2063
|
+
if message.get("screenHeightInches", 0): writer.uint32(325).float(message["screenHeightInches"])
|
|
2064
|
+
if message.get("screenPixelDensity", 0): writer.uint32(328).int32(message["screenPixelDensity"])
|
|
2065
|
+
if message.get("clientFormFactor", 0): writer.uint32(368).int32(message["clientFormFactor"])
|
|
2066
|
+
if message.get("gmscoreVersionCode", 0): writer.uint32(400).int32(message["gmscoreVersionCode"])
|
|
2067
|
+
if message.get("windowWidthPoints", 0): writer.uint32(440).int32(message["windowWidthPoints"])
|
|
2068
|
+
if message.get("windowHeightPoints", 0): writer.uint32(448).int32(message["windowHeightPoints"])
|
|
2069
|
+
if message.get("androidSdkVersion", 0): writer.uint32(512).int32(message["androidSdkVersion"])
|
|
2070
|
+
if message.get("screenDensityFloat", 0): writer.uint32(525).float(message["screenDensityFloat"])
|
|
2071
|
+
if message.get("utcOffsetMinutes", 0): writer.uint32(536).int64(message["utcOffsetMinutes"])
|
|
2072
|
+
if message.get("timeZone", ""): writer.uint32(642).string(message["timeZone"])
|
|
2073
|
+
if message.get("chipset", ""): writer.uint32(738).string(message["chipset"])
|
|
2074
|
+
return writer
|
|
2075
|
+
'''decode'''
|
|
2076
|
+
@staticmethod
|
|
2077
|
+
def decode(input_data, length=None):
|
|
2078
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
2079
|
+
end = reader.len if length is None else reader.pos + length
|
|
2080
|
+
message = StreamerContextClientInfo()
|
|
2081
|
+
while reader.pos < end:
|
|
2082
|
+
tag = reader.uint32()
|
|
2083
|
+
field_number = tag >> 3
|
|
2084
|
+
if field_number == 1 and tag == 10: message.locale = reader.string(); continue
|
|
2085
|
+
if field_number == 12 and tag == 98: message.deviceMake = reader.string(); continue
|
|
2086
|
+
elif field_number == 13 and tag == 106: message.deviceModel = reader.string(); continue
|
|
2087
|
+
elif field_number == 16 and tag == 128: message.clientName = reader.int32(); continue
|
|
2088
|
+
elif field_number == 17 and tag == 138: message.clientVersion = reader.string(); continue
|
|
2089
|
+
elif field_number == 18 and tag == 146: message.osName = reader.string(); continue
|
|
2090
|
+
elif field_number == 19 and tag == 154: message.osVersion = reader.string(); continue
|
|
2091
|
+
elif field_number == 21 and tag == 170: message.acceptLanguage = reader.string(); continue
|
|
2092
|
+
elif field_number == 22 and tag == 178: message.acceptRegion = reader.string(); continue
|
|
2093
|
+
elif field_number == 37 and tag == 296: message.screenWidthPoints = reader.int32(); continue
|
|
2094
|
+
elif field_number == 38 and tag == 304: message.screenHeightPoints = reader.int32(); continue
|
|
2095
|
+
elif field_number == 39 and tag == 317: message.screenWidthInches = reader.float(); continue
|
|
2096
|
+
elif field_number == 40 and tag == 325: message.screenHeightInches = reader.float(); continue
|
|
2097
|
+
elif field_number == 41 and tag == 328: message.screenPixelDensity = reader.int32(); continue
|
|
2098
|
+
elif field_number == 46 and tag == 368: message.clientFormFactor = reader.int32(); continue
|
|
2099
|
+
elif field_number == 50 and tag == 400: message.gmscoreVersionCode = reader.int32(); continue
|
|
2100
|
+
elif field_number == 55 and tag == 440: message.windowWidthPoints = reader.int32(); continue
|
|
2101
|
+
elif field_number == 56 and tag == 448: message.windowHeightPoints = reader.int32(); continue
|
|
2102
|
+
elif field_number == 64 and tag == 512: message.androidSdkVersion = reader.int32(); continue
|
|
2103
|
+
elif field_number == 65 and tag == 525: message.screenDensityFloat = reader.float(); continue
|
|
2104
|
+
elif field_number == 67 and tag == 536: message.utcOffsetMinutes = longtonumber(reader.int64()); continue
|
|
2105
|
+
elif field_number == 80 and tag == 642: message.timeZone = reader.string(); continue
|
|
2106
|
+
elif field_number == 92 and tag == 738: message.chipset = reader.string(); continue
|
|
2107
|
+
elif field_number == 102 and tag == 818: message.glDeviceInfo = StreamerContextGLDeviceInfo.decode(reader, reader.uint32())
|
|
2108
|
+
else:
|
|
2109
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
2110
|
+
reader.skip(tag & 7)
|
|
2111
|
+
return message
|
|
2112
|
+
|
|
2113
|
+
|
|
2114
|
+
'''StreamerContextGLDeviceInfo'''
|
|
2115
|
+
class StreamerContextGLDeviceInfo:
|
|
2116
|
+
def __init__(self):
|
|
2117
|
+
self.glRenderer = ""
|
|
2118
|
+
self.glEsVersionMajor = 0
|
|
2119
|
+
self.glEsVersionMinor = 0
|
|
2120
|
+
'''encode'''
|
|
2121
|
+
@staticmethod
|
|
2122
|
+
def encode(message: dict, writer=None):
|
|
2123
|
+
if writer is None: writer = BinaryWriter()
|
|
2124
|
+
if message.get("glRenderer", ""): writer.uint32(10).string(message["glRenderer"])
|
|
2125
|
+
if message.get("glEsVersionMajor", ""): writer.uint32(16).int32(message["glEsVersionMajor"])
|
|
2126
|
+
if message.get("glEsVersionMinor", ""): writer.uint32(24).int32(message["glEsVersionMinor"])
|
|
2127
|
+
return writer
|
|
2128
|
+
'''decode'''
|
|
2129
|
+
@staticmethod
|
|
2130
|
+
def decode(data, length=None):
|
|
2131
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2132
|
+
end = reader.len if length is None else reader.pos + length
|
|
2133
|
+
message = StreamerContextGLDeviceInfo()
|
|
2134
|
+
while reader.pos < end:
|
|
2135
|
+
tag = reader.uint32()
|
|
2136
|
+
field_number = tag >> 3
|
|
2137
|
+
if field_number == 1 and tag == 10: message.glRenderer = reader.string(); continue
|
|
2138
|
+
elif field_number == 2 and tag == 16: message.glEsVersionMajor = reader.int32(); continue
|
|
2139
|
+
elif field_number == 3 and tag == 24: message.glEsVersionMinor = reader.int32(); continue
|
|
2140
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2141
|
+
else: reader.skip(tag & 7)
|
|
2142
|
+
return message
|
|
2143
|
+
|
|
2144
|
+
|
|
2145
|
+
'''StreamerContextUpdate'''
|
|
2146
|
+
class StreamerContextUpdate:
|
|
2147
|
+
'''encode'''
|
|
2148
|
+
@staticmethod
|
|
2149
|
+
def encode(message: dict, writer=None):
|
|
2150
|
+
if writer is None: writer = BinaryWriter()
|
|
2151
|
+
if message.get("type", 0): writer.uint32(8).int32(message["type"])
|
|
2152
|
+
if message.get("value", 0): StreamerContextUpdateValue.encode(message["value"], writer.uint32(18).fork()).join()
|
|
2153
|
+
return writer
|
|
2154
|
+
'''decode'''
|
|
2155
|
+
@staticmethod
|
|
2156
|
+
def decode(data, length=None):
|
|
2157
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2158
|
+
end = reader.len if length is None else reader.pos + length
|
|
2159
|
+
message = dict()
|
|
2160
|
+
while reader.pos < end:
|
|
2161
|
+
tag = reader.uint32()
|
|
2162
|
+
field_number = tag >> 3
|
|
2163
|
+
if field_number == 1 and tag == 8: message["type"] = reader.int32(); continue
|
|
2164
|
+
elif field_number == 2 and tag == 16: message["scope"] = reader.int32(); continue
|
|
2165
|
+
elif field_number == 3 and tag == 26: message["value"] = StreamerContextUpdateValue.decode(reader, reader.uint32()); continue
|
|
2166
|
+
elif field_number == 4 and tag == 32: message["sendByDefault"] = reader.bool(); continue
|
|
2167
|
+
elif field_number == 5 and tag == 40: message["writePolicy"] = reader.int32(); continue
|
|
2168
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2169
|
+
else: reader.skip(tag & 7)
|
|
2170
|
+
return message
|
|
2171
|
+
'''SabrContextWritePolicy'''
|
|
2172
|
+
class SabrContextWritePolicy(Enum):
|
|
2173
|
+
SABR_CONTEXT_WRITE_POLICY_UNSPECIFIED = 0
|
|
2174
|
+
SABR_CONTEXT_WRITE_POLICY_OVERWRITE = 1
|
|
2175
|
+
SABR_CONTEXT_WRITE_POLICY_KEEP_EXISTING = 2
|
|
2176
|
+
|
|
2177
|
+
|
|
2178
|
+
''''StreamerContextUpdateValue'''
|
|
2179
|
+
class StreamerContextUpdateValue:
|
|
2180
|
+
'''encode'''
|
|
2181
|
+
@staticmethod
|
|
2182
|
+
def encode(message: dict, writer=None):
|
|
2183
|
+
if writer is None: writer = BinaryWriter()
|
|
2184
|
+
if message.get("field1", 0): StreamerContextUpdateField1.encode(message["field1"], writer.uint32(10).fork()).join()
|
|
2185
|
+
if message.get("field2", 0): writer.uint32(18).bytes(message["field2"])
|
|
2186
|
+
if message.get("field3", 0): writer.uint32(40).int32(message["field3"])
|
|
2187
|
+
return writer
|
|
2188
|
+
'''decode'''
|
|
2189
|
+
@staticmethod
|
|
2190
|
+
def decode(data, length=None):
|
|
2191
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2192
|
+
end = reader.len if length is None else reader.pos + length
|
|
2193
|
+
message = dict()
|
|
2194
|
+
while reader.pos < end:
|
|
2195
|
+
tag = reader.uint32()
|
|
2196
|
+
field_number = tag >> 3
|
|
2197
|
+
if field_number == 1 and tag == 10: message["field1"] = StreamerContextUpdateField1.decode(reader, reader.uint32()); continue
|
|
2198
|
+
elif field_number == 2 and tag == 18: message["field2"] = reader.bytes(); continue
|
|
2199
|
+
elif field_number == 5 and tag == 40: message["field3"] = reader.int32(); continue
|
|
2200
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2201
|
+
else: reader.skip(tag & 7)
|
|
2202
|
+
return message
|
|
2203
|
+
|
|
2204
|
+
|
|
2205
|
+
'''StreamerContextUpdateField1'''
|
|
2206
|
+
class StreamerContextUpdateField1:
|
|
2207
|
+
'''encode'''
|
|
2208
|
+
@staticmethod
|
|
2209
|
+
def encode(message: dict, writer=None):
|
|
2210
|
+
if writer is None: writer = BinaryWriter()
|
|
2211
|
+
if message.get("timestamp", 0): writer.uint32(8).int64(message["timestamp"])
|
|
2212
|
+
if message.get("skip", 0): writer.uint32(16).int32(message["skip"])
|
|
2213
|
+
if message.get("fiedl3", 0): writer.uint32(26).bytes(message["fiedl3"])
|
|
2214
|
+
return writer
|
|
2215
|
+
'''decode'''
|
|
2216
|
+
@staticmethod
|
|
2217
|
+
def decode(data, length=None):
|
|
2218
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2219
|
+
end = reader.len if length is None else reader.pos + length
|
|
2220
|
+
message = dict()
|
|
2221
|
+
while reader.pos < end:
|
|
2222
|
+
tag = reader.uint32()
|
|
2223
|
+
field_number = tag >> 3
|
|
2224
|
+
if field_number == 1 and tag == 8: message["timestamp"] = reader.int64(); continue
|
|
2225
|
+
elif field_number == 2 and tag == 16: message["skip"] = reader.int32(); continue
|
|
2226
|
+
elif field_number == 3 and tag == 26: message["fiedl3"] = reader.bytes(); continue
|
|
2227
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2228
|
+
else: reader.skip(tag & 7)
|
|
2229
|
+
return message
|
|
2230
|
+
|
|
2231
|
+
|
|
2232
|
+
'''StreamerContextGqa'''
|
|
2233
|
+
class StreamerContextGqa:
|
|
2234
|
+
def __init__(self):
|
|
2235
|
+
self.field1 = None
|
|
2236
|
+
self.field2 = None
|
|
2237
|
+
'''encode'''
|
|
2238
|
+
@staticmethod
|
|
2239
|
+
def encode(message: dict, writer=None):
|
|
2240
|
+
if writer is None: writer = BinaryWriter()
|
|
2241
|
+
if message.get("field1", 0): writer.uint32(10).bytes(message["field1"])
|
|
2242
|
+
if message.get("field2", 0): StreamerContextGqaHqa.encode(message["field2"], writer.uint32(18).fork()).join()
|
|
2243
|
+
return writer
|
|
2244
|
+
'''decode'''
|
|
2245
|
+
@staticmethod
|
|
2246
|
+
def decode(data, length=None):
|
|
2247
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2248
|
+
end = reader.len if length is None else reader.pos + length
|
|
2249
|
+
message = StreamerContextGqa()
|
|
2250
|
+
while reader.pos < end:
|
|
2251
|
+
tag = reader.uint32()
|
|
2252
|
+
field_number = tag >> 3
|
|
2253
|
+
if field_number == 1 and tag == 10: message.field1 = reader.bytes(); continue
|
|
2254
|
+
elif field_number == 2 and tag == 18: message.field2 = StreamerContextGqaHqa.decode(reader, reader.uint32()); continue
|
|
2255
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2256
|
+
else: reader.skip(tag & 7)
|
|
2257
|
+
return message
|
|
2258
|
+
|
|
2259
|
+
|
|
2260
|
+
'''StreamerContextGqaHqa'''
|
|
2261
|
+
class StreamerContextGqaHqa:
|
|
2262
|
+
def __init__(self):
|
|
2263
|
+
self.code = 0
|
|
2264
|
+
self.message = ""
|
|
2265
|
+
'''encode'''
|
|
2266
|
+
@staticmethod
|
|
2267
|
+
def encode(message: dict, writer=None):
|
|
2268
|
+
if writer is None: writer = BinaryWriter()
|
|
2269
|
+
if message.get("code", 0): writer.uint32(8).int32(message["code"])
|
|
2270
|
+
if message.get("message", ""): writer.uint32(18).string(message["message"])
|
|
2271
|
+
return writer
|
|
2272
|
+
'''decode'''
|
|
2273
|
+
@staticmethod
|
|
2274
|
+
def decode(data, length=None):
|
|
2275
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2276
|
+
end = reader.len if length is None else reader.pos + length
|
|
2277
|
+
message = StreamerContextGqaHqa()
|
|
2278
|
+
while reader.pos < end:
|
|
2279
|
+
tag = reader.uint32()
|
|
2280
|
+
field_number = tag >> 3
|
|
2281
|
+
if field_number == 1 and tag == 8: message.code = reader.int32(); continue
|
|
2282
|
+
elif field_number == 2 and tag == 18: message.message = reader.string(); continue
|
|
2283
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2284
|
+
else: reader.skip(tag & 7)
|
|
2285
|
+
return message
|
|
2286
|
+
|
|
2287
|
+
|
|
2288
|
+
'''StreamerContext'''
|
|
2289
|
+
class StreamerContext:
|
|
2290
|
+
def __init__(self):
|
|
2291
|
+
self.clientInfo = None
|
|
2292
|
+
self.poToken = None
|
|
2293
|
+
self.playbackCookie = None
|
|
2294
|
+
self.gp = None
|
|
2295
|
+
self.sabrContexts = []
|
|
2296
|
+
self.field6 = []
|
|
2297
|
+
self.field6 = ""
|
|
2298
|
+
self.field6 = []
|
|
2299
|
+
'''encode'''
|
|
2300
|
+
@staticmethod
|
|
2301
|
+
def encode(message: dict, writer=None):
|
|
2302
|
+
if writer is None: writer = BinaryWriter()
|
|
2303
|
+
if message.get("clientInfo") is not None: StreamerContextClientInfo.encode(message["clientInfo"], writer.uint32(10).fork()).join()
|
|
2304
|
+
if message.get("poToken"): writer.uint32(18).bytes(message["poToken"])
|
|
2305
|
+
if message.get("playbackCookie"): writer.uint32(26).bytes(message["playbackCookie"])
|
|
2306
|
+
if message.get("gp"): writer.uint32(34).bytes(message["gp"])
|
|
2307
|
+
for v in message.get("sabrContexts", []): StreamerContextUpdate.encode(v, writer.uint32(42).fork()).join()
|
|
2308
|
+
writer.uint32(50).fork()
|
|
2309
|
+
for v in message.get("field6", []): writer.int32(v)
|
|
2310
|
+
writer.join()
|
|
2311
|
+
if message.get("field7", "") != "": writer.uint32(58).string(message["field7"])
|
|
2312
|
+
if message.get("field8") is not None: StreamerContextGqa.encode(message["field8"], writer.uint32(66).fork()).join()
|
|
2313
|
+
return writer
|
|
2314
|
+
'''decode'''
|
|
2315
|
+
@staticmethod
|
|
2316
|
+
def decode(input_data, length=None):
|
|
2317
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
2318
|
+
end = reader.len if length is None else reader.pos + length
|
|
2319
|
+
message = StreamerContext()
|
|
2320
|
+
while reader.pos < end:
|
|
2321
|
+
tag = reader.uint32()
|
|
2322
|
+
field_number = tag >> 3
|
|
2323
|
+
if field_number == 1 and tag == 10:
|
|
2324
|
+
message.clientInfo = StreamerContextClientInfo.decode(reader, reader.uint32())
|
|
2325
|
+
continue
|
|
2326
|
+
if field_number == 2 and tag == 18:
|
|
2327
|
+
message.poToken = reader.bytes()
|
|
2328
|
+
continue
|
|
2329
|
+
if field_number == 3 and tag == 26:
|
|
2330
|
+
message.playbackCookie = PlaybackCookie.decode(reader, reader.uint32())
|
|
2331
|
+
continue
|
|
2332
|
+
if field_number == 4 and tag == 34:
|
|
2333
|
+
message.gp = reader.bytes()
|
|
2334
|
+
continue
|
|
2335
|
+
if field_number == 5 and tag == 42:
|
|
2336
|
+
message.sabrContexts.append(StreamerContextUpdate.decode(reader, reader.uint32()))
|
|
2337
|
+
continue
|
|
2338
|
+
if field_number == 6 and tag == 48:
|
|
2339
|
+
message.field6.append(reader.int32())
|
|
2340
|
+
continue
|
|
2341
|
+
if field_number == 6 and tag == 50:
|
|
2342
|
+
end2 = reader.uint32() + reader.pos
|
|
2343
|
+
while (reader.pos < end2): message.field6.append(reader.int32())
|
|
2344
|
+
continue
|
|
2345
|
+
if field_number == 7 and tag == 58: message.field7 = reader.string(); continue
|
|
2346
|
+
if field_number == 8 and tag == 66: message.field5.append(StreamerContextGqa.decode(reader, reader.uint32())); continue
|
|
2347
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
2348
|
+
reader.skip(tag & 7)
|
|
2349
|
+
return message
|
|
2350
|
+
|
|
2351
|
+
|
|
2352
|
+
'''VideoPlaybackAbrRequest'''
|
|
2353
|
+
class VideoPlaybackAbrRequest:
|
|
2354
|
+
def __init__(self):
|
|
2355
|
+
self.client_abr_state = None
|
|
2356
|
+
self.selected_format_ids = []
|
|
2357
|
+
self.buffered_ranges = []
|
|
2358
|
+
self.player_time_ms: int = 0
|
|
2359
|
+
self.video_playback_ustreamer_config: bytes = bytes()
|
|
2360
|
+
self.lo = None
|
|
2361
|
+
self.lj = None
|
|
2362
|
+
self.selected_audio_format_ids = []
|
|
2363
|
+
self.selected_video_format_ids = []
|
|
2364
|
+
self.streamer_context = None
|
|
2365
|
+
self.field1 = None
|
|
2366
|
+
self.field2 = None
|
|
2367
|
+
self.field3 = None
|
|
2368
|
+
self.field21 = None
|
|
2369
|
+
self.field22: int = 0
|
|
2370
|
+
self.field23: int = 0
|
|
2371
|
+
self.field1000 = []
|
|
2372
|
+
'''encode'''
|
|
2373
|
+
@staticmethod
|
|
2374
|
+
def encode(message: dict, writer=None):
|
|
2375
|
+
if writer is None: writer = BinaryWriter()
|
|
2376
|
+
if "clientAbrState" in message and message["clientAbrState"] is not None:
|
|
2377
|
+
writer.uint32(10)
|
|
2378
|
+
ClientAbrState.encode(message["clientAbrState"], writer.fork())
|
|
2379
|
+
writer.join()
|
|
2380
|
+
for v in message.get("selectedFormatIds", []):
|
|
2381
|
+
writer.uint32(18)
|
|
2382
|
+
FormatId.encode(v, writer.fork())
|
|
2383
|
+
writer.join()
|
|
2384
|
+
for v in message.get("bufferedRanges", []):
|
|
2385
|
+
writer.uint32(26)
|
|
2386
|
+
BufferedRange.encode(v, writer.fork())
|
|
2387
|
+
writer.join()
|
|
2388
|
+
if message.get("playerTimeMs", 0):
|
|
2389
|
+
writer.uint32(32).int64(message["playerTimeMs"])
|
|
2390
|
+
if message.get("videoPlaybackUstreamerConfig", b''):
|
|
2391
|
+
writer.uint32(42).bytes(message["videoPlaybackUstreamerConfig"])
|
|
2392
|
+
if "lo" in message and message["lo"] is not None:
|
|
2393
|
+
writer.uint32(50)
|
|
2394
|
+
Lo.encode(message["lo"], writer.fork())
|
|
2395
|
+
writer.join()
|
|
2396
|
+
for v in message.get("selectedAudioFormatIds", []):
|
|
2397
|
+
writer.uint32(130)
|
|
2398
|
+
FormatId.encode(v, writer.fork())
|
|
2399
|
+
writer.join()
|
|
2400
|
+
for v in message.get("selectedVideoFormatIds", []):
|
|
2401
|
+
writer.uint32(138)
|
|
2402
|
+
FormatId.encode(v, writer.fork())
|
|
2403
|
+
writer.join()
|
|
2404
|
+
if "streamerContext" in message and message["streamerContext"] is not None:
|
|
2405
|
+
writer.uint32(154)
|
|
2406
|
+
StreamerContext.encode(message["streamerContext"], writer.fork())
|
|
2407
|
+
writer.join()
|
|
2408
|
+
if "field21" in message and message["field21"] is not None:
|
|
2409
|
+
writer.uint32(170)
|
|
2410
|
+
OQa.encode(message["field21"], writer.fork())
|
|
2411
|
+
writer.join()
|
|
2412
|
+
if message.get("field22", 0):
|
|
2413
|
+
writer.uint32(176).int32(message["field22"])
|
|
2414
|
+
if message.get("field23", 0):
|
|
2415
|
+
writer.uint32(184).int32(message["field23"])
|
|
2416
|
+
for v in message.get("field1000", []):
|
|
2417
|
+
writer.uint32(8002)
|
|
2418
|
+
Pqa.encode(v, writer.fork())
|
|
2419
|
+
writer.join()
|
|
2420
|
+
return writer
|
|
2421
|
+
'''decode'''
|
|
2422
|
+
@staticmethod
|
|
2423
|
+
def decode(input_data, length=None):
|
|
2424
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
2425
|
+
end = reader.len if length is None else reader.pos + length
|
|
2426
|
+
message = VideoPlaybackAbrRequest()
|
|
2427
|
+
while reader.pos < end:
|
|
2428
|
+
tag = reader.uint32()
|
|
2429
|
+
field_number = tag >> 3
|
|
2430
|
+
if field_number == 1 and tag == 10: message.client_abr_state = ClientAbrState.decode(reader, reader.uint32()); continue
|
|
2431
|
+
elif field_number == 2 and tag == 18: message.selected_format_ids.append(FormatId.decode(reader, reader.uint32())); continue
|
|
2432
|
+
elif field_number == 3 and tag == 26: message.buffered_ranges.append(BufferedRange.decode(reader, reader.uint32())); continue
|
|
2433
|
+
elif field_number == 4 and tag == 32: message.player_time_ms = longtonumber(reader.int64()); continue
|
|
2434
|
+
elif field_number == 5 and tag == 42: message.video_playback_ustreamer_config = reader.bytes(); continue
|
|
2435
|
+
elif field_number == 6 and tag == 50: message.lo = Lo.decode(reader, reader.uint32()); continue
|
|
2436
|
+
elif field_number == 16 and tag == 130: message.selected_audio_format_ids.append(FormatId.decode(reader, reader.uint32())); continue
|
|
2437
|
+
elif field_number == 17 and tag == 138: message.selected_video_format_ids.append(FormatId.decode(reader, reader.uint32())); continue
|
|
2438
|
+
elif field_number == 19 and tag == 154: message.streamer_context = StreamerContext.decode(reader, reader.uint32()); continue
|
|
2439
|
+
elif field_number == 21 and tag == 170: message.field21 = OQa.decode(reader, reader.uint32()); continue
|
|
2440
|
+
elif field_number == 22 and tag == 176: message.field22 = reader.int32(); continue
|
|
2441
|
+
elif field_number == 23 and tag == 184: message.field23 = reader.int32(); continue
|
|
2442
|
+
elif field_number == 1000 and tag == 8002: message.field1000.append(Pqa.decode(reader, reader.uint32())); continue
|
|
2443
|
+
return message
|
|
2444
|
+
|
|
2445
|
+
|
|
2446
|
+
'''SabrError'''
|
|
2447
|
+
class SabrError:
|
|
2448
|
+
def __init__(self):
|
|
2449
|
+
self.type = ""
|
|
2450
|
+
self.code = 0
|
|
2451
|
+
'''encode'''
|
|
2452
|
+
@staticmethod
|
|
2453
|
+
def encode(message: dict, writer=None):
|
|
2454
|
+
if writer is None: writer = BinaryWriter()
|
|
2455
|
+
if message.get("type") not in (None, ""): writer.uint32(10).string(message["type"])
|
|
2456
|
+
if message.get("code") not in (None, 0): writer.uint32(16).int32(message["code"])
|
|
2457
|
+
return writer
|
|
2458
|
+
'''decode'''
|
|
2459
|
+
@staticmethod
|
|
2460
|
+
def decode(input_data, length=None):
|
|
2461
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
2462
|
+
end = reader.len if length is None else reader.pos + length
|
|
2463
|
+
message = SabrError()
|
|
2464
|
+
while reader.pos < end:
|
|
2465
|
+
tag = reader.uint32()
|
|
2466
|
+
field_number = tag >> 3
|
|
2467
|
+
if field_number == 1 and tag == 10: message.type = reader.string(); continue
|
|
2468
|
+
if field_number == 2 and tag == 16: message.code = reader.int32(); continue
|
|
2469
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
2470
|
+
reader.skip(tag & 7)
|
|
2471
|
+
return message
|
|
2472
|
+
|
|
2473
|
+
|
|
2474
|
+
'''MediaHeader'''
|
|
2475
|
+
class MediaHeader:
|
|
2476
|
+
def __init__(self):
|
|
2477
|
+
self.headerId: int = 0
|
|
2478
|
+
self.videoId: str = ""
|
|
2479
|
+
self.itag: int = 0
|
|
2480
|
+
self.lmt: int = 0
|
|
2481
|
+
self.xtags: str = ""
|
|
2482
|
+
self.startRange: int = 0
|
|
2483
|
+
self.compressionAlgorithm: int = 0
|
|
2484
|
+
self.isInitSeg: bool = False
|
|
2485
|
+
self.sequenceNumber: int = 0
|
|
2486
|
+
self.field10: int = 0
|
|
2487
|
+
self.startMs: int = 0
|
|
2488
|
+
self.durationMs: int = 0
|
|
2489
|
+
self.formatId: Optional[FormatId] = None
|
|
2490
|
+
self.contentLength: int = 0
|
|
2491
|
+
self.timeRange: Optional[TimeRange] = None
|
|
2492
|
+
'''decode'''
|
|
2493
|
+
@staticmethod
|
|
2494
|
+
def decode(reader, length: Optional[int] = None):
|
|
2495
|
+
if not isinstance(reader, BinaryReader): reader = BinaryReader(reader)
|
|
2496
|
+
end = reader.len if length is None else reader.pos + length
|
|
2497
|
+
message = MediaHeader()
|
|
2498
|
+
while reader.pos < end:
|
|
2499
|
+
tag = reader.uint32()
|
|
2500
|
+
field = tag >> 3
|
|
2501
|
+
if field == 1 and tag == 8: message.headerId = reader.uint32(); continue
|
|
2502
|
+
elif field == 2 and tag == 18: message.videoId = reader.string(); continue
|
|
2503
|
+
elif field == 3 and tag == 24: message.itag = reader.int32(); continue
|
|
2504
|
+
elif field == 4 and tag == 32: message.lmt = longtonumber(reader.uint64()); continue
|
|
2505
|
+
elif field == 5 and tag == 42: message.xtags = reader.string(); continue
|
|
2506
|
+
elif field == 6 and tag == 48: message.startRange = longtonumber(reader.int64()); continue
|
|
2507
|
+
elif field == 7 and tag == 56: message.compressionAlgorithm = reader.int32(); continue
|
|
2508
|
+
elif field == 8 and tag == 64: message.isInitSeg = reader.bool(); continue
|
|
2509
|
+
elif field == 9 and tag == 72: message.sequenceNumber = longtonumber(reader.int64()); continue
|
|
2510
|
+
elif field == 10 and tag == 80: message.field10 = longtonumber(reader.int64()); continue
|
|
2511
|
+
elif field == 11 and tag == 88: message.startMs = longtonumber(reader.int64()); continue
|
|
2512
|
+
elif field == 12 and tag == 96: message.durationMs = longtonumber(reader.int64()); continue
|
|
2513
|
+
elif field == 13 and tag == 106:
|
|
2514
|
+
length = reader.uint32()
|
|
2515
|
+
message.formatId = FormatId.decode(reader, length)
|
|
2516
|
+
continue
|
|
2517
|
+
elif field == 14 and tag == 112:
|
|
2518
|
+
message.contentLength = longtonumber(reader.int64())
|
|
2519
|
+
continue
|
|
2520
|
+
elif field == 15 and tag == 122:
|
|
2521
|
+
length = reader.uint32()
|
|
2522
|
+
message.timeRange = TimeRange.decode(reader, length)
|
|
2523
|
+
continue
|
|
2524
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2525
|
+
else: reader.skip(tag & 7)
|
|
2526
|
+
return message
|
|
2527
|
+
'''encode'''
|
|
2528
|
+
@staticmethod
|
|
2529
|
+
def encode(message: dict, writer=None):
|
|
2530
|
+
if writer is None: writer = BinaryWriter()
|
|
2531
|
+
if message.get("headerId", 0): writer.uint32(8).uint32(message["headerId"])
|
|
2532
|
+
if message.get("videoId", ""): writer.uint32(18).string(message["videoId"])
|
|
2533
|
+
if message.get("itag", 0): writer.uint32(24).int32(message["itag"])
|
|
2534
|
+
if message.get("lmt", 0): writer.uint32(32).uint64(message["lmt"])
|
|
2535
|
+
if message.get("xtags", ""): writer.uint32(42).string(message["xtags"])
|
|
2536
|
+
if message.get("startRange", 0): writer.uint32(48).int64(message["startRange"])
|
|
2537
|
+
if message.get("compressionAlgorithm", 0): writer.uint32(56).int32(message["compressionAlgorithm"])
|
|
2538
|
+
if message.get("isInitSeg", False): writer.uint32(64).bool(message["isInitSeg"])
|
|
2539
|
+
if message.get("sequenceNumber", 0): writer.uint32(72).int64(message["sequenceNumber"])
|
|
2540
|
+
if message.get("field10", 0): writer.uint32(80).int64(message["field10"])
|
|
2541
|
+
if message.get("startMs", 0): writer.uint32(88).int64(message["startMs"])
|
|
2542
|
+
if message.get("durationMs", 0): writer.uint32(96).int64(message["durationMs"])
|
|
2543
|
+
if message.get("formatId", 0): FormatId.encode(message["formatId"], writer.uint32(106).fork()).join()
|
|
2544
|
+
if message.get("contentLength", 0): writer.uint32(112).int64(message["contentLength"])
|
|
2545
|
+
if message.get("timeRange", 0): TimeRange.encode(message["timeRange"], writer.uint32(122).fork()).join()
|
|
2546
|
+
return writer
|
|
2547
|
+
|
|
2548
|
+
|
|
2549
|
+
'''NextRequestPolicy'''
|
|
2550
|
+
class NextRequestPolicy:
|
|
2551
|
+
def __init__(self):
|
|
2552
|
+
self.targetAudioReadaheadMs = 0
|
|
2553
|
+
self.targetVideoReadaheadMs = 0
|
|
2554
|
+
self.backoffTimeMs = 0
|
|
2555
|
+
self.playbackCookie = None
|
|
2556
|
+
self.videoId = ""
|
|
2557
|
+
'''encode'''
|
|
2558
|
+
@staticmethod
|
|
2559
|
+
def encode(message: dict, writer=None):
|
|
2560
|
+
if writer is None: writer = BinaryWriter()
|
|
2561
|
+
if message.get("targetAudioReadaheadMs", 0) != 0: writer.uint32(8).int32(message["targetAudioReadaheadMs"])
|
|
2562
|
+
if message.get("targetVideoReadaheadMs", 0) != 0: writer.uint32(16).int32(message["targetVideoReadaheadMs"])
|
|
2563
|
+
if message.get("backoffTimeMs", 0) != 0: writer.uint32(32).int32(message["backoffTimeMs"])
|
|
2564
|
+
if message.get("playbackCookie") is not None: PlaybackCookie.encode(message["playbackCookie"], writer.uint32(58).fork()).join()
|
|
2565
|
+
if message.get("videoId", "") != "": writer.uint32(66).string(message["videoId"])
|
|
2566
|
+
return writer
|
|
2567
|
+
'''decode'''
|
|
2568
|
+
@staticmethod
|
|
2569
|
+
def decode(data, length=None):
|
|
2570
|
+
reader = data if isinstance(data, BinaryReader) else BinaryReader(data)
|
|
2571
|
+
end = reader.len if length is None else reader.pos + length
|
|
2572
|
+
message = NextRequestPolicy
|
|
2573
|
+
while reader.pos < end:
|
|
2574
|
+
tag = reader.uint32()
|
|
2575
|
+
field = tag >> 3
|
|
2576
|
+
if field == 1 and tag == 8: message.targetAudioReadaheadMs = reader.int32(); continue
|
|
2577
|
+
elif field == 2 and tag == 16: message.targetVideoReadaheadMs = reader.int32(); continue
|
|
2578
|
+
elif field == 4 and tag == 32: message.backoffTimeMs = reader.int32(); continue
|
|
2579
|
+
elif field == 7 and tag == 58: message.playbackCookie = PlaybackCookie.decode(reader, reader.uint32()); continue
|
|
2580
|
+
elif field == 8 and tag == 66: message.videoId = reader.string(); continue
|
|
2581
|
+
elif (tag & 7) == 4 or tag == 0: break
|
|
2582
|
+
else: reader.skip(tag & 7)
|
|
2583
|
+
return message
|
|
2584
|
+
|
|
2585
|
+
|
|
2586
|
+
'''FormatInitializationMetadata'''
|
|
2587
|
+
class FormatInitializationMetadata:
|
|
2588
|
+
def __init__( self):
|
|
2589
|
+
self.videoId = ""
|
|
2590
|
+
self.formatId = None
|
|
2591
|
+
self.endTimeMs = 0
|
|
2592
|
+
self.endSegmentNumber = 0
|
|
2593
|
+
self.mimeType = ""
|
|
2594
|
+
self.initRange = None
|
|
2595
|
+
self.indexRange = None
|
|
2596
|
+
self.field8 = 0
|
|
2597
|
+
self.durationMs = 0
|
|
2598
|
+
self.field10 = 0
|
|
2599
|
+
'''encode'''
|
|
2600
|
+
@staticmethod
|
|
2601
|
+
def encode(message, writer=None):
|
|
2602
|
+
if writer is None: writer = BinaryWriter()
|
|
2603
|
+
if message.videoId != "":
|
|
2604
|
+
writer.uint32(10)
|
|
2605
|
+
writer.string(message.videoId)
|
|
2606
|
+
if message.formatId is not None:
|
|
2607
|
+
writer.uint32(18)
|
|
2608
|
+
FormatId.encode(message.formatId, writer.fork()).join()
|
|
2609
|
+
if message.endTimeMs != 0:
|
|
2610
|
+
writer.uint32(24)
|
|
2611
|
+
writer.int32(message.endTimeMs)
|
|
2612
|
+
if message.endSegmentNumber != 0:
|
|
2613
|
+
writer.uint32(32)
|
|
2614
|
+
writer.int64(message.endSegmentNumber)
|
|
2615
|
+
if message.mimeType != "":
|
|
2616
|
+
writer.uint32(42)
|
|
2617
|
+
writer.string(message.mimeType)
|
|
2618
|
+
if message.initRange is not None:
|
|
2619
|
+
writer.uint32(50)
|
|
2620
|
+
InitRange.encode(message.initRange, writer.fork()).join()
|
|
2621
|
+
if message.indexRange is not None:
|
|
2622
|
+
writer.uint32(58)
|
|
2623
|
+
IndexRange.encode(message.indexRange, writer.fork()).join()
|
|
2624
|
+
if message.field8 != 0:
|
|
2625
|
+
writer.uint32(64)
|
|
2626
|
+
writer.int32(message.field8)
|
|
2627
|
+
if message.durationMs != 0:
|
|
2628
|
+
writer.uint32(72)
|
|
2629
|
+
writer.int32(message.durationMs)
|
|
2630
|
+
if message.field10 != 0:
|
|
2631
|
+
writer.uint32(80)
|
|
2632
|
+
writer.int32(message.field10)
|
|
2633
|
+
return writer
|
|
2634
|
+
'''decode'''
|
|
2635
|
+
@staticmethod
|
|
2636
|
+
def decode(input_data, length=None):
|
|
2637
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
2638
|
+
end = reader.len if length is None else reader.pos + length
|
|
2639
|
+
message = FormatInitializationMetadata()
|
|
2640
|
+
while reader.pos < end:
|
|
2641
|
+
tag = reader.uint32()
|
|
2642
|
+
field_no = tag >> 3
|
|
2643
|
+
if field_no == 1 and tag == 10: message.videoId = reader.string(); continue
|
|
2644
|
+
elif field_no == 2 and tag == 18: message.formatId = FormatId.decode(reader, reader.uint32()); continue
|
|
2645
|
+
elif field_no == 3 and tag == 24: message.endTimeMs = reader.int32(); continue
|
|
2646
|
+
elif field_no == 4 and tag == 32: message.endSegmentNumber = longtonumber(reader.int64()); continue
|
|
2647
|
+
elif field_no == 5 and tag == 42: message.mimeType = reader.string(); continue
|
|
2648
|
+
elif field_no == 6 and tag == 50: message.initRange = InitRange.decode(reader, reader.uint32()); continue
|
|
2649
|
+
elif field_no == 7 and tag == 58: message.indexRange = IndexRange.decode(reader, reader.uint32()); continue
|
|
2650
|
+
elif field_no == 8 and tag == 64: message.field8 = reader.int32(); continue
|
|
2651
|
+
elif field_no == 9 and tag == 72: message.durationMs = reader.int32(); continue
|
|
2652
|
+
elif field_no == 10 and tag == 80: message.field10 = reader.int32(); continue
|
|
2653
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
2654
|
+
reader.skip(tag & 7)
|
|
2655
|
+
return message
|
|
2656
|
+
|
|
2657
|
+
|
|
2658
|
+
'''SabrRedirect'''
|
|
2659
|
+
class SabrRedirect:
|
|
2660
|
+
def __init__(self):
|
|
2661
|
+
self.url = ""
|
|
2662
|
+
'''encode'''
|
|
2663
|
+
@staticmethod
|
|
2664
|
+
def encode(message: dict, writer=None):
|
|
2665
|
+
if writer is None: writer = BinaryWriter()
|
|
2666
|
+
if message.get("url") not in (None, ""): writer.uint32(10).string(message["url"])
|
|
2667
|
+
return writer
|
|
2668
|
+
'''decode'''
|
|
2669
|
+
@staticmethod
|
|
2670
|
+
def decode(input_data, length=None):
|
|
2671
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
2672
|
+
end = reader.len if length is None else reader.pos + length
|
|
2673
|
+
message = SabrRedirect
|
|
2674
|
+
while reader.pos < end:
|
|
2675
|
+
tag = reader.uint32()
|
|
2676
|
+
field_number = tag >> 3
|
|
2677
|
+
if field_number == 1 and tag == 10: message.url = reader.string(); continue
|
|
2678
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
2679
|
+
reader.skip(tag & 7)
|
|
2680
|
+
return message
|
|
2681
|
+
|
|
2682
|
+
|
|
2683
|
+
'''StreamProtectionStatus'''
|
|
2684
|
+
class StreamProtectionStatus:
|
|
2685
|
+
def __init__(self):
|
|
2686
|
+
self.status = None
|
|
2687
|
+
self.field2 = None
|
|
2688
|
+
'''encode'''
|
|
2689
|
+
@staticmethod
|
|
2690
|
+
def encode(message: dict, writer=None):
|
|
2691
|
+
if writer is None: writer = BinaryWriter()
|
|
2692
|
+
if message.get("status", 0) != 0:
|
|
2693
|
+
writer.uint32(8)
|
|
2694
|
+
writer.int32(message["status"])
|
|
2695
|
+
if message.get("field2") != 0:
|
|
2696
|
+
writer.uint32(16)
|
|
2697
|
+
writer.int32(message["field2"])
|
|
2698
|
+
return writer
|
|
2699
|
+
'''decode'''
|
|
2700
|
+
@staticmethod
|
|
2701
|
+
def decode(input_data, length=None):
|
|
2702
|
+
reader = input_data if isinstance(input_data, BinaryReader) else BinaryReader(input_data)
|
|
2703
|
+
end = reader.len if length is None else reader.pos + length
|
|
2704
|
+
message = StreamProtectionStatus()
|
|
2705
|
+
while reader.pos < end:
|
|
2706
|
+
tag = reader.uint32()
|
|
2707
|
+
field_no = tag >> 3
|
|
2708
|
+
if field_no == 1 and tag == 8: message.status = reader.int32(); continue
|
|
2709
|
+
elif field_no == 2 and tag == 16: message.field2 = reader.int32(); continue
|
|
2710
|
+
if (tag & 7) == 4 or tag == 0: break
|
|
2711
|
+
reader.skip(tag & 7)
|
|
2712
|
+
return message
|
|
2713
|
+
'''Status'''
|
|
2714
|
+
class Status(Enum):
|
|
2715
|
+
OK = 1
|
|
2716
|
+
ATTESTATION_PENDING = 2
|
|
2717
|
+
ATTESTATION_REQUIRED = 3
|
|
2718
|
+
|
|
2719
|
+
|
|
2720
|
+
'''ServerAbrStream'''
|
|
2721
|
+
class ServerAbrStream:
|
|
2722
|
+
def __init__(self, stream: Stream, write_chunk: Callable, monostate: Monostate):
|
|
2723
|
+
self.stream = stream
|
|
2724
|
+
self.write_chunk = write_chunk
|
|
2725
|
+
self.youtube = monostate.youtube
|
|
2726
|
+
self.po_token = self.stream.po_token
|
|
2727
|
+
self.server_abr_streaming_url = self.stream.url
|
|
2728
|
+
self.video_playback_ustreamer_config = self.stream.video_playback_ustreamer_config
|
|
2729
|
+
self.totalDurationMs = int(self.stream.durationMs)
|
|
2730
|
+
self.bytes_remaining = self.stream.filesize
|
|
2731
|
+
self.initialized_formats = []
|
|
2732
|
+
self.formats_by_key = {}
|
|
2733
|
+
self.playback_cookie = None
|
|
2734
|
+
self.header_id_to_format_key_map = {}
|
|
2735
|
+
self.previous_sequences = {}
|
|
2736
|
+
self.RELOAD = False
|
|
2737
|
+
self.maximum_reload_attempt = 4
|
|
2738
|
+
self.stream_protection_status = PoTokenStatus.UNKNOWN.name
|
|
2739
|
+
self.sabr_contexts_to_send = []
|
|
2740
|
+
self.sabr_context_updates = dict()
|
|
2741
|
+
'''emit'''
|
|
2742
|
+
def emit(self, data):
|
|
2743
|
+
for formatId in data['initialized_formats']:
|
|
2744
|
+
if formatId['formatId']['itag'] == self.stream.itag:
|
|
2745
|
+
media_chunks = formatId['mediaChunks']
|
|
2746
|
+
for chunk in media_chunks:
|
|
2747
|
+
self.bytes_remaining -= len(chunk)
|
|
2748
|
+
self.write_chunk(chunk, self.bytes_remaining)
|
|
2749
|
+
'''start'''
|
|
2750
|
+
def start(self):
|
|
2751
|
+
audio_format = [{'itag': self.stream.itag, 'lastModified': int(self.stream.last_Modified), 'xtags': self.stream.xtags}] if self.stream.type == 'audio' else []
|
|
2752
|
+
video_format = [{'itag': self.stream.itag, 'lastModified': int(self.stream.last_Modified), 'xtags': self.stream.xtags}] if self.stream.type == 'video' else []
|
|
2753
|
+
client_abr_state = {
|
|
2754
|
+
'lastManualDirection': 0, 'timeSinceLastManualFormatSelectionMs': 0, 'lastManualSelectedResolution': int(self.stream.resolution.replace('p', '')) if video_format else 720,
|
|
2755
|
+
'stickyResolution': int(self.stream.resolution.replace('p', '')) if video_format else 720, 'playerTimeMs': 0, 'visibility': 0, 'drcEnabled': self.stream.is_drc,
|
|
2756
|
+
'enabledTrackTypesBitfield': 0 if video_format else 1
|
|
2757
|
+
}
|
|
2758
|
+
while client_abr_state['playerTimeMs'] < self.totalDurationMs:
|
|
2759
|
+
data = self.fetchmedia(client_abr_state, audio_format, video_format)
|
|
2760
|
+
if data.get("sabr_error"): self.reload()
|
|
2761
|
+
self.emit(data)
|
|
2762
|
+
if data.get("sabr_context_update"):
|
|
2763
|
+
if self.maximum_reload_attempt > 0: continue
|
|
2764
|
+
else: raise
|
|
2765
|
+
if client_abr_state["enabledTrackTypesBitfield"] == 0:
|
|
2766
|
+
main_format = next((fmt for fmt in data.get("initialized_formats", []) if "video" in (fmt.get("mimeType") or "")), None)
|
|
2767
|
+
else:
|
|
2768
|
+
main_format = data['initialized_formats'][0] if data['initialized_formats'] else None
|
|
2769
|
+
for fmt in data.get("initialized_formats", []):
|
|
2770
|
+
format_key = fmt["formatKey"]
|
|
2771
|
+
sequence_numbers = [seq.get("sequenceNumber", 0) for seq in fmt.get("sequenceList", [])]
|
|
2772
|
+
self.previous_sequences[format_key] = sequence_numbers
|
|
2773
|
+
if not self.RELOAD and (main_format is None or not main_format.get("sequenceList")): self.reload()
|
|
2774
|
+
if self.RELOAD:
|
|
2775
|
+
if self.maximum_reload_attempt > 0:
|
|
2776
|
+
self.RELOAD = False
|
|
2777
|
+
continue
|
|
2778
|
+
else:
|
|
2779
|
+
raise
|
|
2780
|
+
if (not main_format or main_format["sequenceCount"] == main_format["sequenceList"][-1].get("sequenceNumber")): break
|
|
2781
|
+
total_sequence_duration = sum(seq.get("durationMs", 0) for seq in main_format["sequenceList"])
|
|
2782
|
+
client_abr_state["playerTimeMs"] += total_sequence_duration
|
|
2783
|
+
'''fetchmedia'''
|
|
2784
|
+
def fetchmedia(self, client_abr_state, audio_format, video_format):
|
|
2785
|
+
body = VideoPlaybackAbrRequest.encode({
|
|
2786
|
+
'clientAbrState': client_abr_state, 'selectedAudioFormatIds': audio_format, 'selectedVideoFormatIds': video_format,
|
|
2787
|
+
'selectedFormatIds': [fmt["formatId"] for fmt in self.initialized_formats], 'videoPlaybackUstreamerConfig': self.base64tou8(self.video_playback_ustreamer_config),
|
|
2788
|
+
'streamerContext': {
|
|
2789
|
+
'sabrContexts': [ctx for ctx in self.sabr_context_updates.values() if ctx["type"] in self.sabr_contexts_to_send],
|
|
2790
|
+
'field6': [],
|
|
2791
|
+
'poToken': self.base64tou8(self.po_token) if self.po_token else None,
|
|
2792
|
+
'playbackCookie': PlaybackCookie.encode(self.playback_cookie).finish() if self.playback_cookie else None,
|
|
2793
|
+
'clientInfo': {'clientName': 1, 'clientVersion': '2.20250523.01.00', 'osName': 'Windows', 'osVersion': '10.0', 'platform': 'DESKTOP'}
|
|
2794
|
+
},
|
|
2795
|
+
'bufferedRanges': [fmt["_state"] for fmt in self.initialized_formats], 'field1000': []
|
|
2796
|
+
}).finish()
|
|
2797
|
+
base_headers = {"User-Agent": "Mozilla/5.0", "accept-language": "en-US,en", "Content-Type": "application/vnd.yt-ump"}
|
|
2798
|
+
request = Request(self.server_abr_streaming_url, headers=base_headers, method="POST", data=bytes(body))
|
|
2799
|
+
return self.parseumpresponse(bytes(urlopen(request).read()))
|
|
2800
|
+
'''parseumpresponse'''
|
|
2801
|
+
def parseumpresponse(self, resp):
|
|
2802
|
+
self.header_id_to_format_key_map.clear()
|
|
2803
|
+
for k, v in enumerate(self.initialized_formats): self.initialized_formats[k]['sequenceList'], self.initialized_formats[k]['mediaChunks'] = [], []
|
|
2804
|
+
sabr_error, sabr_redirect, sabr_context_update = None, None, False
|
|
2805
|
+
ump = UMP(ChunkedDataBuffer([resp]))
|
|
2806
|
+
def callback(part):
|
|
2807
|
+
data = list(part['data'].chunks[0] if part['data'].chunks else [])
|
|
2808
|
+
if part['type'] == PART.MEDIA_HEADER.value:
|
|
2809
|
+
self.processmediaheader(data)
|
|
2810
|
+
elif part['type'] == PART.MEDIA.value:
|
|
2811
|
+
self.processmediadata(part['data'])
|
|
2812
|
+
elif part['type'] == PART.MEDIA_END.value:
|
|
2813
|
+
self.processendofmedia(part['data'])
|
|
2814
|
+
elif part['type'] == PART.NEXT_REQUEST_POLICY.value:
|
|
2815
|
+
self.processnextrequestpolicy(data)
|
|
2816
|
+
elif part['type'] == PART.FORMAT_INITIALIZATION_METADATA.value:
|
|
2817
|
+
self.processformatinitialization(data)
|
|
2818
|
+
elif part['type'] == PART.SABR_ERROR.value:
|
|
2819
|
+
nonlocal sabr_error
|
|
2820
|
+
sabr_error = SabrError.decode(data)
|
|
2821
|
+
elif part['type'] == PART.SABR_REDIRECT.value:
|
|
2822
|
+
nonlocal sabr_redirect
|
|
2823
|
+
sabr_redirect = self.processsabrredirect(data)
|
|
2824
|
+
elif part['type'] == PART.STREAM_PROTECTION_STATUS.value:
|
|
2825
|
+
self.processstreamprotectionstatus(data)
|
|
2826
|
+
elif part['type'] == PART.RELOAD_PLAYER_RESPONSE.value:
|
|
2827
|
+
self.reload()
|
|
2828
|
+
elif part["type"] == PART.PLAYBACK_START_POLICY.value:
|
|
2829
|
+
pass
|
|
2830
|
+
elif part["type"] == PART.REQUEST_CANCELLATION_POLICY.value:
|
|
2831
|
+
pass
|
|
2832
|
+
elif part["type"] == PART.SABR_CONTEXT_UPDATE.value:
|
|
2833
|
+
nonlocal sabr_context_update
|
|
2834
|
+
sabr_context_update = True
|
|
2835
|
+
self.processsabrcontextupdate(data)
|
|
2836
|
+
elif part["type"] == PART.SNACKBAR_MESSAGE.value:
|
|
2837
|
+
sabr_context_update = True
|
|
2838
|
+
self.processsnackbarmessage()
|
|
2839
|
+
ump.parse(callback)
|
|
2840
|
+
return {"initialized_formats": self.initialized_formats, "sabr_redirect": sabr_redirect, "sabr_error": sabr_error, "sabr_context_update": sabr_context_update}
|
|
2841
|
+
'''processmediaheader'''
|
|
2842
|
+
def processmediaheader(self, data):
|
|
2843
|
+
media_header = MediaHeader.decode(data)
|
|
2844
|
+
if not media_header.formatId: return
|
|
2845
|
+
format_key = self.getformatkey(media_header.formatId)
|
|
2846
|
+
current_format = self.formats_by_key.get(format_key) or self.registerformat(media_header)
|
|
2847
|
+
if not current_format: return
|
|
2848
|
+
sequence_number = media_header.sequenceNumber
|
|
2849
|
+
if sequence_number is not None:
|
|
2850
|
+
if format_key in self.previous_sequences:
|
|
2851
|
+
if sequence_number in self.previous_sequences[format_key]: return
|
|
2852
|
+
header_id = media_header.headerId
|
|
2853
|
+
if header_id is not None:
|
|
2854
|
+
if header_id not in self.header_id_to_format_key_map:
|
|
2855
|
+
self.header_id_to_format_key_map[header_id] = format_key
|
|
2856
|
+
if not any(seq.get("sequenceNumber") == (media_header.sequenceNumber or 0) for seq in current_format["sequenceList"]):
|
|
2857
|
+
current_format["sequenceList"].append({
|
|
2858
|
+
"itag": media_header.itag, "formatId": media_header.formatId, "isInitSegment": media_header.isInitSeg, "durationMs": media_header.durationMs, "startMs": media_header.startMs,
|
|
2859
|
+
"startDataRange": media_header.startRange, "sequenceNumber": media_header.sequenceNumber, "contentLength": media_header.contentLength, "timeRange": media_header.timeRange
|
|
2860
|
+
})
|
|
2861
|
+
if isinstance(sequence_number, int):
|
|
2862
|
+
current_format["_state"]["durationMs"] += media_header.durationMs
|
|
2863
|
+
current_format["_state"]["endSegmentIndex"] += 1
|
|
2864
|
+
'''processmediadata'''
|
|
2865
|
+
def processmediadata(self, data):
|
|
2866
|
+
header_id = data.getuint8(0)
|
|
2867
|
+
stream_data = data.split(1)['remaining_buffer']
|
|
2868
|
+
format_key = self.header_id_to_format_key_map.get(header_id)
|
|
2869
|
+
if not format_key: return
|
|
2870
|
+
current_format = self.formats_by_key.get(format_key)
|
|
2871
|
+
if not current_format: return
|
|
2872
|
+
current_format['mediaChunks'].append(stream_data.chunks[0])
|
|
2873
|
+
'''processendofmedia'''
|
|
2874
|
+
def processendofmedia(self, data):
|
|
2875
|
+
header_id = data.getuint8(0)
|
|
2876
|
+
self.header_id_to_format_key_map.pop(header_id, None)
|
|
2877
|
+
'''processnextrequestpolicy'''
|
|
2878
|
+
def processnextrequestpolicy(self, data):
|
|
2879
|
+
next_request_policy = NextRequestPolicy.decode(data)
|
|
2880
|
+
self.playback_cookie = next_request_policy.playbackCookie
|
|
2881
|
+
'''processformatinitialization'''
|
|
2882
|
+
def processformatinitialization(self, data):
|
|
2883
|
+
format_metadata = FormatInitializationMetadata.decode(data)
|
|
2884
|
+
self.registerformat(format_metadata)
|
|
2885
|
+
'''processsabrredirect'''
|
|
2886
|
+
def processsabrredirect(self, data):
|
|
2887
|
+
sabr_redirect = SabrRedirect.decode(data)
|
|
2888
|
+
if not sabr_redirect.url:
|
|
2889
|
+
raise ValueError("Invalid SABR redirect")
|
|
2890
|
+
self.server_abr_streaming_url = sabr_redirect.url
|
|
2891
|
+
return sabr_redirect
|
|
2892
|
+
'''processsnackbarmessage'''
|
|
2893
|
+
def processsnackbarmessage(self):
|
|
2894
|
+
skip = self.sabr_context_updates[self.sabr_contexts_to_send[-1]].get("skip", 1000) / 1000
|
|
2895
|
+
if skip >= 60: raise
|
|
2896
|
+
time.sleep(skip)
|
|
2897
|
+
self.maximum_reload_attempt -= 1
|
|
2898
|
+
'''processstreamprotectionstatus'''
|
|
2899
|
+
def processstreamprotectionstatus(self, data):
|
|
2900
|
+
protection_status = StreamProtectionStatus.decode(data).status
|
|
2901
|
+
if protection_status == StreamProtectionStatus.Status.OK.value:
|
|
2902
|
+
result_status = PoTokenStatus.OK.name if self.po_token else PoTokenStatus.NOT_REQUIRED.name
|
|
2903
|
+
elif protection_status == StreamProtectionStatus.Status.ATTESTATION_PENDING.value:
|
|
2904
|
+
result_status = PoTokenStatus.PENDING.name if self.po_token else PoTokenStatus.PENDING_MISSING.name
|
|
2905
|
+
elif protection_status == StreamProtectionStatus.Status.ATTESTATION_REQUIRED.value:
|
|
2906
|
+
result_status = PoTokenStatus.INVALID.name if self.po_token else PoTokenStatus.MISSING.name
|
|
2907
|
+
else:
|
|
2908
|
+
result_status = PoTokenStatus.UNKNOWN.name
|
|
2909
|
+
self.stream_protection_status = result_status
|
|
2910
|
+
'''processsabrcontextupdate'''
|
|
2911
|
+
def processsabrcontextupdate(self, data):
|
|
2912
|
+
sabr_ctx_update = StreamerContextUpdate.decode(data)
|
|
2913
|
+
if not (sabr_ctx_update["type"] and sabr_ctx_update["value"] and sabr_ctx_update["writePolicy"]): return
|
|
2914
|
+
if (sabr_ctx_update["writePolicy"] == StreamerContextUpdate.SabrContextWritePolicy.SABR_CONTEXT_WRITE_POLICY_KEEP_EXISTING.value and sabr_ctx_update["type"] in self.sabr_context_updates): return
|
|
2915
|
+
self.sabr_context_updates[sabr_ctx_update["type"]] = sabr_ctx_update
|
|
2916
|
+
timestamp = sabr_ctx_update.get("value", "").get("field1", "").get("timestamp", "")
|
|
2917
|
+
skip = sabr_ctx_update.get("value", "").get("field1", "").get("skip", "")
|
|
2918
|
+
self.sabr_context_updates[sabr_ctx_update["type"]]["timestamp"] = timestamp
|
|
2919
|
+
self.sabr_context_updates[sabr_ctx_update["type"]]["skip"] = skip
|
|
2920
|
+
if sabr_ctx_update["sendByDefault"] is True: self.sabr_contexts_to_send.append(sabr_ctx_update["type"])
|
|
2921
|
+
'''getformatkey'''
|
|
2922
|
+
@staticmethod
|
|
2923
|
+
def getformatkey(format_id):
|
|
2924
|
+
return f"{format_id['itag']};{format_id['lastModified']};"
|
|
2925
|
+
'''registerformat'''
|
|
2926
|
+
def registerformat(self, data):
|
|
2927
|
+
if data.formatId is None: return None
|
|
2928
|
+
format_key = self.getformatkey(data.formatId)
|
|
2929
|
+
if format_key not in self.formats_by_key:
|
|
2930
|
+
format_ = {
|
|
2931
|
+
"formatId": data.formatId, "formatKey": format_key, "durationMs": data.durationMs, "mimeType": data.mimeType, "sequenceCount": data.endSegmentNumber,
|
|
2932
|
+
"sequenceList": [], "mediaChunks": [], "_state": {"formatId": data.formatId, "startTimeMs": 0, "durationMs": 0, "startSegmentIndex": 1, "endSegmentIndex": 0}
|
|
2933
|
+
}
|
|
2934
|
+
self.initialized_formats.append(format_)
|
|
2935
|
+
self.formats_by_key[format_key] = self.initialized_formats[-1]
|
|
2936
|
+
return format_
|
|
2937
|
+
return None
|
|
2938
|
+
'''reload'''
|
|
2939
|
+
def reload(self):
|
|
2940
|
+
self.RELOAD = True
|
|
2941
|
+
self.maximum_reload_attempt -= 1
|
|
2942
|
+
self.sabr_contexts_to_send = []
|
|
2943
|
+
self.sabr_context_updates = dict()
|
|
2944
|
+
self.youtube.vid_info = None
|
|
2945
|
+
refresh_url = self.youtube.server_abr_streaming_url
|
|
2946
|
+
if not refresh_url: raise ValueError("Invalid SABR refresh")
|
|
2947
|
+
self.server_abr_streaming_url = refresh_url
|
|
2948
|
+
self.video_playback_ustreamer_config = self.youtube.video_playback_ustreamer_config
|
|
2949
|
+
'''base64tou8'''
|
|
2950
|
+
@staticmethod
|
|
2951
|
+
def base64tou8(base64_str: str):
|
|
2952
|
+
standard_base64 = base64_str.replace('-', '+').replace('_', '/')
|
|
2953
|
+
padded_base64 = standard_base64 + '=' * ((4 - len(standard_base64) % 4) % 4)
|
|
2954
|
+
byte_data = base64.b64decode(padded_base64)
|
|
2955
|
+
return bytearray(byte_data)
|
|
2956
|
+
|
|
2957
|
+
|
|
2958
|
+
'''StreamQuery'''
|
|
2959
|
+
class StreamQuery(Sequence):
|
|
2960
|
+
def __init__(self, fmt_streams):
|
|
2961
|
+
self.fmt_streams = fmt_streams
|
|
2962
|
+
self.itag_index = {int(s.itag): s for s in fmt_streams}
|
|
2963
|
+
'''filter'''
|
|
2964
|
+
def filter(self, fps=None, res=None, resolution=None, mime_type=None, type=None, subtype=None, file_extension=None, abr=None, bitrate=None, video_codec=None, audio_codec=None,
|
|
2965
|
+
only_audio=None, only_video=None, progressive=None, adaptive=None, is_dash=None, is_drc=None, audio_track_name=None, custom_filter_functions=None):
|
|
2966
|
+
filters = []
|
|
2967
|
+
if res or resolution:
|
|
2968
|
+
if isinstance(res, str) or isinstance(resolution, str): filters.append(lambda s: s.resolution == (res or resolution))
|
|
2969
|
+
elif isinstance(res, list) or isinstance(resolution, list): filters.append(lambda s: s.resolution in (res or resolution))
|
|
2970
|
+
if fps: filters.append(lambda s: s.fps == fps)
|
|
2971
|
+
if mime_type: filters.append(lambda s: s.mime_type == mime_type)
|
|
2972
|
+
if type: filters.append(lambda s: s.type == type)
|
|
2973
|
+
if subtype or file_extension: filters.append(lambda s: s.subtype == (subtype or file_extension))
|
|
2974
|
+
if abr or bitrate: filters.append(lambda s: s.abr == (abr or bitrate))
|
|
2975
|
+
if video_codec: filters.append(lambda s: s.video_codec == video_codec)
|
|
2976
|
+
if audio_codec: filters.append(lambda s: s.audio_codec == audio_codec)
|
|
2977
|
+
if only_audio: filters.append(lambda s: (s.includesaudiotrack and not s.includesvideotrack))
|
|
2978
|
+
if only_video: filters.append(lambda s: (s.includesvideotrack and not s.includesaudiotrack))
|
|
2979
|
+
if progressive: filters.append(lambda s: s.isprogressive)
|
|
2980
|
+
if adaptive: filters.append(lambda s: s.isadaptive)
|
|
2981
|
+
if audio_track_name: filters.append(lambda s: s.audio_track_name == audio_track_name)
|
|
2982
|
+
if custom_filter_functions: filters.extend(custom_filter_functions)
|
|
2983
|
+
if is_dash is not None: filters.append(lambda s: s.is_dash == is_dash)
|
|
2984
|
+
if is_drc is not None: filters.append(lambda s: s.is_drc == is_drc)
|
|
2985
|
+
return self._filter(filters)
|
|
2986
|
+
'''_filter'''
|
|
2987
|
+
def _filter(self, filters: List[Callable]):
|
|
2988
|
+
fmt_streams = self.fmt_streams
|
|
2989
|
+
for filter_lambda in filters: fmt_streams = filter(filter_lambda, fmt_streams)
|
|
2990
|
+
return StreamQuery(list(fmt_streams))
|
|
2991
|
+
'''orderby'''
|
|
2992
|
+
def orderby(self, attribute_name: str):
|
|
2993
|
+
has_attribute = [s for s in self.fmt_streams if getattr(s, attribute_name) is not None]
|
|
2994
|
+
if has_attribute and isinstance(getattr(has_attribute[0], attribute_name), str):
|
|
2995
|
+
try: return StreamQuery(sorted(has_attribute, key=lambda s: int("".join(filter(str.isdigit, getattr(s, attribute_name))))))
|
|
2996
|
+
except ValueError: pass
|
|
2997
|
+
return StreamQuery(sorted(has_attribute, key=lambda s: getattr(s, attribute_name)))
|
|
2998
|
+
'''desc'''
|
|
2999
|
+
def desc(self):
|
|
3000
|
+
return StreamQuery(self.fmt_streams[::-1])
|
|
3001
|
+
'''asc'''
|
|
3002
|
+
def asc(self):
|
|
3003
|
+
return self
|
|
3004
|
+
'''getbyitag'''
|
|
3005
|
+
def getbyitag(self, itag: Union[int, str]):
|
|
3006
|
+
if isinstance(itag, int): return self.itag_index.get(itag)
|
|
3007
|
+
elif isinstance(itag, str) and itag.isdigit(): return self.itag_index.get(int(itag))
|
|
3008
|
+
'''getbyresolution'''
|
|
3009
|
+
def getbyresolution(self, resolution: str):
|
|
3010
|
+
return self.filter(progressive=True, subtype="mp4", resolution=resolution).first()
|
|
3011
|
+
'''getdefaultaudiotrack'''
|
|
3012
|
+
def getdefaultaudiotrack(self):
|
|
3013
|
+
return self._filter([lambda s: s.is_default_audio_track])
|
|
3014
|
+
'''getextraaudiotrack'''
|
|
3015
|
+
def getextraaudiotrack(self):
|
|
3016
|
+
return self._filter([lambda s: not s.is_default_audio_track and s.includesaudiotrack and not s.includesvideotrack])
|
|
3017
|
+
'''getextraaudiotrackbyname'''
|
|
3018
|
+
def getextraaudiotrackbyname(self, name):
|
|
3019
|
+
return self._filter([lambda s: s.audio_track_name == name])
|
|
3020
|
+
'''getlowestresolution'''
|
|
3021
|
+
def getlowestresolution(self, progressive=True):
|
|
3022
|
+
return self.filter(progressive=progressive, subtype="mp4").orderby("resolution").first()
|
|
3023
|
+
'''gethighestresolution'''
|
|
3024
|
+
def gethighestresolution(self, progressive=True, mime_type=None):
|
|
3025
|
+
return self.filter(progressive=progressive, mime_type=mime_type).orderby("resolution").last()
|
|
3026
|
+
'''getaudioonly'''
|
|
3027
|
+
def getaudioonly(self, subtype: str = "mp4"):
|
|
3028
|
+
return self.filter(only_audio=True, subtype=subtype).orderby("abr").last()
|
|
3029
|
+
'''otf'''
|
|
3030
|
+
def otf(self, is_otf: bool = False):
|
|
3031
|
+
return self._filter([lambda s: s.is_otf == is_otf])
|
|
3032
|
+
'''first'''
|
|
3033
|
+
def first(self):
|
|
3034
|
+
try: return self.fmt_streams[0]
|
|
3035
|
+
except IndexError: return None
|
|
3036
|
+
'''last'''
|
|
3037
|
+
def last(self):
|
|
3038
|
+
try: return self.fmt_streams[-1]
|
|
3039
|
+
except IndexError: pass
|
|
3040
|
+
'''count'''
|
|
3041
|
+
def count(self, value: Optional[str] = None):
|
|
3042
|
+
return self.fmt_streams.count(value) if value else len(self)
|
|
3043
|
+
'''all'''
|
|
3044
|
+
def all(self):
|
|
3045
|
+
return self.fmt_streams
|
|
3046
|
+
'''getitem'''
|
|
3047
|
+
def __getitem__(self, i: Union[slice, int]):
|
|
3048
|
+
return self.fmt_streams[i]
|
|
3049
|
+
'''len'''
|
|
3050
|
+
def __len__(self):
|
|
3051
|
+
return len(self.fmt_streams)
|
|
3052
|
+
|
|
3053
|
+
|
|
3054
|
+
'''InnerTube'''
|
|
3055
|
+
class InnerTube:
|
|
3056
|
+
def __init__(self, client='ANDROID_VR', use_oauth=False, allow_cache=True, token_file=None, oauth_verifier=None, use_po_token=False, po_token_verifier=None):
|
|
3057
|
+
self.client_name = client
|
|
3058
|
+
self.innertube_context = DEFAULT_CLIENTS[client]['innertube_context']
|
|
3059
|
+
self.header = DEFAULT_CLIENTS[client]['header']
|
|
3060
|
+
self.api_key = DEFAULT_CLIENTS[client]['api_key']
|
|
3061
|
+
self.require_js_player = DEFAULT_CLIENTS[client]['require_js_player']
|
|
3062
|
+
self.require_po_token = DEFAULT_CLIENTS[client]['require_po_token']
|
|
3063
|
+
self.access_token = None
|
|
3064
|
+
self.refresh_token = None
|
|
3065
|
+
self.access_po_token = None
|
|
3066
|
+
self.access_visitorData = None
|
|
3067
|
+
self.use_oauth = use_oauth
|
|
3068
|
+
self.allow_cache = allow_cache
|
|
3069
|
+
self.oauth_verifier = oauth_verifier or defaultoauthverifier
|
|
3070
|
+
self.expires = None
|
|
3071
|
+
self.use_po_token = use_po_token
|
|
3072
|
+
self.po_token_verifier = po_token_verifier or defaultpotokenverifier
|
|
3073
|
+
if not self.allow_cache:
|
|
3074
|
+
cache_dir = os.path.join(os.path.dirname(__file__), '__cache__')
|
|
3075
|
+
if os.path.exists(cache_dir) and os.path.isdir(cache_dir):
|
|
3076
|
+
shutil.rmtree(cache_dir)
|
|
3077
|
+
self.token_file = token_file or os.path.join(pathlib.Path(__file__).parent.resolve() / '__cache__', 'tokens.json')
|
|
3078
|
+
if self.use_oauth and self.allow_cache and os.path.exists(self.token_file):
|
|
3079
|
+
with open(self.token_file) as f:
|
|
3080
|
+
data = json.load(f)
|
|
3081
|
+
if data['access_token']:
|
|
3082
|
+
self.access_token = data['access_token']
|
|
3083
|
+
self.refresh_token = data['refresh_token']
|
|
3084
|
+
self.expires = data['expires']
|
|
3085
|
+
self.refreshbearertoken()
|
|
3086
|
+
if self.use_po_token and self.allow_cache and os.path.exists(self.token_file):
|
|
3087
|
+
with open(self.token_file) as f:
|
|
3088
|
+
data = json.load(f)
|
|
3089
|
+
self.access_visitorData = data['visitorData']
|
|
3090
|
+
self.access_po_token = data['po_token']
|
|
3091
|
+
'''cachetokens'''
|
|
3092
|
+
def cachetokens(self):
|
|
3093
|
+
if not self.allow_cache: return
|
|
3094
|
+
data = {'access_token': self.access_token, 'refresh_token': self.refresh_token, 'expires': self.expires, 'visitorData': self.access_visitorData, 'po_token': self.access_po_token}
|
|
3095
|
+
cache_dir = os.path.dirname(self.token_file)
|
|
3096
|
+
if not os.path.exists(cache_dir): os.makedirs(cache_dir, exist_ok=True)
|
|
3097
|
+
with open(self.token_file, 'w') as f: json.dump(data, f)
|
|
3098
|
+
'''refreshbearertoken'''
|
|
3099
|
+
def refreshbearertoken(self, force=False):
|
|
3100
|
+
if not self.use_oauth: return
|
|
3101
|
+
if self.expires > time.time() and not force: return
|
|
3102
|
+
start_time = int(time.time() - 30)
|
|
3103
|
+
data = {'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'grant_type': 'refresh_token', 'refresh_token': self.refresh_token}
|
|
3104
|
+
resp = RequestWrapper._executerequest('https://oauth2.googleapis.com/token', 'POST', headers={'Content-Type': 'application/json'}, data=data)
|
|
3105
|
+
resp_data = json.loads(resp.read())
|
|
3106
|
+
self.access_token = resp_data['access_token']
|
|
3107
|
+
self.expires = start_time + resp_data['expires_in']
|
|
3108
|
+
self.cachetokens()
|
|
3109
|
+
'''fetchbearertoken'''
|
|
3110
|
+
def fetchbearertoken(self):
|
|
3111
|
+
start_time = int(time.time() - 30)
|
|
3112
|
+
data = {'client_id': CLIENT_ID, 'scope': 'https://www.googleapis.com/auth/youtube'}
|
|
3113
|
+
resp = RequestWrapper._executerequest('https://oauth2.googleapis.com/device/code', 'POST', headers={'Content-Type': 'application/json'}, data=data)
|
|
3114
|
+
resp_data = json.loads(resp.read())
|
|
3115
|
+
verification_url = resp_data['verification_url']
|
|
3116
|
+
user_code = resp_data['user_code']
|
|
3117
|
+
self.oauth_verifier(verification_url, user_code)
|
|
3118
|
+
data = {'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'device_code': resp_data['device_code'], 'grant_type': 'urn:ietf:params:oauth:grant-type:device_code'}
|
|
3119
|
+
resp = RequestWrapper._executerequest('https://oauth2.googleapis.com/token', 'POST', headers={'Content-Type': 'application/json'}, data=data)
|
|
3120
|
+
resp_data = json.loads(resp.read())
|
|
3121
|
+
self.access_token = resp_data['access_token']
|
|
3122
|
+
self.refresh_token = resp_data['refresh_token']
|
|
3123
|
+
self.expires = start_time + resp_data['expires_in']
|
|
3124
|
+
self.cachetokens()
|
|
3125
|
+
'''insertvisitordata'''
|
|
3126
|
+
def insertvisitordata(self, visitor_data: str):
|
|
3127
|
+
self.innertube_context['context']['client'].update({"visitorData": visitor_data})
|
|
3128
|
+
'''insertpotoken'''
|
|
3129
|
+
def insertpotoken(self, visitor_data: str = None, po_token : str = None):
|
|
3130
|
+
self.insertvisitordata(self.access_visitorData or visitor_data)
|
|
3131
|
+
self.innertube_context.update({"serviceIntegrityDimensions": {"poToken": self.access_po_token or po_token}})
|
|
3132
|
+
'''fetchpotoken'''
|
|
3133
|
+
def fetchpotoken(self):
|
|
3134
|
+
self.access_visitorData, self.access_po_token = self.po_token_verifier()
|
|
3135
|
+
self.cachetokens()
|
|
3136
|
+
self.insertpotoken()
|
|
3137
|
+
'''baseurl'''
|
|
3138
|
+
@property
|
|
3139
|
+
def baseurl(self):
|
|
3140
|
+
return 'https://www.youtube.com/youtubei/v1'
|
|
3141
|
+
'''basedata'''
|
|
3142
|
+
@property
|
|
3143
|
+
def basedata(self):
|
|
3144
|
+
return self.innertube_context
|
|
3145
|
+
'''baseparams'''
|
|
3146
|
+
@property
|
|
3147
|
+
def baseparams(self):
|
|
3148
|
+
return {'prettyPrint': "false"}
|
|
3149
|
+
'''callapi'''
|
|
3150
|
+
def callapi(self, endpoint, query, data):
|
|
3151
|
+
endpoint_url = f'{endpoint}?{parse.urlencode(query)}'
|
|
3152
|
+
headers = {'Content-Type': 'application/json'}
|
|
3153
|
+
if self.use_oauth:
|
|
3154
|
+
if self.access_token: self.refreshbearertoken()
|
|
3155
|
+
else: self.fetchbearertoken()
|
|
3156
|
+
headers['Authorization'] = f'Bearer {self.access_token}'
|
|
3157
|
+
if self.use_po_token:
|
|
3158
|
+
if self.access_po_token: self.insertpotoken()
|
|
3159
|
+
else: self.fetchpotoken()
|
|
3160
|
+
headers.update(self.header)
|
|
3161
|
+
resp = RequestWrapper._executerequest(endpoint_url, 'POST', headers=headers, data=data)
|
|
3162
|
+
return json.loads(resp.read())
|
|
3163
|
+
'''browse'''
|
|
3164
|
+
def browse(self, continuation=None, visitor_data=None):
|
|
3165
|
+
endpoint = f'{self.baseurl}/browse'
|
|
3166
|
+
query = self.baseparams
|
|
3167
|
+
if continuation: self.basedata.update({"continuation": continuation})
|
|
3168
|
+
if visitor_data: self.basedata['context']['client'].update({"visitorData": visitor_data})
|
|
3169
|
+
return self.callapi(endpoint, query, self.basedata)
|
|
3170
|
+
'''next'''
|
|
3171
|
+
def next(self, video_id: str = None, continuation: str = None):
|
|
3172
|
+
if continuation: self.basedata.update({"continuation": continuation})
|
|
3173
|
+
if video_id: self.basedata.update({'videoId': video_id, 'contentCheckOk': "true"})
|
|
3174
|
+
endpoint = f'{self.baseurl}/next'
|
|
3175
|
+
query = self.baseparams
|
|
3176
|
+
return self.callapi(endpoint, query, self.basedata)
|
|
3177
|
+
'''player'''
|
|
3178
|
+
def player(self, video_id):
|
|
3179
|
+
endpoint = f'{self.baseurl}/player'
|
|
3180
|
+
query = self.baseparams
|
|
3181
|
+
self.basedata.update({'videoId': video_id, 'contentCheckOk': "true"})
|
|
3182
|
+
return self.callapi(endpoint, query, self.basedata)
|
|
3183
|
+
'''search'''
|
|
3184
|
+
def search(self, search_query, continuation=None, data=None):
|
|
3185
|
+
endpoint = f'{self.baseurl}/search'
|
|
3186
|
+
query = self.baseparams
|
|
3187
|
+
data = data if data else {}
|
|
3188
|
+
self.basedata.update({'query': search_query})
|
|
3189
|
+
if continuation: data['continuation'] = continuation
|
|
3190
|
+
data.update(self.basedata)
|
|
3191
|
+
return self.callapi(endpoint, query, data)
|
|
3192
|
+
'''verifyage'''
|
|
3193
|
+
def verifyage(self, video_id):
|
|
3194
|
+
endpoint = f'{self.baseurl}/verify_age'
|
|
3195
|
+
data = {'nextEndpoint': {'watchEndpoint': {'racyCheckOk': True, 'contentCheckOk': True, 'videoId': video_id}}, 'setControvercy': True}
|
|
3196
|
+
data.update(self.basedata)
|
|
3197
|
+
result = self.callapi(endpoint, self.baseparams, data)
|
|
3198
|
+
return result
|
|
3199
|
+
'''gettranscript'''
|
|
3200
|
+
def gettranscript(self, video_id):
|
|
3201
|
+
endpoint = f'{self.baseurl}/get_transcript'
|
|
3202
|
+
query = {'videoId': video_id}
|
|
3203
|
+
query.update(self.baseparams)
|
|
3204
|
+
result = self.callapi(endpoint, query, self.basedata)
|
|
3205
|
+
return result
|
|
3206
|
+
|
|
3207
|
+
|
|
3208
|
+
'''YouTubeMetadata'''
|
|
3209
|
+
class YouTubeMetadata:
|
|
3210
|
+
def __init__(self, metadata):
|
|
3211
|
+
self._raw_metadata = metadata
|
|
3212
|
+
self._metadata = [{}]
|
|
3213
|
+
for el in metadata:
|
|
3214
|
+
if 'title' in el and 'simpleText' in el['title']: metadata_title = el['title']['simpleText']
|
|
3215
|
+
else: continue
|
|
3216
|
+
contents = el['contents'][0]
|
|
3217
|
+
if 'simpleText' in contents: self._metadata[-1][metadata_title] = contents['simpleText']
|
|
3218
|
+
elif 'runs' in contents: self._metadata[-1][metadata_title] = contents['runs'][0]['text']
|
|
3219
|
+
if el.get('hasDividerLine', False): self._metadata.append({})
|
|
3220
|
+
if self._metadata[-1] == {}: self._metadata = self._metadata[:-1]
|
|
3221
|
+
'''getitem'''
|
|
3222
|
+
def __getitem__(self, key):
|
|
3223
|
+
return self._metadata[key]
|
|
3224
|
+
'''iter'''
|
|
3225
|
+
def __iter__(self):
|
|
3226
|
+
for el in self._metadata:
|
|
3227
|
+
yield el
|
|
3228
|
+
'''str'''
|
|
3229
|
+
def __str__(self):
|
|
3230
|
+
return json.dumps(self._metadata)
|
|
3231
|
+
'''rawmetadata'''
|
|
3232
|
+
@property
|
|
3233
|
+
def rawmetadata(self):
|
|
3234
|
+
return self._raw_metadata
|
|
3235
|
+
'''metadata'''
|
|
3236
|
+
@property
|
|
3237
|
+
def metadata(self):
|
|
3238
|
+
return self._metadata
|
|
3239
|
+
|
|
3240
|
+
|
|
3241
|
+
'''Cipher'''
|
|
3242
|
+
class Cipher:
|
|
3243
|
+
def __init__(self, js: str, js_url: str):
|
|
3244
|
+
self.js_url = js_url
|
|
3245
|
+
self.js = js
|
|
3246
|
+
self._sig_param_val = None
|
|
3247
|
+
self._nsig_param_val = None
|
|
3248
|
+
self.sig_function_name = self.getsigfunctionname(js, js_url)
|
|
3249
|
+
self.nsig_function_name = self.getnsigfunctionname(js, js_url)
|
|
3250
|
+
self.runner_sig = NodeRunner(js)
|
|
3251
|
+
self.runner_sig.loadfunction(self.sig_function_name)
|
|
3252
|
+
self.runner_nsig = NodeRunner(js)
|
|
3253
|
+
self.runner_nsig.loadfunction(self.nsig_function_name)
|
|
3254
|
+
self.calculated_n = None
|
|
3255
|
+
'''getnsig'''
|
|
3256
|
+
def getnsig(self, n: str):
|
|
3257
|
+
try:
|
|
3258
|
+
if self._nsig_param_val:
|
|
3259
|
+
for param in self._nsig_param_val:
|
|
3260
|
+
nsig = self.runner_nsig.call([param, n])
|
|
3261
|
+
if not isinstance(nsig, str): continue
|
|
3262
|
+
else: break
|
|
3263
|
+
else:
|
|
3264
|
+
nsig = self.runner_nsig.call([n])
|
|
3265
|
+
except Exception as err:
|
|
3266
|
+
raise err
|
|
3267
|
+
if 'error' in nsig or '_w8_' in nsig or not isinstance(nsig, str): raise
|
|
3268
|
+
return nsig
|
|
3269
|
+
'''getsig'''
|
|
3270
|
+
def getsig(self, ciphered_signature: str):
|
|
3271
|
+
try:
|
|
3272
|
+
if self._sig_param_val: sig = self.runner_sig.call([self._sig_param_val, ciphered_signature])
|
|
3273
|
+
else: sig = self.runner_sig.call([ciphered_signature])
|
|
3274
|
+
except Exception as err:
|
|
3275
|
+
raise err
|
|
3276
|
+
if 'error' in sig or not isinstance(sig, str): raise
|
|
3277
|
+
return sig
|
|
3278
|
+
'''getsigfunctionname'''
|
|
3279
|
+
def getsigfunctionname(self, js: str, js_url: str):
|
|
3280
|
+
function_patterns = [
|
|
3281
|
+
r'(?P<sig>[a-zA-Z0-9_$]+)\s*=\s*function\(\s*(?P<arg>[a-zA-Z0-9_$]+)\s*\)\s*{\s*(?P=arg)\s*=\s*(?P=arg)\.split\(\s*[a-zA-Z0-9_\$\"\[\]]+\s*\)\s*;\s*[^}]+;\s*return\s+(?P=arg)\.join\(\s*[a-zA-Z0-9_\$\"\[\]]+\s*\)',
|
|
3282
|
+
r'(?:\b|[^a-zA-Z0-9_$])(?P<sig>[a-zA-Z0-9_$]{2,})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)(?:;[a-zA-Z0-9_$]{2}\.[a-zA-Z0-9_$]{2}\(a,\d+\))?',
|
|
3283
|
+
r'\b(?P<var>[a-zA-Z0-9_$]+)&&\((?P=var)=(?P<sig>[a-zA-Z0-9_$]{2,})\((?:(?P<param>\d+),decodeURIComponent|decodeURIComponent)\((?P=var)\)\)',
|
|
3284
|
+
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
3285
|
+
r'\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
3286
|
+
r'\bm=(?P<sig>[a-zA-Z0-9$]{2,})\(decodeURIComponent\(h\.s\)\)',
|
|
3287
|
+
r'("|\')signature\1\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
3288
|
+
r'\.sig\|\|(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
3289
|
+
r'yt\.akamaized\.net/\)\s*\|\|\s*.*?\s*[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
3290
|
+
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
3291
|
+
r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\('
|
|
3292
|
+
]
|
|
3293
|
+
for pattern in function_patterns:
|
|
3294
|
+
regex = re.compile(pattern)
|
|
3295
|
+
function_match = regex.search(js)
|
|
3296
|
+
if function_match:
|
|
3297
|
+
sig = function_match.group('sig')
|
|
3298
|
+
if "param" in function_match.groupdict():
|
|
3299
|
+
param = function_match.group('param')
|
|
3300
|
+
if param: self._sig_param_val = int(param)
|
|
3301
|
+
return sig
|
|
3302
|
+
raise
|
|
3303
|
+
'''getnsigfunctionname'''
|
|
3304
|
+
def getnsigfunctionname(self, js: str, js_url: str):
|
|
3305
|
+
try:
|
|
3306
|
+
pattern = r"var\s*[a-zA-Z0-9$_]{3}\s*=\s*\[(?P<funcname>[a-zA-Z0-9$_]{3})\]"
|
|
3307
|
+
func_name = re.search(pattern, js)
|
|
3308
|
+
if func_name: return func_name.group("funcname")
|
|
3309
|
+
else:
|
|
3310
|
+
global_obj, varname, code = extractplayerjsglobalvar(js)
|
|
3311
|
+
if global_obj and varname and code:
|
|
3312
|
+
global_obj = JSInterpreter(js).interpretexpression(code, {}, 100)
|
|
3313
|
+
for k, v in enumerate(global_obj):
|
|
3314
|
+
if v.endswith('_w8_'):
|
|
3315
|
+
pattern = r'''(?xs)
|
|
3316
|
+
[;\n](?:
|
|
3317
|
+
(?P<f>function\s+)|
|
|
3318
|
+
(?:var\s+)?
|
|
3319
|
+
)(?P<funcname>[a-zA-Z0-9_$]+)\s*(?(f)|=\s*function\s*)
|
|
3320
|
+
\(\s*(?:[a-zA-Z0-9_$]+\s*,\s*)?(?P<argname>[a-zA-Z0-9_$]+)(?:\s*,\s*[a-zA-Z0-9_$]+)*\s*\)\s*\{
|
|
3321
|
+
(?:(?!(?<!\{)\};(?![\]\)])).)*
|
|
3322
|
+
\}\s*catch\(\s*[a-zA-Z0-9_$]+\s*\)\s*
|
|
3323
|
+
\{\s*(?:return\s+|[\w=]+)%s\[%d\]\s*\+\s*(?P=argname)\s*[\};].*?\s*return\s+[^}]+\}[;\n]
|
|
3324
|
+
''' % (re.escape(varname), k)
|
|
3325
|
+
func_name = re.search(pattern, js)
|
|
3326
|
+
if func_name:
|
|
3327
|
+
n_func = func_name.group("funcname")
|
|
3328
|
+
self._nsig_param_val = self._extractnsigparamval(js, n_func)
|
|
3329
|
+
return n_func
|
|
3330
|
+
raise
|
|
3331
|
+
except Exception as err:
|
|
3332
|
+
raise err
|
|
3333
|
+
'''_extractnsigparamval'''
|
|
3334
|
+
@staticmethod
|
|
3335
|
+
def _extractnsigparamval(code: str, func_name: str):
|
|
3336
|
+
pattern = re.compile(
|
|
3337
|
+
rf'(?<![A-Za-z0-9_$\.])'
|
|
3338
|
+
rf'(?P<func>{re.escape(func_name)})\s*'
|
|
3339
|
+
r'\[\w\[\d+\]\]'
|
|
3340
|
+
r'\(\s*'
|
|
3341
|
+
r'(?P<arg1>[A-Za-z0-9_$]+)'
|
|
3342
|
+
r'(?:\s*,\s*(?P<arg2>[A-Za-z0-9_$]+))?'
|
|
3343
|
+
r'(?:\s*,\s*[^)]*)?'
|
|
3344
|
+
r'\s*\)',
|
|
3345
|
+
re.MULTILINE
|
|
3346
|
+
)
|
|
3347
|
+
results = []
|
|
3348
|
+
for m in pattern.finditer(code):
|
|
3349
|
+
chosen = m.group('arg2') if m.group('arg1') == 'this' and m.group('arg2') else m.group('arg1')
|
|
3350
|
+
results.append(chosen)
|
|
3351
|
+
return results
|
|
3352
|
+
|
|
3353
|
+
|
|
3354
|
+
'''YouTube'''
|
|
3355
|
+
class YouTube:
|
|
3356
|
+
def __init__(self, video_id: str, client: str = InnerTube().client_name, on_progress_callback: Optional[Callable[[Any, bytes, int], None]] = None, on_complete_callback: Optional[Callable[[Any, Optional[str]], None]] = None,
|
|
3357
|
+
use_oauth: bool = False, allow_oauth_cache: bool = True, token_file: Optional[str] = None, oauth_verifier: Optional[Callable[[str, str], None]] = None, use_po_token: Optional[bool] = False,
|
|
3358
|
+
po_token_verifier: Optional[Callable[[None], Tuple[str, str]]] = None):
|
|
3359
|
+
self._js: Optional[str] = None
|
|
3360
|
+
self._js_url: Optional[str] = None
|
|
3361
|
+
self._vid_info: Optional[Dict] = None
|
|
3362
|
+
self._vid_details: Optional[Dict] = None
|
|
3363
|
+
self._watch_html: Optional[str] = None
|
|
3364
|
+
self._embed_html: Optional[str] = None
|
|
3365
|
+
self._player_config_args: Optional[Dict] = None
|
|
3366
|
+
self._age_restricted: Optional[bool] = None
|
|
3367
|
+
self._fmt_streams: Optional[List[Stream]] = None
|
|
3368
|
+
self._initial_data = None
|
|
3369
|
+
self._metadata = None
|
|
3370
|
+
self.video_id = video_id
|
|
3371
|
+
self.watch_url = f"https://youtube.com/watch?v={self.video_id}"
|
|
3372
|
+
self.embed_url = f"https://www.youtube.com/embed/{self.video_id}"
|
|
3373
|
+
self.client = client
|
|
3374
|
+
self.client = 'TV' if use_oauth else self.client
|
|
3375
|
+
self.fallback_clients = ['TV', 'IOS']
|
|
3376
|
+
self._signature_timestamp: dict = {}
|
|
3377
|
+
self._visitor_data = None
|
|
3378
|
+
self.stream_monostate = Monostate(on_progress=on_progress_callback, on_complete=on_complete_callback, youtube=self)
|
|
3379
|
+
self._author = None
|
|
3380
|
+
self._title = None
|
|
3381
|
+
self.use_oauth = use_oauth
|
|
3382
|
+
self.allow_oauth_cache = allow_oauth_cache
|
|
3383
|
+
self.token_file = token_file
|
|
3384
|
+
self.oauth_verifier = oauth_verifier
|
|
3385
|
+
self.use_po_token = use_po_token
|
|
3386
|
+
self.po_token_verifier = po_token_verifier
|
|
3387
|
+
self.po_token = None
|
|
3388
|
+
self._pot = None
|
|
3389
|
+
'''watch_html'''
|
|
3390
|
+
@property
|
|
3391
|
+
def watch_html(self):
|
|
3392
|
+
if self._watch_html: return self._watch_html
|
|
3393
|
+
self._watch_html = RequestWrapper.get(url=self.watch_url)
|
|
3394
|
+
return self._watch_html
|
|
3395
|
+
'''embed_html'''
|
|
3396
|
+
@property
|
|
3397
|
+
def embed_html(self):
|
|
3398
|
+
if self._embed_html: return self._embed_html
|
|
3399
|
+
self._embed_html = RequestWrapper.get(url=self.embed_url)
|
|
3400
|
+
return self._embed_html
|
|
3401
|
+
'''age_restricted'''
|
|
3402
|
+
@property
|
|
3403
|
+
def age_restricted(self):
|
|
3404
|
+
if self._age_restricted: return self._age_restricted
|
|
3405
|
+
self._age_restricted = isagerestricted(self.watch_html)
|
|
3406
|
+
return self._age_restricted
|
|
3407
|
+
'''js_url'''
|
|
3408
|
+
@property
|
|
3409
|
+
def js_url(self):
|
|
3410
|
+
if self._js_url: return self._js_url
|
|
3411
|
+
if self.age_restricted: self._js_url = extractjsurl(self.embed_html)
|
|
3412
|
+
else: self._js_url = extractjsurl(self.watch_html)
|
|
3413
|
+
return self._js_url
|
|
3414
|
+
'''js'''
|
|
3415
|
+
@property
|
|
3416
|
+
def js(self):
|
|
3417
|
+
if self._js: return self._js
|
|
3418
|
+
self._js = RequestWrapper.get(self.js_url)
|
|
3419
|
+
return self._js
|
|
3420
|
+
'''visitor_data'''
|
|
3421
|
+
@property
|
|
3422
|
+
def visitor_data(self):
|
|
3423
|
+
if self._visitor_data: return self._visitor_data
|
|
3424
|
+
if InnerTube(self.client).require_po_token:
|
|
3425
|
+
try:
|
|
3426
|
+
self._visitor_data = extractvisitordata(str(self.initial_data['responseContext']))
|
|
3427
|
+
return self._visitor_data
|
|
3428
|
+
except:
|
|
3429
|
+
pass
|
|
3430
|
+
innertube_response = InnerTube('WEB').player(self.video_id)
|
|
3431
|
+
try:
|
|
3432
|
+
self._visitor_data = innertube_response['responseContext']['visitorData']
|
|
3433
|
+
except KeyError:
|
|
3434
|
+
p_dicts = innertube_response['responseContext']['serviceTrackingParams'][0]['params']
|
|
3435
|
+
self._visitor_data = next(p for p in p_dicts if p['key'] == 'visitor_data')['value']
|
|
3436
|
+
return self._visitor_data
|
|
3437
|
+
'''pot'''
|
|
3438
|
+
@property
|
|
3439
|
+
def pot(self):
|
|
3440
|
+
if self._pot: return self._pot
|
|
3441
|
+
try:
|
|
3442
|
+
self._pot = generatepotoken(video_id=self.video_id)
|
|
3443
|
+
except Exception as err:
|
|
3444
|
+
pass
|
|
3445
|
+
return self._pot
|
|
3446
|
+
'''initial_data'''
|
|
3447
|
+
@property
|
|
3448
|
+
def initial_data(self):
|
|
3449
|
+
if self._initial_data: return self._initial_data
|
|
3450
|
+
self._initial_data = extractinitialdata(self.watch_html)
|
|
3451
|
+
return self._initial_data
|
|
3452
|
+
'''streaming_data'''
|
|
3453
|
+
@property
|
|
3454
|
+
def streaming_data(self):
|
|
3455
|
+
invalid_id_list = ['aQvGIIdgFDM']
|
|
3456
|
+
if 'streamingData' not in self.vid_info or self.vid_info['videoDetails']['videoId'] in invalid_id_list:
|
|
3457
|
+
for client in self.fallback_clients:
|
|
3458
|
+
self.client = client
|
|
3459
|
+
self.vid_info = None
|
|
3460
|
+
if 'streamingData' in self.vid_info: break
|
|
3461
|
+
return self.vid_info['streamingData']
|
|
3462
|
+
'''fmt_streams'''
|
|
3463
|
+
@property
|
|
3464
|
+
def fmt_streams(self):
|
|
3465
|
+
if self._fmt_streams: return self._fmt_streams
|
|
3466
|
+
self._fmt_streams = []
|
|
3467
|
+
stream_manifest = applydescrambler(self.streaming_data)
|
|
3468
|
+
inner_tube = InnerTube(self.client)
|
|
3469
|
+
if self.po_token: applypotoken(stream_manifest, self.vid_info, self.po_token)
|
|
3470
|
+
if inner_tube.require_js_player:
|
|
3471
|
+
try:
|
|
3472
|
+
applysignature(stream_manifest, self.vid_info, self.js, self.js_url)
|
|
3473
|
+
except:
|
|
3474
|
+
self._js = None
|
|
3475
|
+
self._js_url = None
|
|
3476
|
+
applysignature(stream_manifest, self.vid_info, self.js, self.js_url)
|
|
3477
|
+
for stream in stream_manifest:
|
|
3478
|
+
video = Stream(stream=stream, monostate=self.stream_monostate, po_token=self.po_token, video_playback_ustreamer_config=self.video_playback_ustreamer_config)
|
|
3479
|
+
self._fmt_streams.append(video)
|
|
3480
|
+
self.stream_monostate.title = self.title
|
|
3481
|
+
self.stream_monostate.duration = self.length
|
|
3482
|
+
return self._fmt_streams
|
|
3483
|
+
'''signature_timestamp'''
|
|
3484
|
+
@property
|
|
3485
|
+
def signature_timestamp(self):
|
|
3486
|
+
if not self._signature_timestamp:
|
|
3487
|
+
self._signature_timestamp = {'playbackContext': {'contentPlaybackContext': {'signatureTimestamp': extractsignaturetimestamp(self.js)}}}
|
|
3488
|
+
return self._signature_timestamp
|
|
3489
|
+
'''video_playback_ustreamer_config'''
|
|
3490
|
+
@property
|
|
3491
|
+
def video_playback_ustreamer_config(self):
|
|
3492
|
+
return self.vid_info['playerConfig']['mediaCommonConfig']['mediaUstreamerRequestConfig']['videoPlaybackUstreamerConfig']
|
|
3493
|
+
'''server_abr_streaming_url'''
|
|
3494
|
+
@property
|
|
3495
|
+
def server_abr_streaming_url(self):
|
|
3496
|
+
try:
|
|
3497
|
+
url = self.vid_info['streamingData']['serverAbrStreamingUrl']
|
|
3498
|
+
stream_manifest = [{"url": url}]
|
|
3499
|
+
applysignature(stream_manifest, vid_info=self.vid_info, js=self.js, url_js=self.js_url)
|
|
3500
|
+
return stream_manifest[0]["url"]
|
|
3501
|
+
except Exception:
|
|
3502
|
+
return None
|
|
3503
|
+
'''vid_info'''
|
|
3504
|
+
@property
|
|
3505
|
+
def vid_info(self):
|
|
3506
|
+
if self._vid_info: return self._vid_info
|
|
3507
|
+
self._vid_info = self.vid_info_client()
|
|
3508
|
+
return self._vid_info
|
|
3509
|
+
@vid_info.setter
|
|
3510
|
+
def vid_info(self, value):
|
|
3511
|
+
self._vid_info = value
|
|
3512
|
+
'''vid_info_client'''
|
|
3513
|
+
def vid_info_client(self, optional_client=None):
|
|
3514
|
+
if optional_client is None:
|
|
3515
|
+
if self._vid_info: return self._vid_info
|
|
3516
|
+
optional_client = self.client
|
|
3517
|
+
def _callinnertube(optional_client):
|
|
3518
|
+
innertube = InnerTube(
|
|
3519
|
+
client=optional_client, use_oauth=self.use_oauth, allow_cache=self.allow_oauth_cache, token_file=self.token_file, oauth_verifier=self.oauth_verifier,
|
|
3520
|
+
use_po_token=self.use_po_token, po_token_verifier=self.po_token_verifier
|
|
3521
|
+
)
|
|
3522
|
+
if innertube.require_js_player: innertube.innertube_context.update(self.signature_timestamp)
|
|
3523
|
+
if innertube.require_po_token and not self.use_po_token: innertube.insertvisitordata(visitor_data=self.visitor_data)
|
|
3524
|
+
elif not self.use_po_token: innertube.insertvisitordata(visitor_data=self.visitor_data)
|
|
3525
|
+
response = innertube.player(self.video_id)
|
|
3526
|
+
if self.use_po_token or innertube.require_po_token: self.po_token = innertube.access_po_token or self.pot
|
|
3527
|
+
return response
|
|
3528
|
+
innertube_response = _callinnertube(optional_client)
|
|
3529
|
+
for client in self.fallback_clients:
|
|
3530
|
+
playability_status = innertube_response['playabilityStatus']
|
|
3531
|
+
if playability_status['status'] == 'UNPLAYABLE' and 'reason' in playability_status and playability_status['reason'] == 'This video is not available':
|
|
3532
|
+
self.client = client
|
|
3533
|
+
innertube_response = _callinnertube(client)
|
|
3534
|
+
else:
|
|
3535
|
+
break
|
|
3536
|
+
return innertube_response
|
|
3537
|
+
'''vid_details'''
|
|
3538
|
+
@property
|
|
3539
|
+
def vid_details(self):
|
|
3540
|
+
if self._vid_details: return self._vid_details
|
|
3541
|
+
innertube = InnerTube(
|
|
3542
|
+
client='TV' if self.use_oauth else 'WEB', use_oauth=self.use_oauth, allow_cache=self.allow_oauth_cache, token_file=self.token_file,
|
|
3543
|
+
oauth_verifier=self.oauth_verifier, use_po_token=self.use_po_token, po_token_verifier=self.po_token_verifier
|
|
3544
|
+
)
|
|
3545
|
+
innertube_response = innertube.next(self.video_id)
|
|
3546
|
+
self._vid_details = innertube_response
|
|
3547
|
+
return self._vid_details
|
|
3548
|
+
@vid_details.setter
|
|
3549
|
+
def vid_details(self, value):
|
|
3550
|
+
self._vid_details = value
|
|
3551
|
+
'''streams'''
|
|
3552
|
+
@property
|
|
3553
|
+
def streams(self):
|
|
3554
|
+
return StreamQuery(self.fmt_streams)
|
|
3555
|
+
'''vid_engagement_items'''
|
|
3556
|
+
def vid_engagement_items(self):
|
|
3557
|
+
for i in range(len(self.vid_details.get('engagementPanels', []))):
|
|
3558
|
+
try:
|
|
3559
|
+
return self.vid_details['engagementPanels'][i]['engagementPanelSectionListRenderer']['content']['structuredDescriptionContentRenderer']['items']
|
|
3560
|
+
except:
|
|
3561
|
+
continue
|
|
3562
|
+
'''title'''
|
|
3563
|
+
@property
|
|
3564
|
+
def title(self):
|
|
3565
|
+
self._author = self.vid_info.get("videoDetails", {}).get("author", "unknown")
|
|
3566
|
+
if self._title: return self._title
|
|
3567
|
+
if self.use_oauth == True: self._title = self.vid_engagement_items()[0]['videoDescriptionHeaderRenderer']['title']['runs'][0]['text']
|
|
3568
|
+
if 'title' in self.vid_info['videoDetails']:
|
|
3569
|
+
self._title = self.vid_info['videoDetails']['title']
|
|
3570
|
+
else:
|
|
3571
|
+
if 'singleColumnWatchNextResults' in self.vid_details['contents']:
|
|
3572
|
+
contents = self.vid_details['contents']['singleColumnWatchNextResults']['results']['results']['contents'][0]['itemSectionRenderer']['contents'][0]
|
|
3573
|
+
if 'videoMetadataRenderer' in contents:
|
|
3574
|
+
self._title = contents['videoMetadataRenderer']['title']['runs'][0]['text']
|
|
3575
|
+
else:
|
|
3576
|
+
self._title = contents['musicWatchMetadataRenderer']['title']['simpleText']
|
|
3577
|
+
elif 'twoColumnWatchNextResults' in self.vid_details['contents']:
|
|
3578
|
+
contents = self.vid_details['contents']['twoColumnWatchNextResults']['results']['results']['contents']
|
|
3579
|
+
for videoPrimaryInfoRenderer in contents:
|
|
3580
|
+
if 'videoPrimaryInfoRenderer' in videoPrimaryInfoRenderer:
|
|
3581
|
+
self._title = videoPrimaryInfoRenderer['videoPrimaryInfoRenderer']['title']['runs'][0]['text']
|
|
3582
|
+
break
|
|
3583
|
+
return self._title
|
|
3584
|
+
@title.setter
|
|
3585
|
+
def title(self, value):
|
|
3586
|
+
self._title = value
|
|
3587
|
+
'''length'''
|
|
3588
|
+
@property
|
|
3589
|
+
def length(self):
|
|
3590
|
+
return int(self.vid_info.get('videoDetails', {}).get('lengthSeconds'))
|
|
3591
|
+
'''author'''
|
|
3592
|
+
@property
|
|
3593
|
+
def author(self):
|
|
3594
|
+
_author = self.vid_info.get("videoDetails", {}).get("author", "unknown")
|
|
3595
|
+
if self.use_oauth == True: _author = self.vid_engagement_items()[0]['videoDescriptionHeaderRenderer']['channel']['simpleText']
|
|
3596
|
+
self._author = _author
|
|
3597
|
+
return self._author
|
|
3598
|
+
@author.setter
|
|
3599
|
+
def author(self, value):
|
|
3600
|
+
self._author = value
|
|
3601
|
+
'''metadata'''
|
|
3602
|
+
@property
|
|
3603
|
+
def metadata(self):
|
|
3604
|
+
if not self._metadata:
|
|
3605
|
+
self._metadata = extractmetadata(self.initial_data)
|
|
3606
|
+
return self._metadata
|