Ask the provider again for a favorite yt-dlp can't resolve
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPZFnNdHbPGDTqQUNiE4ZN
This commit is contained in:
@@ -2767,6 +2767,39 @@ App.videos = App.videos || {};
|
||||
}
|
||||
};
|
||||
|
||||
// Some providers (rule34video among them) hand the listing their media
|
||||
// formats themselves, and yt-dlp can't extract their pages. A favorite keeps
|
||||
// no formats, so for those /api/resolve comes back empty -- ask the
|
||||
// providers again instead, the way the listing did: search the channel for
|
||||
// the title and take the item that is this video.
|
||||
const formatsFromProvider = async function(video) {
|
||||
const idHead = String(video.id || '').split(':')[0];
|
||||
const channelId = (idHead && idHead !== String(video.id) ? idHead : String(video.channel || '')).toLowerCase();
|
||||
if (!channelId) return null;
|
||||
const servers = (App.storage.getConfig().servers || []).map((entry) => Object.keys(entry)[0]);
|
||||
const wanted = App.favorites && App.favorites.urlKey ? App.favorites.urlKey(video.url) : video.url;
|
||||
const same = (item) => item && (item.id === video.id ||
|
||||
(item.url && (App.favorites && App.favorites.urlKey ? App.favorites.urlKey(item.url) : item.url) === wanted));
|
||||
for (const server of servers) {
|
||||
try {
|
||||
const response = await fetch('/api/videos', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ channel: channelId, query: video.title || '', page: 1, perPage: 24, server })
|
||||
});
|
||||
if (!response.ok) continue;
|
||||
const data = await response.json();
|
||||
const found = (data && Array.isArray(data.items) ? data.items : []).find(same);
|
||||
if (found && hasFormats(found)) {
|
||||
return { url: found.url, formats: found.formats, http_headers: found.http_headers || {}, isLive: !!found.isLive };
|
||||
}
|
||||
} catch (err) {
|
||||
// Next server.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
App.videos.ensureFormats = function(video) {
|
||||
if (!video || typeof video !== 'object') return Promise.resolve(null);
|
||||
if (hasFormats(video.meta)) return Promise.resolve(video.meta);
|
||||
@@ -2793,13 +2826,14 @@ App.videos = App.videos || {};
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: video.url })
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return hasFormats(data) ? data : null;
|
||||
if (hasFormats(data)) return data;
|
||||
}
|
||||
} catch (err) {
|
||||
// Best-effort: playback still works through the proxy.
|
||||
return null;
|
||||
}
|
||||
return await formatsFromProvider(video);
|
||||
})();
|
||||
metaCache.set(cacheKey, promise);
|
||||
}
|
||||
|
||||
69
tests/smoke_favorite_formats.py
Normal file
69
tests/smoke_favorite_formats.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""A favorite whose site yt-dlp can't extract still gets its formats.
|
||||
|
||||
Providers like rule34video hand the listing their formats; a favorite keeps
|
||||
none, and /api/resolve comes back empty for them. ensureFormats must then ask
|
||||
the providers again and take the item that is this video.
|
||||
|
||||
backend/main.py &
|
||||
.venv/bin/python tests/smoke_favorite_formats.py
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
BASE = "http://127.0.0.1:5000/"
|
||||
SERVER = "https://a.fixture.test"
|
||||
FAV = {"key": "rule34video:4511669", "id": "rule34video:4511669",
|
||||
"url": "https://rule34video.com/video/4511669/sindel-cassie-sonya/",
|
||||
"title": "Sindel, Cassie, Sonya", "thumb": "", "channel": "Rule34video",
|
||||
"uploader": "Nyl", "duration": 510, "isLive": False,
|
||||
"favoriteDate": "2026-11-02T22:00:05.356Z"}
|
||||
STATUS = {"id": "a", "name": "a", "online": True, "channels": [
|
||||
{"id": "rule34video", "name": "Rule34video", "description": "x", "options": []}]}
|
||||
FMT = [{"url": "https://rule34video.com/get_file/1/x/4511669_720p.mp4/", "height": 720, "ext": "mp4"}]
|
||||
asked = []
|
||||
|
||||
|
||||
def videos(route):
|
||||
body = json.loads(route.request.post_data or "{}")
|
||||
asked.append(body)
|
||||
items = [{"id": "rule34video:1", "url": "https://rule34video.com/video/1/other/", "formats": []},
|
||||
{"id": FAV["id"], "url": FAV["url"], "formats": FMT}]
|
||||
route.fulfill(status=200, content_type="application/json", body=json.dumps({"items": items}))
|
||||
|
||||
|
||||
def main():
|
||||
failed = 0
|
||||
with sync_playwright() as p:
|
||||
b = p.chromium.launch(args=["--no-sandbox", "--disable-gpu"])
|
||||
page = b.new_page()
|
||||
page.route(f"{SERVER}/api/status", lambda r: r.fulfill(
|
||||
status=200, content_type="application/json", body=json.dumps(STATUS)))
|
||||
page.route("**/api/resolve", lambda r: r.fulfill(
|
||||
status=200, content_type="application/json",
|
||||
body='{"formats":[],"http_headers":{},"isLive":false,"url":null}'))
|
||||
page.route("**/api/videos", videos)
|
||||
page.goto(BASE, wait_until="domcontentloaded")
|
||||
page.evaluate("""([s, f]) => {
|
||||
localStorage.setItem('config', JSON.stringify({servers: [{[s]: {}}]}));
|
||||
localStorage.setItem('favorites', JSON.stringify([f]));
|
||||
}""", [SERVER, FAV])
|
||||
page.goto(BASE, wait_until="load")
|
||||
meta = page.evaluate("""async (f) => {
|
||||
const v = Object.assign({}, f);
|
||||
const m = await App.videos.ensureFormats(v);
|
||||
return { m, attached: !!v.meta };
|
||||
}""", FAV)
|
||||
ok = bool(meta["m"]) and meta["m"]["formats"] == FMT and meta["attached"]
|
||||
print(f" [{'PASS' if ok else 'FAIL'}] favorite gets the provider's formats {meta}")
|
||||
failed += 0 if ok else 1
|
||||
ok = bool(asked) and asked[0]["channel"] == "rule34video" and asked[0]["server"] == SERVER
|
||||
print(f" [{'PASS' if ok else 'FAIL'}] asked the right channel and server {asked[:1]}")
|
||||
failed += 0 if ok else 1
|
||||
b.close()
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user