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:
jtrzupek 2026-06-06 19:44:41 +02:00
parent a5ec6ca991
commit 83918e9a8d
3 changed files with 92 additions and 26 deletions

View file

@ -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,

View file

@ -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(

View file

@ -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,
});
},