Subtitles: wire hianime's external .vtt into the player
- hianime.py: capture the first .vtt from the deobfuscated embed JSON
(SUBS), expose subtitles(ep_id)
- upstream.py: subtitles() dispatcher (None for anidb, which bakes subs in)
- app.py: /api/episode/{id}/stream also returns a 'sub' stream token
- app.js: add a native <track> after hls.attachMedia (hls.js _cleanTracks
disables tracks present at attach time — that silently killed earlier
attempts) and set mode=showing so Chrome fetches the cues
This commit is contained in:
1 parent
834ca80a8c
commit
5b8170ece0
4 files changed
+37
-1
No files matched your search
+5
-1
@@ -533,7 +533,11 @@ def api_stream(ep_id: int, lang: str = "jpn", request: Request = None):
|
|||||||
master = _upstream(upstream.master_m3u8, ep_id, lang)
|
master = _upstream(upstream.master_m3u8, ep_id, lang)
|
||||||
if not master:
|
if not master:
|
||||||
raise HTTPException(404, f"no {lang} stream for episode {ep_id}")
|
raise HTTPException(404, f"no {lang} stream for episode {ep_id}")
|
||||||
return {"token": _b64(master)}
|
out = {"token": _b64(master)}
|
||||||
|
sub = upstream.subtitles(ep_id) # external .vtt (hianime); anidb bakes subs in
|
||||||
|
if sub:
|
||||||
|
out["sub"] = _b64(sub)
|
||||||
|
return out
|
||||||
|
|
||||||
try:
|
try:
|
||||||
out = go()
|
out = go()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ KEY = b"otaku-embed-v1"
|
|||||||
|
|
||||||
REFERER = None # embed origin, set when a master playlist is resolved
|
REFERER = None # embed origin, set when a master playlist is resolved
|
||||||
STREAM_HOSTS = set() # playlist/segment hosts the /stream proxy may fetch
|
STREAM_HOSTS = set() # playlist/segment hosts the /stream proxy may fetch
|
||||||
|
SUBS = {} # ep_id -> external .vtt url (the player bakes no subs)
|
||||||
|
|
||||||
|
|
||||||
def _search(q):
|
def _search(q):
|
||||||
@@ -107,6 +108,9 @@ def _master_m3u8(ep_id, lang="eng"):
|
|||||||
if line and not line.startswith("#"):
|
if line and not line.startswith("#"):
|
||||||
hosts.add(urlparse(urljoin(master, line)).netloc)
|
hosts.add(urlparse(urljoin(master, line)).netloc)
|
||||||
STREAM_HOSTS |= hosts
|
STREAM_HOSTS |= hosts
|
||||||
|
subs = j.get("subtitles") or []
|
||||||
|
if subs and (subs[0] or {}).get("src"):
|
||||||
|
SUBS[ep_id] = subs[0]["src"] # first track is the english one
|
||||||
return master
|
return master
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -124,3 +128,8 @@ def stream_meta(url):
|
|||||||
if REFERER and urlparse(url).netloc in STREAM_HOSTS:
|
if REFERER and urlparse(url).netloc in STREAM_HOSTS:
|
||||||
return {"Referer": REFERER}
|
return {"Referer": REFERER}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def subtitles(ep_id):
|
||||||
|
"""External .vtt for this episode, or None (anidb bakes subs in)."""
|
||||||
|
return SUBS.get(ep_id)
|
||||||
@@ -205,6 +205,17 @@ def stream_meta(url):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def subtitles(ep_id):
|
||||||
|
"""External .vtt url for an episode, or None (anidb bakes subs in)."""
|
||||||
|
for p in _providers():
|
||||||
|
fn = getattr(_prov(p), "subtitles", None)
|
||||||
|
if fn:
|
||||||
|
v = fn(ep_id)
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# ponytail: assert self-check instead of a test framework.
|
# ponytail: assert self-check instead of a test framework.
|
||||||
# Needs a live source: ANI_SOURCE=<provider> python -m server.upstream
|
# Needs a live source: ANI_SOURCE=<provider> python -m server.upstream
|
||||||
|
|||||||
@@ -927,6 +927,17 @@ async function play(ep_id, ep_no) {
|
|||||||
const s = await api(`/api/episode/${ep_id}/stream?lang=${lang}`);
|
const s = await api(`/api/episode/${ep_id}/stream?lang=${lang}`);
|
||||||
stopHls();
|
stopHls();
|
||||||
const v = videoEl();
|
const v = videoEl();
|
||||||
|
let subTrack = null;
|
||||||
|
if (s.sub) {
|
||||||
|
// external subtitle track (hianime serves a .vtt; anidb bakes subs in).
|
||||||
|
// native <track> works on MSE video (same as dash.js); served via /stream
|
||||||
|
// so the referer-hungry CDN is behind our proxy. Appended AFTER the
|
||||||
|
// branch below: hls.js's attachMedia runs _cleanTracks, which disables
|
||||||
|
// every track present at attach time
|
||||||
|
subTrack = document.createElement("track");
|
||||||
|
subTrack.kind = "subtitles"; subTrack.srclang = "en"; subTrack.label = "English"; subTrack.default = true;
|
||||||
|
subTrack.src = "/stream/" + s.sub;
|
||||||
|
}
|
||||||
v.playbackRate = +(localStorage.getItem("aniweb_spd") || 1);
|
v.playbackRate = +(localStorage.getItem("aniweb_spd") || 1);
|
||||||
document.getElementById("player").replaceChildren(v);
|
document.getElementById("player").replaceChildren(v);
|
||||||
playerOverlay("Loading\u2026", null, null, true);
|
playerOverlay("Loading\u2026", null, null, true);
|
||||||
@@ -1007,6 +1018,7 @@ async function play(ep_id, ep_no) {
|
|||||||
ov: (document.querySelector(".pl-overlay")?.textContent || "").slice(0, 60)});
|
ov: (document.querySelector(".pl-overlay")?.textContent || "").slice(0, 60)});
|
||||||
}, 15000);
|
}, 15000);
|
||||||
}
|
}
|
||||||
|
if (subTrack) { v.append(subTrack); subTrack.mode = "showing"; }
|
||||||
currentEp = {ep_id, ep_no, v};
|
currentEp = {ep_id, ep_no, v};
|
||||||
lastSavedPos = 0;
|
lastSavedPos = 0;
|
||||||
lastSeenPos = 0;
|
lastSeenPos = 0;
|
||||||
|
|||||||
Reference in new issue
Block a user