perf(movies+scenes): direct-play #hash movie hosters; skip empty blacklist filters
Movies: the seekplayer-engine family (easyvidplayer/player4me/seekplayer/ embedseek/upns, ~322k sources) returns a time-bound master.m3u8 on a CDN with a valid IP-SAN cert that plays cross-IP. Mark it mobile_direct in resolve, and make MovieDetailScreen prefer direct_url with a proxy fallback (mirrors the scene path) — previously every movie streamed through the VPS proxy. Paradisehill multipart parts now go direct too. Device-verified: ExoPlayer plays the raw CDN direct, zero proxy traffic, no flicker. Scenes: the three blacklist NOT EXISTS clauses were appended to every filtered list and evaluated per-row even when all blacklist tables are empty (~3.4s tax on a deep mega-tag walk). Skip them when the tables are empty (cached check) — mega-tag list 6.7s -> 3.3s, and every filtered list benefits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a5ec6ca991
commit
83918e9a8d
3 changed files with 92 additions and 26 deletions
|
|
@ -224,6 +224,19 @@ def resolve_movie_playback(
|
|||
if stream:
|
||||
type_hint = "m3u8" if ".m3u8" in stream.lower() else "mp4"
|
||||
raw_meta: dict = {"origin": pb.origin, "host": target}
|
||||
# seekplayer-engine (#hash family: easyvidplayer/player4me/seekplayer/
|
||||
# embedseek/upns — ~322k sources) zwraca master.m3u8 na raw-IP CDN
|
||||
# (185.237.x/203.188.x/45.156.x `/v4/<tok>/<exp>/pp/<hash>/master.m3u8`).
|
||||
# Zweryfikowane cross-IP (curl_cffi chrome + Bright Data, 2026-06-06):
|
||||
# manifest + variant + fMP4 segment WSZYSTKIE 200 z innego IP, a cert jest
|
||||
# VALID (verify=True OK — IP-SAN, nie self-signed jak głosił stary docstring).
|
||||
# Token jest TIME-bound (`<exp>` unix ts), NIE IP-bound. → mobile ExoPlayer
|
||||
# gra direct z CDN, zero VPS bandwidth (był to największy movie proxy-sink).
|
||||
# Proxy (`stream_url`) zostaje jako fallback (stream_proxy IP-host gałąź
|
||||
# robi verify=False). Device-verified na emulatorze przed deployem.
|
||||
from app.extractors.hosters import seekplayer_engine
|
||||
if seekplayer_engine.matches(target):
|
||||
raw_meta["mobile_direct_ok"] = True
|
||||
links.append(
|
||||
StreamLink(
|
||||
stream_url=stream,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,40 @@ def _default_scene_count(session: Session) -> int:
|
|||
return total
|
||||
|
||||
|
||||
# Blacklisty (performer/studio/tag) są zwykle PUSTE (self-hosted, single-user). Mimo to
|
||||
# 3 NOT EXISTS klauzule doklejały się do KAŻDEJ filtrowanej listy scen i były ewaluowane
|
||||
# per-row — przy filtrze typu duży-tag/has_playback planer chodzi po ~176k scen, więc te
|
||||
# puste-zawsze klauzule kosztowały ~3.4s (mega-tag „anal": 6.7s→3.3s po pominięciu).
|
||||
# Cache'ujemy emptiness (TTL 5 min); gdy ktoś doda blacklist-wpis, w ciągu 5 min klauzule
|
||||
# wracają. Patrz reference_scenes_list_perf / task #22.
|
||||
_BLACKLIST_EMPTY_CACHE: dict = {"ts": 0.0, "val": False, "checked": False}
|
||||
_BLACKLIST_EMPTY_TTL = 300.0
|
||||
|
||||
|
||||
def _blacklists_empty(session: Session) -> bool:
|
||||
"""True gdy WSZYSTKIE 3 blacklisty puste → można pominąć NOT EXISTS klauzule."""
|
||||
import time as _time
|
||||
from app.models.blacklist import (
|
||||
BlacklistedPerformer,
|
||||
BlacklistedStudio,
|
||||
BlacklistedTag,
|
||||
)
|
||||
now = _time.monotonic()
|
||||
if _BLACKLIST_EMPTY_CACHE["checked"] and (now - _BLACKLIST_EMPTY_CACHE["ts"]) < _BLACKLIST_EMPTY_TTL:
|
||||
return _BLACKLIST_EMPTY_CACHE["val"]
|
||||
has_any = session.execute(
|
||||
select(
|
||||
exists(select(1).select_from(BlacklistedPerformer))
|
||||
| exists(select(1).select_from(BlacklistedStudio))
|
||||
| exists(select(1).select_from(BlacklistedTag))
|
||||
)
|
||||
).scalar_one()
|
||||
_BLACKLIST_EMPTY_CACHE["ts"] = now
|
||||
_BLACKLIST_EMPTY_CACHE["val"] = not has_any
|
||||
_BLACKLIST_EMPTY_CACHE["checked"] = True
|
||||
return not has_any
|
||||
|
||||
|
||||
def _split_csv(raw: str | None) -> list[str]:
|
||||
if not raw:
|
||||
return []
|
||||
|
|
@ -211,30 +245,33 @@ def list_scenes(
|
|||
|
||||
# Blacklisty — globalne wykluczenia. Jeśli scena ma JAKIEGOKOLWIEK blacklisted
|
||||
# performera, jest na blacklisted studio, lub ma JAKIKOLWIEK blacklisted tag → out.
|
||||
from app.models.blacklist import (
|
||||
BlacklistedPerformer,
|
||||
BlacklistedStudio,
|
||||
BlacklistedTag,
|
||||
)
|
||||
base = base.where(
|
||||
~exists(
|
||||
select(1)
|
||||
.select_from(ScenePerformer)
|
||||
.join(BlacklistedPerformer, BlacklistedPerformer.performer_id == ScenePerformer.performer_id)
|
||||
.where(ScenePerformer.scene_id == Scene.id)
|
||||
# Pomijamy gdy wszystkie 3 blacklisty puste (typowy stan single-user) — te NOT EXISTS
|
||||
# ewaluują się per-row na ~176k scen przy mega-tagu i kosztowały ~3.4s za nic.
|
||||
if not _blacklists_empty(session):
|
||||
from app.models.blacklist import (
|
||||
BlacklistedPerformer,
|
||||
BlacklistedStudio,
|
||||
BlacklistedTag,
|
||||
)
|
||||
)
|
||||
base = base.where(
|
||||
~Scene.studio_id.in_(select(BlacklistedStudio.studio_id))
|
||||
)
|
||||
base = base.where(
|
||||
~exists(
|
||||
select(1)
|
||||
.select_from(SceneTag)
|
||||
.join(BlacklistedTag, BlacklistedTag.tag_id == SceneTag.tag_id)
|
||||
.where(SceneTag.scene_id == Scene.id)
|
||||
base = base.where(
|
||||
~exists(
|
||||
select(1)
|
||||
.select_from(ScenePerformer)
|
||||
.join(BlacklistedPerformer, BlacklistedPerformer.performer_id == ScenePerformer.performer_id)
|
||||
.where(ScenePerformer.scene_id == Scene.id)
|
||||
)
|
||||
)
|
||||
base = base.where(
|
||||
~Scene.studio_id.in_(select(BlacklistedStudio.studio_id))
|
||||
)
|
||||
base = base.where(
|
||||
~exists(
|
||||
select(1)
|
||||
.select_from(SceneTag)
|
||||
.join(BlacklistedTag, BlacklistedTag.tag_id == SceneTag.tag_id)
|
||||
.where(SceneTag.scene_id == Scene.id)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if has_animated_thumbnail:
|
||||
base = base.where(
|
||||
|
|
|
|||
|
|
@ -205,14 +205,21 @@ function WatchChip({
|
|||
...parts.map((p) => ({
|
||||
text: ((p.raw as any).part_label as string) ?? p.quality ?? 'Part',
|
||||
onPress: () => {
|
||||
// Preferuj direct CDN URL (0 VPS bandwidth) → fallback proxy. paradisehill
|
||||
// parts to mp4 z portable CDN (v1.paradisehill.cc, time-bound) → direct gra
|
||||
// natywnie, proxy tylko gdyby direct padł. Mirror scen (SceneDetailScreen).
|
||||
const pDirect = p.direct_url;
|
||||
const pIsDirect = !!pDirect && pDirect !== p.stream_url;
|
||||
navigation.navigate('Player', {
|
||||
url: p.stream_url || p.embed_url || pb.page_url,
|
||||
url: pDirect || p.stream_url || p.embed_url || pb.page_url,
|
||||
sceneId: movieId,
|
||||
playbackId: pb.id,
|
||||
entityKind: 'movie',
|
||||
durationSec: pb.duration_sec ?? null,
|
||||
title: `${title} — ${(p.raw as any).part_label ?? p.quality}`,
|
||||
mode: p.stream_url ? 'video' : 'webview',
|
||||
mode: (p.direct_url || p.stream_url) ? 'video' : 'webview',
|
||||
headers: pIsDirect && p.headers ? p.headers : undefined,
|
||||
fallbackProxyUrl: pIsDirect ? p.stream_url || undefined : undefined,
|
||||
fallbackEmbedUrl: p.embed_url || pb.embed_url || pb.page_url,
|
||||
});
|
||||
},
|
||||
|
|
@ -223,7 +230,14 @@ function WatchChip({
|
|||
return;
|
||||
}
|
||||
|
||||
const target = res.best?.stream_url || res.best?.embed_url || pb.page_url;
|
||||
// Preferuj direct CDN URL (0 VPS bandwidth) → fallback proxy gdy direct fails.
|
||||
// seekplayer-engine (#hash family, ~322k źródeł) zwraca master.m3u8 na raw-IP CDN
|
||||
// z VALID ZeroSSL IP-SAN cert + time-bound token — zweryfikowane na emulatorze
|
||||
// (ExoPlayer gra direct, PLAYING, zero VPS proxy). Wcześniej movie path szedł
|
||||
// ZAWSZE przez proxy (używał stream_url jako primary). Mirror SceneDetailScreen.
|
||||
const bestDirect = res.best?.direct_url;
|
||||
const isDirect = !!bestDirect && bestDirect !== res.best?.stream_url;
|
||||
const target = bestDirect || res.best?.stream_url || res.best?.embed_url || pb.page_url;
|
||||
const fallbackEmbed = res.best?.embed_url || pb.embed_url || pb.page_url;
|
||||
navigation.navigate('Player', {
|
||||
url: target,
|
||||
|
|
@ -236,7 +250,9 @@ function WatchChip({
|
|||
entityKind: 'movie',
|
||||
durationSec: pb.duration_sec ?? null,
|
||||
title,
|
||||
mode: res.best?.stream_url ? 'video' : 'webview',
|
||||
mode: (res.best?.direct_url || res.best?.stream_url) ? 'video' : 'webview',
|
||||
headers: isDirect && res.best?.headers ? res.best.headers : undefined,
|
||||
fallbackProxyUrl: isDirect ? res.best?.stream_url || undefined : undefined,
|
||||
fallbackEmbedUrl: fallbackEmbed,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue