Compare commits

...

7 Commits

Author SHA1 Message Date
lamp 9de3ef8e1f restart always 2023-12-05 23:14:48 -06:00
lamp 92ad71dbd4 python3.11 2023-11-27 19:11:00 -06:00
lamp bd3e1447e4 update regex 2023-09-19 02:59:21 -07:00
lamp eec86be88c disable resolver 2023-09-19 02:28:47 -07:00
lamp a2b64041ba fix cache prune 2023-08-19 14:37:46 -07:00
lamp 162bd1c1b9 fix using expired 2023-08-19 14:03:24 -07:00
lamp 63cc3e90b9 fix formatting 2023-08-18 20:03:42 -05:00
3 changed files with 15 additions and 52 deletions
-45
View File
@@ -1,45 +0,0 @@
# u2b.cx
A YouTube search resolver + raw file resolver w/ proxy for Quest VRChat.
- Get the video you want just by typing its name in the URL
- Video works for both PC and Quest VRChat
- Proxying avoids unpredictable blocks from google's servers
## Technical Features
- Written in Python to integrate with YoutubeDL (yt-dlp) for fastest performance
- Multi-threaded for concurrent usage
- Requests coalesced to one YoutubeDL invocation per input
- Limited to one YoutubeDL invocation per IP address
- Results cached for 5 hours or until expiry found in extracted URL
- Extracted URLs proxied in Caddy so that they work in all countries
- Errors displayed as a 10 second single-frame video
## Potential improvements
- Support other sites besides YouTube
- Rewrite on Twisted framework
## Usage
### GET `https://u2b.cx/<query>(/<number>)`
Get a YouTube video via search query. Optional <number> parameter selects the Nth result, default is first result. Max 100. Query may not start with `.`.
Example: https://u2b.cx/nyan%20cat%20cover/2 will play the second result from the search results of "nyan cat cover".
Responds with 302 redirect to raw mp4 file, or 200 with an error video if something went wrong.
### GET `https://u2b.cx/id/<video id>`
### GET `https://u2b.cx/https://www.youtube.com/watch?v=<video id>`
### GET `https://u2b.cx/https://youtu.be/<video id>`
### GET `https://u2b.cx/https://www.youtube.com/shorts/<video id>`
### GET `https://u2b.cx/https://music.youtube.com/watch?v=<video id>`
### etcetera... (see regex in code)
Bypasses search to resolve the video directly by its id.
Regex only matches the start of the string; anything after the 11-char video id is ignored.
Malformed YouTube URLs will be treated as a YouTube search query and YouTube search will probably give what you want.
+11 -5
View File
@@ -74,7 +74,7 @@ class Handler(BaseHTTPRequestHandler):
return
path = unquote(self.path)
id_match = re.match("\/(?:id\/|(?:https?:\/\/)?(?:(?:www\.|music\.|m\.)?youtube\.com\/(?:watch\?v=|shorts\/)|youtu\.be\/))([A-Za-z0-9_-]{11})", path)
id_match = re.match("\/(?:id\/|(?:https?:\/\/)?(?:(?:www\.|music\.|m\.)?youtube\.com\/(?:watch\?v=|shorts\/|live\/)|youtu\.be\/))([A-Za-z0-9_-]{11})", path)
if id_match:
video_id = id_match[1]
@@ -88,6 +88,12 @@ class Handler(BaseHTTPRequestHandler):
search_index = max(min(search_index, 100), 1)
video_id = ytdl_search_to_id(self, search_query, search_index)
self.send_response(302)
self.send_header("Location", f"https://www.youtube.com/watch?v={video_id}")
self.end_headers()
#half this code now defunct
return
video_url = ytdl_resolve_mp4_url(self, video_id)
if 'PROXY' in environ:
@@ -128,7 +134,7 @@ def ytdl_search_to_id(self: Handler, query: str, index: int) -> str:
else:
results = None
if results == None or results['count'] < index:
if results == None or results['count'] < index or datetime.now() >= ctx['expires_at']:
search_cache[query] = ctx = {
'event': Event(),
'expires_at': datetime.now() + timedelta(hours=5)
@@ -153,7 +159,7 @@ def ytdl_search_to_id(self: Handler, query: str, index: int) -> str:
resolve_cache = {}
def ytdl_resolve_mp4_url(self: Handler, input: str) -> str:
ctx = resolve_cache.get(input)
if ctx:
if ctx and datetime.now() <= ctx['expires_at']:
ctx['event'].wait(60)
if 'error' in ctx:
raise CachedException(ctx['error'])
@@ -199,10 +205,10 @@ def ytdl_resolve_mp4_url(self: Handler, input: str) -> str:
def cache_prune_loop():
while True:
sleep(3600)
for key in search_cache:
for key in list(search_cache.keys()):
if datetime.now() >= search_cache[key]['expires_at']:
del search_cache[key]
for key in resolve_cache:
for key in list(resolve_cache.keys()):
if datetime.now() >= resolve_cache[key]['expires_at']:
del resolve_cache[key]
Thread(target=cache_prune_loop, daemon=True).start()
+4 -2
View File
@@ -7,9 +7,11 @@ After=network.target
User=u2b
Group=u2b
WorkingDirectory=/srv/u2b.cx/
Environment=ADDRESS=127.29.151.200 PORT=52482 PROXY=https://proxy.u2b.cx/
ExecStart=/usr/bin/python3.9 server.py
Environment=ADDRESS=127.0.0.1 PORT=52482
ExecStart=/usr/bin/python3.11 server.py
MemoryMax=1G
LimitNOFILE=262144
Restart=always
[Install]
WantedBy=multi-user.target