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.
Files changed (59) hide show
  1. musicdl/__init__.py +5 -5
  2. musicdl/modules/__init__.py +10 -3
  3. musicdl/modules/common/__init__.py +2 -0
  4. musicdl/modules/common/gdstudio.py +204 -0
  5. musicdl/modules/js/__init__.py +1 -0
  6. musicdl/modules/js/youtube/__init__.py +2 -0
  7. musicdl/modules/js/youtube/botguard.js +1 -0
  8. musicdl/modules/js/youtube/jsinterp.py +902 -0
  9. musicdl/modules/js/youtube/runner.js +2 -0
  10. musicdl/modules/sources/__init__.py +41 -10
  11. musicdl/modules/sources/apple.py +207 -0
  12. musicdl/modules/sources/base.py +256 -28
  13. musicdl/modules/sources/bilibili.py +118 -0
  14. musicdl/modules/sources/buguyy.py +148 -0
  15. musicdl/modules/sources/fangpi.py +153 -0
  16. musicdl/modules/sources/fivesing.py +108 -0
  17. musicdl/modules/sources/gequbao.py +148 -0
  18. musicdl/modules/sources/jamendo.py +108 -0
  19. musicdl/modules/sources/joox.py +104 -68
  20. musicdl/modules/sources/kugou.py +129 -76
  21. musicdl/modules/sources/kuwo.py +188 -68
  22. musicdl/modules/sources/lizhi.py +107 -0
  23. musicdl/modules/sources/migu.py +172 -66
  24. musicdl/modules/sources/mitu.py +140 -0
  25. musicdl/modules/sources/mp3juice.py +264 -0
  26. musicdl/modules/sources/netease.py +163 -115
  27. musicdl/modules/sources/qianqian.py +125 -77
  28. musicdl/modules/sources/qq.py +232 -94
  29. musicdl/modules/sources/tidal.py +342 -0
  30. musicdl/modules/sources/ximalaya.py +256 -0
  31. musicdl/modules/sources/yinyuedao.py +144 -0
  32. musicdl/modules/sources/youtube.py +238 -0
  33. musicdl/modules/utils/__init__.py +12 -4
  34. musicdl/modules/utils/appleutils.py +563 -0
  35. musicdl/modules/utils/data.py +107 -0
  36. musicdl/modules/utils/logger.py +211 -58
  37. musicdl/modules/utils/lyric.py +73 -0
  38. musicdl/modules/utils/misc.py +335 -23
  39. musicdl/modules/utils/modulebuilder.py +75 -0
  40. musicdl/modules/utils/neteaseutils.py +81 -0
  41. musicdl/modules/utils/qqutils.py +184 -0
  42. musicdl/modules/utils/quarkparser.py +105 -0
  43. musicdl/modules/utils/songinfoutils.py +54 -0
  44. musicdl/modules/utils/tidalutils.py +738 -0
  45. musicdl/modules/utils/youtubeutils.py +3606 -0
  46. musicdl/musicdl.py +184 -86
  47. musicdl-2.7.3.dist-info/LICENSE +203 -0
  48. musicdl-2.7.3.dist-info/METADATA +704 -0
  49. musicdl-2.7.3.dist-info/RECORD +53 -0
  50. {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/WHEEL +5 -5
  51. musicdl-2.7.3.dist-info/entry_points.txt +2 -0
  52. musicdl/modules/sources/baiduFlac.py +0 -69
  53. musicdl/modules/sources/xiami.py +0 -104
  54. musicdl/modules/utils/downloader.py +0 -80
  55. musicdl-2.1.11.dist-info/LICENSE +0 -22
  56. musicdl-2.1.11.dist-info/METADATA +0 -82
  57. musicdl-2.1.11.dist-info/RECORD +0 -24
  58. {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/top_level.txt +0 -0
  59. {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