Compare commits
7 Commits
a08303eb3a
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9de3ef8e1f | |||
| 92ad71dbd4 | |||
| bd3e1447e4 | |||
| eec86be88c | |||
| a2b64041ba | |||
| 162bd1c1b9 | |||
| 63cc3e90b9 |
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user