Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
0dc1747178
|
|||
|
8577164785
|
|||
|
8af98968dd
|
|||
|
8f00cbcdd6
|
|||
|
af75551bc2
|
|||
|
3a6cc1e44f
|
|||
|
7664b5f0ff
|
|||
|
ec5d236cad
|
|||
|
d6b7a255d0
|
|||
|
22bc7324db
|
|||
|
48e8f271e7
|
|||
|
9a0ad6070b
|
|||
|
6039589f24
|
|||
|
d4cba7eb6c
|
|||
|
70cb453280
|
|||
|
7a106331e7
|
|||
|
8775e131af
|
|||
|
1f16f7cb62
|
|||
|
80b7f3cd00
|
|||
|
8b79e067bc
|
|||
|
cda0627d5a
|
|||
|
ad40dd6d6b
|
|||
|
b91d53dc6f
|
|||
|
cda4fd1f26
|
|||
|
ff2a2edaa5
|
|||
|
38d8d5d4c5
|
|||
|
f010452abf
|
@@ -1,5 +1,3 @@
|
|||||||
[](https://drone.hgit.ga/heckyel/yt-local)
|
|
||||||
|
|
||||||
# yt-local
|
# yt-local
|
||||||
|
|
||||||
Fork of [youtube-local](https://github.com/user234683/youtube-local)
|
Fork of [youtube-local](https://github.com/user234683/youtube-local)
|
||||||
@@ -153,7 +151,7 @@ For coding guidelines and an overview of the software architecture, see the [HAC
|
|||||||
|
|
||||||
yt-local is not made to work in public mode, however there is an instance of yt-local in public mode but with less features
|
yt-local is not made to work in public mode, however there is an instance of yt-local in public mode but with less features
|
||||||
|
|
||||||
- <https://1cd1-93-95-230-133.ngrok-free.app/https://youtube.com>
|
- <https://m.fridu.us/https://youtube.com>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -114,10 +114,12 @@ if bitness == '32':
|
|||||||
visual_c_runtime_url = 'https://github.com/yuempek/vc-archive/raw/master/archives/vc15_(14.10.25017.0)_2017_x86.7z'
|
visual_c_runtime_url = 'https://github.com/yuempek/vc-archive/raw/master/archives/vc15_(14.10.25017.0)_2017_x86.7z'
|
||||||
visual_c_runtime_sha256 = '2549eb4d2ce4cf3a87425ea01940f74368bf1cda378ef8a8a1f1a12ed59f1547'
|
visual_c_runtime_sha256 = '2549eb4d2ce4cf3a87425ea01940f74368bf1cda378ef8a8a1f1a12ed59f1547'
|
||||||
visual_c_name = 'vc15_(14.10.25017.0)_2017_x86.7z'
|
visual_c_name = 'vc15_(14.10.25017.0)_2017_x86.7z'
|
||||||
|
visual_c_path_to_dlls = 'runtime_minimum/System'
|
||||||
else:
|
else:
|
||||||
visual_c_runtime_url = 'https://github.com/yuempek/vc-archive/raw/master/archives/vc15_(14.10.25017.0)_2017_x64.7z'
|
visual_c_runtime_url = 'https://github.com/yuempek/vc-archive/raw/master/archives/vc15_(14.10.25017.0)_2017_x64.7z'
|
||||||
visual_c_runtime_sha256 = '4f00b824c37e1017a93fccbd5775e6ee54f824b6786f5730d257a87a3d9ce921'
|
visual_c_runtime_sha256 = '4f00b824c37e1017a93fccbd5775e6ee54f824b6786f5730d257a87a3d9ce921'
|
||||||
visual_c_name = 'vc15_(14.10.25017.0)_2017_x64.7z'
|
visual_c_name = 'vc15_(14.10.25017.0)_2017_x64.7z'
|
||||||
|
visual_c_path_to_dlls = 'runtime_minimum/System64'
|
||||||
|
|
||||||
download_if_not_exists('get-pip.py', get_pip_url)
|
download_if_not_exists('get-pip.py', get_pip_url)
|
||||||
|
|
||||||
@@ -198,7 +200,7 @@ with open('./python/python3' + major_release + '._pth', 'a', encoding='utf-8') a
|
|||||||
f.write('..\n')'''
|
f.write('..\n')'''
|
||||||
|
|
||||||
log('Inserting Microsoft C Runtime')
|
log('Inserting Microsoft C Runtime')
|
||||||
check_subp(subprocess.run([r'7z', '-y', 'e', '-opython', 'vc15_(14.10.25017.0)_2017_x86.7z', 'runtime_minimum/System']))
|
check_subp(subprocess.run([r'7z', '-y', 'e', '-opython', visual_c_name, visual_c_path_to_dlls]))
|
||||||
|
|
||||||
log('Installing dependencies')
|
log('Installing dependencies')
|
||||||
wine_run(['./python/python.exe', '-I', '-m', 'pip', 'install', '--no-compile', '-r', './requirements.txt'])
|
wine_run(['./python/python.exe', '-I', '-m', 'pip', 'install', '--no-compile', '-r', './requirements.txt'])
|
||||||
|
|||||||
@@ -1,28 +1,21 @@
|
|||||||
attrs==22.1.0
|
blinker==1.7.0
|
||||||
Brotli==1.0.9
|
Brotli==1.1.0
|
||||||
cachetools==4.2.4
|
cachetools==5.3.3
|
||||||
click==8.0.4
|
click==8.1.7
|
||||||
dataclasses==0.6
|
|
||||||
defusedxml==0.7.1
|
defusedxml==0.7.1
|
||||||
Flask==2.0.1
|
Flask==3.0.2
|
||||||
gevent==22.10.2
|
gevent==24.2.1
|
||||||
greenlet==2.0.1
|
greenlet==3.0.3
|
||||||
importlib-metadata==4.6.4
|
iniconfig==2.0.0
|
||||||
iniconfig==1.1.1
|
itsdangerous==2.1.2
|
||||||
itsdangerous==2.0.1
|
Jinja2==3.1.3
|
||||||
Jinja2==3.0.3
|
MarkupSafe==2.1.5
|
||||||
MarkupSafe==2.0.1
|
packaging==24.0
|
||||||
packaging==20.9
|
pluggy==1.4.0
|
||||||
pluggy>=0.13.1
|
|
||||||
py==1.10.0
|
|
||||||
pyparsing==2.4.7
|
|
||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
pytest==6.2.5
|
pytest==8.1.1
|
||||||
stem==1.8.0
|
stem==1.8.2
|
||||||
toml==0.10.2
|
urllib3==2.2.1
|
||||||
typing-extensions==3.10.0.2
|
Werkzeug==3.0.1
|
||||||
urllib3==1.26.11
|
zope.event==5.0
|
||||||
Werkzeug==2.1.1
|
zope.interface==6.2
|
||||||
zipp==3.5.1
|
|
||||||
zope.event==4.5.0
|
|
||||||
zope.interface==5.4.0
|
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
Brotli==1.0.9
|
blinker==1.7.0
|
||||||
cachetools==4.2.4
|
Brotli==1.1.0
|
||||||
click==8.0.4
|
cachetools==5.3.3
|
||||||
dataclasses==0.6
|
click==8.1.7
|
||||||
defusedxml==0.7.1
|
defusedxml==0.7.1
|
||||||
Flask==2.0.1
|
Flask==3.0.2
|
||||||
gevent==22.10.2
|
gevent==24.2.1
|
||||||
greenlet==2.0.1
|
greenlet==3.0.3
|
||||||
importlib-metadata==4.6.4
|
itsdangerous==2.1.2
|
||||||
itsdangerous==2.0.1
|
Jinja2==3.1.3
|
||||||
Jinja2==3.0.3
|
MarkupSafe==2.1.5
|
||||||
MarkupSafe==2.0.1
|
|
||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
stem==1.8.0
|
stem==1.8.2
|
||||||
typing-extensions==3.10.0.2
|
urllib3==2.2.1
|
||||||
urllib3==1.26.11
|
Werkzeug==3.0.1
|
||||||
Werkzeug==2.1.1
|
zope.event==5.0
|
||||||
zipp==3.5.1
|
zope.interface==6.2
|
||||||
zope.event==4.5.0
|
|
||||||
zope.interface==5.4.0
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def proxy_site(env, start_response, video=False):
|
|||||||
else:
|
else:
|
||||||
response, cleanup_func = util.fetch_url_response(url, send_headers)
|
response, cleanup_func = util.fetch_url_response(url, send_headers)
|
||||||
|
|
||||||
response_headers = response.getheaders()
|
response_headers = response.headers
|
||||||
if isinstance(response_headers, urllib3._collections.HTTPHeaderDict):
|
if isinstance(response_headers, urllib3._collections.HTTPHeaderDict):
|
||||||
response_headers = response_headers.items()
|
response_headers = response_headers.items()
|
||||||
if video:
|
if video:
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ def get_channel_tab(channel_id, page="1", sort=3, tab='videos', view=1,
|
|||||||
'hl': 'en',
|
'hl': 'en',
|
||||||
'gl': 'US',
|
'gl': 'US',
|
||||||
'clientName': 'WEB',
|
'clientName': 'WEB',
|
||||||
'clientVersion': '2.20180830',
|
'clientVersion': '2.20240327.00.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'continuation': ctoken,
|
'continuation': ctoken,
|
||||||
@@ -371,7 +371,7 @@ def get_channel_search_json(channel_id, query, page):
|
|||||||
'hl': 'en',
|
'hl': 'en',
|
||||||
'gl': 'US',
|
'gl': 'US',
|
||||||
'clientName': 'WEB',
|
'clientName': 'WEB',
|
||||||
'clientVersion': '2.20180830',
|
'clientVersion': '2.20240327.00.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'continuation': ctoken,
|
'continuation': ctoken,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def request_comments(ctoken, replies=False):
|
|||||||
'hl': 'en',
|
'hl': 'en',
|
||||||
'gl': 'US',
|
'gl': 'US',
|
||||||
'clientName': 'MWEB',
|
'clientName': 'MWEB',
|
||||||
'clientVersion': '2.20210804.02.00',
|
'clientVersion': '2.20240328.08.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'continuation': ctoken.replace('=', '%3D'),
|
'continuation': ctoken.replace('=', '%3D'),
|
||||||
|
|||||||
@@ -256,7 +256,8 @@ hr {
|
|||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border: none;
|
border: 1px solid;
|
||||||
|
border-color: var(--button-border);
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
:root {
|
:root {
|
||||||
--background: #212121;
|
--background: #121113;
|
||||||
--text: #FFFFFF;
|
--text: #FFFFFF;
|
||||||
--secondary-hover: #73828c;
|
--secondary-hover: #222222;
|
||||||
--secondary-focus: #303030;
|
--secondary-focus: #121113;
|
||||||
--secondary-inverse: #FFF;
|
--secondary-inverse: #FFFFFF;
|
||||||
--primary-background: #242424;
|
--primary-background: #242424;
|
||||||
--secondary-background: #424242;
|
--secondary-background: #222222;
|
||||||
--thumb-background: #757575;
|
--thumb-background: #222222;
|
||||||
--link: #00B0FF;
|
--link: #00B0FF;
|
||||||
--link-visited: #40C4FF;
|
--link-visited: #40C4FF;
|
||||||
--border-bg: #FFFFFF;
|
--border-bg: #222222;
|
||||||
--buttom: #dcdcdb;
|
--border-bg-settings: #000000;
|
||||||
--buttom-text: #415462;
|
--border-bg-license: #000000;
|
||||||
--button-border: #91918c;
|
--buttom: #121113;
|
||||||
--buttom-hover: #BBB;
|
--buttom-text: #FFFFFF;
|
||||||
--search-text: #FFF;
|
--button-border: #222222;
|
||||||
--time-background: #212121;
|
--buttom-hover: #222222;
|
||||||
--time-text: #FFF;
|
--search-text: #FFFFFF;
|
||||||
|
--time-background: #121113;
|
||||||
|
--time-text: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
:root {
|
:root {
|
||||||
--background: #2d3743;
|
--background: #2D3743;
|
||||||
--text: #FFFFFF;
|
--text: #FFFFFF;
|
||||||
--secondary-hover: #73828c;
|
--secondary-hover: #73828C;
|
||||||
--secondary-focus: rgba(115, 130, 140, 0.125);
|
--secondary-focus: rgba(115, 130, 140, 0.125);
|
||||||
--secondary-inverse: #FFFFFF;
|
--secondary-inverse: #FFFFFF;
|
||||||
--primary-background: #2d3743;
|
--primary-background: #2D3743;
|
||||||
--secondary-background: #102027;
|
--secondary-background: #102027;
|
||||||
--thumb-background: #35404D;
|
--thumb-background: #35404D;
|
||||||
--link: #22aaff;
|
--link: #22AAFF;
|
||||||
--link-visited: #7755ff;
|
--link-visited: #7755FF;
|
||||||
--border-bg: #FFFFFF;
|
--border-bg: #FFFFFF;
|
||||||
--buttom: #DCDCDC;
|
--border-bg-settings: #FFFFFF;
|
||||||
--buttom-text: #415462;
|
--border-bg-license: #FFFFFF;
|
||||||
--button-border: #91918c;
|
--buttom: #2D3743;
|
||||||
--buttom-hover: #BBBBBB;
|
--buttom-text: #FFFFFF;
|
||||||
|
--button-border: #102027;
|
||||||
|
--buttom-hover: #102027;
|
||||||
--search-text: #FFFFFF;
|
--search-text: #FFFFFF;
|
||||||
--time-background: #212121;
|
--time-background: #212121;
|
||||||
--time-text: #FFFFFF;
|
--time-text: #FFFFFF;
|
||||||
|
|||||||
@@ -569,7 +569,8 @@ function fetchRange(url, start, end, debugInfo) {
|
|||||||
onFailure(e, 'Network error');
|
onFailure(e, 'Network error');
|
||||||
};
|
};
|
||||||
xhr.ontimeout = function (event){
|
xhr.ontimeout = function (event){
|
||||||
onFailure(null, 'Timeout (15s)', maxRetries=1);
|
xhr.timeout += 5000;
|
||||||
|
onFailure(null, 'Timeout (15s)', maxRetries=5);
|
||||||
};
|
};
|
||||||
xhr.send();
|
xhr.send();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ label[for=options-toggle-cbox] {
|
|||||||
|
|
||||||
.table td,.table th {
|
.table td,.table th {
|
||||||
padding: 10px 10px;
|
padding: 10px 10px;
|
||||||
border: 1px solid var(--secondary-background);
|
border: 1px solid var(--border-bg-license);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@
|
|||||||
--link: #212121;
|
--link: #212121;
|
||||||
--link-visited: #808080;
|
--link-visited: #808080;
|
||||||
--border-bg: #212121;
|
--border-bg: #212121;
|
||||||
--buttom: #DCDCDC;
|
--border-bg-settings: #91918C;
|
||||||
|
--border-bg-license: #91918C;
|
||||||
|
--buttom: #FFFFFF;
|
||||||
--buttom-text: #212121;
|
--buttom-text: #212121;
|
||||||
--button-border: #91918c;
|
--button-border: #91918C;
|
||||||
--buttom-hover: #BBBBBB;
|
--buttom-hover: #BBBBBB;
|
||||||
--search-text: #212121;
|
--search-text: #212121;
|
||||||
--time-background: #212121;
|
--time-background: #212121;
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ label[for=options-toggle-cbox] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-form > h2 {
|
.settings-form > h2 {
|
||||||
border-bottom: 2px solid var(--border-bg);
|
border-bottom: 2px solid var(--border-bg-settings);
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
108
youtube/util.py
108
youtube/util.py
@@ -336,7 +336,7 @@ def fetch_url(url, headers=(), timeout=15, report_text=None, data=None,
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
print(response.status, response.reason, response.getheaders())
|
print(response.status, response.reason, response.headers)
|
||||||
ip = re.search(
|
ip = re.search(
|
||||||
br'IP address: ((?:[\da-f]*:)+[\da-f]+|(?:\d+\.)+\d+)',
|
br'IP address: ((?:[\da-f]*:)+[\da-f]+|(?:\d+\.)+\d+)',
|
||||||
content)
|
content)
|
||||||
@@ -395,22 +395,22 @@ def head(url, use_tor=False, report_text=None, max_redirects=10):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
mobile_user_agent = 'Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36'
|
mobile_user_agent = 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.80 Mobile Safari/537.36'
|
||||||
mobile_ua = (('User-Agent', mobile_user_agent),)
|
mobile_ua = (('User-Agent', mobile_user_agent),)
|
||||||
desktop_user_agent = 'Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0'
|
desktop_user_agent = 'Mozilla/5.0 (Windows NT 10.0; rv:124.0) Gecko/20100101 Firefox/124.0'
|
||||||
desktop_ua = (('User-Agent', desktop_user_agent),)
|
desktop_ua = (('User-Agent', desktop_user_agent),)
|
||||||
json_header = (('Content-Type', 'application/json'),)
|
json_header = (('Content-Type', 'application/json'),)
|
||||||
desktop_xhr_headers = (
|
desktop_xhr_headers = (
|
||||||
('Accept', '*/*'),
|
('Accept', '*/*'),
|
||||||
('Accept-Language', 'en-US,en;q=0.5'),
|
('Accept-Language', 'en-US,en;q=0.5'),
|
||||||
('X-YouTube-Client-Name', '1'),
|
('X-YouTube-Client-Name', '1'),
|
||||||
('X-YouTube-Client-Version', '2.20180830'),
|
('X-YouTube-Client-Version', '2.20240327.00.00'),
|
||||||
) + desktop_ua
|
) + desktop_ua
|
||||||
mobile_xhr_headers = (
|
mobile_xhr_headers = (
|
||||||
('Accept', '*/*'),
|
('Accept', '*/*'),
|
||||||
('Accept-Language', 'en-US,en;q=0.5'),
|
('Accept-Language', 'en-US,en;q=0.5'),
|
||||||
('X-YouTube-Client-Name', '2'),
|
('X-YouTube-Client-Name', '1'),
|
||||||
('X-YouTube-Client-Version', '2.20180830'),
|
('X-YouTube-Client-Version', '2.20240328.08.00'),
|
||||||
) + mobile_ua
|
) + mobile_ua
|
||||||
|
|
||||||
|
|
||||||
@@ -431,34 +431,29 @@ class RateLimitedQueue(gevent.queue.Queue):
|
|||||||
gevent.queue.Queue.__init__(self)
|
gevent.queue.Queue.__init__(self)
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
self.lock.acquire() # blocks if another greenlet currently has the lock
|
with self.lock: # blocks if another greenlet currently has the lock
|
||||||
if self.count_since_last_wait >= self.subsequent_bursts and self.surpassed_initial:
|
if ((self.count_since_last_wait >= self.subsequent_bursts and self.surpassed_initial) or
|
||||||
gevent.sleep(self.waiting_period)
|
(self.count_since_last_wait >= self.initial_burst and not self.surpassed_initial)):
|
||||||
self.count_since_last_wait = 0
|
self.surpassed_initial = True
|
||||||
|
gevent.sleep(self.waiting_period)
|
||||||
elif self.count_since_last_wait >= self.initial_burst and not self.surpassed_initial:
|
|
||||||
self.surpassed_initial = True
|
|
||||||
gevent.sleep(self.waiting_period)
|
|
||||||
self.count_since_last_wait = 0
|
|
||||||
|
|
||||||
self.count_since_last_wait += 1
|
|
||||||
|
|
||||||
if not self.currently_empty and self.empty():
|
|
||||||
self.currently_empty = True
|
|
||||||
self.empty_start = time.monotonic()
|
|
||||||
|
|
||||||
item = gevent.queue.Queue.get(self) # blocks when nothing left
|
|
||||||
|
|
||||||
if self.currently_empty:
|
|
||||||
if time.monotonic() - self.empty_start >= self.waiting_period:
|
|
||||||
self.count_since_last_wait = 0
|
self.count_since_last_wait = 0
|
||||||
self.surpassed_initial = False
|
|
||||||
|
|
||||||
self.currently_empty = False
|
self.count_since_last_wait += 1
|
||||||
|
|
||||||
self.lock.release()
|
if not self.currently_empty and self.empty():
|
||||||
|
self.currently_empty = True
|
||||||
|
self.empty_start = time.monotonic()
|
||||||
|
|
||||||
return item
|
item = gevent.queue.Queue.get(self) # blocks when nothing left
|
||||||
|
|
||||||
|
if self.currently_empty:
|
||||||
|
if time.monotonic() - self.empty_start >= self.waiting_period:
|
||||||
|
self.count_since_last_wait = 0
|
||||||
|
self.surpassed_initial = False
|
||||||
|
|
||||||
|
self.currently_empty = False
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
def download_thumbnail(save_directory, video_id):
|
def download_thumbnail(save_directory, video_id):
|
||||||
@@ -674,21 +669,53 @@ INNERTUBE_CLIENTS = {
|
|||||||
'hl': 'en',
|
'hl': 'en',
|
||||||
'gl': 'US',
|
'gl': 'US',
|
||||||
'clientName': 'ANDROID',
|
'clientName': 'ANDROID',
|
||||||
'clientVersion': '17.31.35',
|
'clientVersion': '19.15.35',
|
||||||
'osName': 'Android',
|
'osName': 'Android',
|
||||||
'osVersion': '12',
|
'osVersion': '14',
|
||||||
'androidSdkVersion': 31,
|
'androidSdkVersion': 34,
|
||||||
'userAgent': 'com.google.android.youtube/17.31.35 (Linux; U; Android 12) gzip'
|
'platform': 'MOBILE',
|
||||||
},
|
'userAgent': 'com.google.android.youtube/19.15.35 (Linux; U; Android 14; en_US; Google Pixel 6 Pro) gzip'
|
||||||
# https://github.com/yt-dlp/yt-dlp/pull/575#issuecomment-887739287
|
}
|
||||||
#'thirdParty': {
|
|
||||||
# 'embedUrl': 'https://google.com', # Can be any valid URL
|
|
||||||
#}
|
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 3,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 3,
|
||||||
'REQUIRE_JS_PLAYER': False,
|
'REQUIRE_JS_PLAYER': False,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'ios': {
|
||||||
|
'INNERTUBE_API_KEY': 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc',
|
||||||
|
'INNERTUBE_CONTEXT': {
|
||||||
|
'client': {
|
||||||
|
'hl': 'en',
|
||||||
|
'gl': 'US',
|
||||||
|
'clientName': 'IOS',
|
||||||
|
'clientVersion': '19.12.3',
|
||||||
|
'deviceModel': 'iPhone14,3',
|
||||||
|
'userAgent': 'com.google.ios.youtube/19.12.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 5,
|
||||||
|
'REQUIRE_JS_PLAYER': False
|
||||||
|
},
|
||||||
|
|
||||||
|
'android_music': {
|
||||||
|
'INNERTUBE_API_KEY': 'AIzaSyAOghZGza2MQSZkY_zfZ370N-PUdXEo8AI',
|
||||||
|
'INNERTUBE_CONTEXT': {
|
||||||
|
'client': {
|
||||||
|
'hl': 'en',
|
||||||
|
'gl': 'US',
|
||||||
|
'clientName': 'ANDROID_MUSIC',
|
||||||
|
'clientVersion': '6.48.51',
|
||||||
|
'osName': 'Android',
|
||||||
|
'osVersion': '14',
|
||||||
|
'androidSdkVersion': 34,
|
||||||
|
'platform': 'MOBILE',
|
||||||
|
'userAgent': 'com.google.android.apps.youtube.music/6.48.51 (Linux; U; Android 14; US) gzip'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 21,
|
||||||
|
'REQUIRE_JS_PLAYER': False
|
||||||
|
},
|
||||||
|
|
||||||
# This client can access age restricted videos (unless the uploader has disabled the 'allow embedding' option)
|
# This client can access age restricted videos (unless the uploader has disabled the 'allow embedding' option)
|
||||||
# See: https://github.com/zerodytrash/YouTube-Internal-Clients
|
# See: https://github.com/zerodytrash/YouTube-Internal-Clients
|
||||||
'tv_embedded': {
|
'tv_embedded': {
|
||||||
@@ -699,6 +726,7 @@ INNERTUBE_CLIENTS = {
|
|||||||
'gl': 'US',
|
'gl': 'US',
|
||||||
'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
|
'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
|
||||||
'clientVersion': '2.0',
|
'clientVersion': '2.0',
|
||||||
|
'clientScreen': 'EMBED',
|
||||||
},
|
},
|
||||||
# https://github.com/yt-dlp/yt-dlp/pull/575#issuecomment-887739287
|
# https://github.com/yt-dlp/yt-dlp/pull/575#issuecomment-887739287
|
||||||
'thirdParty': {
|
'thirdParty': {
|
||||||
@@ -715,7 +743,7 @@ INNERTUBE_CLIENTS = {
|
|||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'WEB',
|
'clientName': 'WEB',
|
||||||
'clientVersion': '2.20220801.00.00',
|
'clientVersion': '2.20240327.00.00',
|
||||||
'userAgent': desktop_user_agent,
|
'userAgent': desktop_user_agent,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '0.2.8'
|
__version__ = '0.2.16'
|
||||||
|
|||||||
155
youtube/watch.py
155
youtube/watch.py
@@ -2,6 +2,7 @@ import youtube
|
|||||||
from youtube import yt_app
|
from youtube import yt_app
|
||||||
from youtube import util, comments, local_playlist, yt_data_extract
|
from youtube import util, comments, local_playlist, yt_data_extract
|
||||||
from youtube.util import time_utc_isoformat
|
from youtube.util import time_utc_isoformat
|
||||||
|
from youtube.util import INNERTUBE_CLIENTS
|
||||||
import settings
|
import settings
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
@@ -343,7 +344,7 @@ def _add_to_error(info, key, additional_message):
|
|||||||
def fetch_player_response(client, video_id):
|
def fetch_player_response(client, video_id):
|
||||||
return util.call_youtube_api(client, 'player', {
|
return util.call_youtube_api(client, 'player', {
|
||||||
'videoId': video_id,
|
'videoId': video_id,
|
||||||
'params': 'CgIQBg',
|
'params': 'CgIIAdgDAQ==',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -369,93 +370,83 @@ def fetch_watch_page_info(video_id, playlist_id, index):
|
|||||||
return yt_data_extract.extract_watch_info_from_html(watch_page)
|
return yt_data_extract.extract_watch_info_from_html(watch_page)
|
||||||
|
|
||||||
def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
def extract_info(video_id, use_invidious, playlist_id=None, index=None):
|
||||||
tasks = (
|
for client in INNERTUBE_CLIENTS:
|
||||||
# Get video metadata from here
|
tasks = (
|
||||||
gevent.spawn(fetch_watch_page_info, video_id, playlist_id, index),
|
gevent.spawn(fetch_watch_page_info, video_id, playlist_id, index),
|
||||||
|
gevent.spawn(fetch_player_response, client, video_id) # Use client from INNERTUBE_CLIENTS
|
||||||
|
)
|
||||||
|
gevent.joinall(tasks)
|
||||||
|
util.check_gevent_exceptions(*tasks)
|
||||||
|
info, player_response = tasks[0].value, tasks[1].value
|
||||||
|
|
||||||
# Get video URLs by spoofing as android client because its urls don't
|
|
||||||
# require decryption
|
|
||||||
# The URLs returned with WEB for videos requiring decryption
|
|
||||||
# couldn't be decrypted with the base.js from the web page for some
|
|
||||||
# reason
|
|
||||||
# https://github.com/yt-dlp/yt-dlp/issues/574#issuecomment-887171136
|
|
||||||
|
|
||||||
# Update 4/26/23, these URLs will randomly start returning 403
|
|
||||||
# mid-playback and I'm not sure why
|
|
||||||
gevent.spawn(fetch_player_response, 'android', video_id)
|
|
||||||
)
|
|
||||||
gevent.joinall(tasks)
|
|
||||||
util.check_gevent_exceptions(*tasks)
|
|
||||||
info, player_response = tasks[0].value, tasks[1].value
|
|
||||||
|
|
||||||
yt_data_extract.update_with_new_urls(info, player_response)
|
|
||||||
|
|
||||||
# Age restricted video, retry
|
|
||||||
if info['age_restricted'] or info['player_urls_missing']:
|
|
||||||
if info['age_restricted']:
|
|
||||||
print('Age restricted video, retrying')
|
|
||||||
else:
|
|
||||||
print('Player urls missing, retrying')
|
|
||||||
player_response = fetch_player_response('tv_embedded', video_id)
|
|
||||||
yt_data_extract.update_with_new_urls(info, player_response)
|
yt_data_extract.update_with_new_urls(info, player_response)
|
||||||
|
|
||||||
# signature decryption
|
# Age restricted video, retry
|
||||||
decryption_error = decrypt_signatures(info, video_id)
|
if info['age_restricted'] or info['player_urls_missing']:
|
||||||
if decryption_error:
|
if info['age_restricted']:
|
||||||
decryption_error = 'Error decrypting url signatures: ' + decryption_error
|
print('Age restricted video, retrying')
|
||||||
info['playability_error'] = decryption_error
|
else:
|
||||||
|
print('Player urls missing, retrying')
|
||||||
|
player_response = fetch_player_response('tv_embedded', video_id)
|
||||||
|
yt_data_extract.update_with_new_urls(info, player_response)
|
||||||
|
|
||||||
# check if urls ready (non-live format) in former livestream
|
# signature decryption
|
||||||
# urls not ready if all of them have no filesize
|
decryption_error = decrypt_signatures(info, video_id)
|
||||||
if info['was_live']:
|
if decryption_error:
|
||||||
info['urls_ready'] = False
|
decryption_error = 'Error decrypting url signatures: ' + decryption_error
|
||||||
for fmt in info['formats']:
|
info['playability_error'] = decryption_error
|
||||||
if fmt['file_size'] is not None:
|
|
||||||
info['urls_ready'] = True
|
|
||||||
else:
|
|
||||||
info['urls_ready'] = True
|
|
||||||
|
|
||||||
# livestream urls
|
# check if urls ready (non-live format) in former livestream
|
||||||
# sometimes only the livestream urls work soon after the livestream is over
|
# urls not ready if all of them have no filesize
|
||||||
if (info['hls_manifest_url']
|
if info['was_live']:
|
||||||
and (info['live'] or not info['formats'] or not info['urls_ready'])
|
info['urls_ready'] = False
|
||||||
):
|
|
||||||
manifest = util.fetch_url(info['hls_manifest_url'],
|
|
||||||
debug_name='hls_manifest.m3u8',
|
|
||||||
report_text='Fetched hls manifest'
|
|
||||||
).decode('utf-8')
|
|
||||||
|
|
||||||
info['hls_formats'], err = yt_data_extract.extract_hls_formats(manifest)
|
|
||||||
if not err:
|
|
||||||
info['playability_error'] = None
|
|
||||||
for fmt in info['hls_formats']:
|
|
||||||
fmt['video_quality'] = video_quality_string(fmt)
|
|
||||||
else:
|
|
||||||
info['hls_formats'] = []
|
|
||||||
|
|
||||||
# check for 403. Unnecessary for tor video routing b/c ip address is same
|
|
||||||
info['invidious_used'] = False
|
|
||||||
info['invidious_reload_button'] = False
|
|
||||||
info['tor_bypass_used'] = False
|
|
||||||
if (settings.route_tor == 1
|
|
||||||
and info['formats'] and info['formats'][0]['url']):
|
|
||||||
try:
|
|
||||||
response = util.head(info['formats'][0]['url'],
|
|
||||||
report_text='Checked for URL access')
|
|
||||||
except urllib3.exceptions.HTTPError:
|
|
||||||
print('Error while checking for URL access:\n')
|
|
||||||
traceback.print_exc()
|
|
||||||
return info
|
|
||||||
|
|
||||||
if response.status == 403:
|
|
||||||
print('Access denied (403) for video urls.')
|
|
||||||
print('Routing video through Tor')
|
|
||||||
info['tor_bypass_used'] = True
|
|
||||||
for fmt in info['formats']:
|
for fmt in info['formats']:
|
||||||
fmt['url'] += '&use_tor=1'
|
if fmt['file_size'] is not None:
|
||||||
elif 300 <= response.status < 400:
|
info['urls_ready'] = True
|
||||||
print('Error: exceeded max redirects while checking video URL')
|
else:
|
||||||
return info
|
info['urls_ready'] = True
|
||||||
|
|
||||||
|
# livestream urls
|
||||||
|
# sometimes only the livestream urls work soon after the livestream is over
|
||||||
|
if (info['hls_manifest_url']
|
||||||
|
and (info['live'] or not info['formats'] or not info['urls_ready'])
|
||||||
|
):
|
||||||
|
manifest = util.fetch_url(info['hls_manifest_url'],
|
||||||
|
debug_name='hls_manifest.m3u8',
|
||||||
|
report_text='Fetched hls manifest'
|
||||||
|
).decode('utf-8')
|
||||||
|
|
||||||
|
info['hls_formats'], err = yt_data_extract.extract_hls_formats(manifest)
|
||||||
|
if not err:
|
||||||
|
info['playability_error'] = None
|
||||||
|
for fmt in info['hls_formats']:
|
||||||
|
fmt['video_quality'] = video_quality_string(fmt)
|
||||||
|
else:
|
||||||
|
info['hls_formats'] = []
|
||||||
|
|
||||||
|
# check for 403. Unnecessary for tor video routing b/c ip address is same
|
||||||
|
info['invidious_used'] = False
|
||||||
|
info['invidious_reload_button'] = False
|
||||||
|
info['tor_bypass_used'] = False
|
||||||
|
if (settings.route_tor == 1
|
||||||
|
and info['formats'] and info['formats'][0]['url']):
|
||||||
|
try:
|
||||||
|
response = util.head(info['formats'][0]['url'],
|
||||||
|
report_text='Checked for URL access')
|
||||||
|
except urllib3.exceptions.HTTPError:
|
||||||
|
print('Error while checking for URL access:\n')
|
||||||
|
traceback.print_exc()
|
||||||
|
return info
|
||||||
|
|
||||||
|
if response.status == 403:
|
||||||
|
print('Access denied (403) for video urls.')
|
||||||
|
print('Routing video through Tor')
|
||||||
|
info['tor_bypass_used'] = True
|
||||||
|
for fmt in info['formats']:
|
||||||
|
fmt['url'] += '&use_tor=1'
|
||||||
|
elif 300 <= response.status < 400:
|
||||||
|
print('Error: exceeded max redirects while checking video URL')
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
def video_quality_string(format):
|
def video_quality_string(format):
|
||||||
|
|||||||
Reference in New Issue
Block a user