goon/app/extractors/tubes/vjav.py
goon-foss 994e4e98ad feat(tubepornclassic): konektor retro/vintage na silniku TXXX, wspolny tor HLS z vjav
tubepornclassic.com to SPA sieci TXXX (ten sam silnik co vjav): HTML ma pusty
<title> i zero linkow do scen, wszystko idzie z JSON API. Listing
/api/json/videos2/<cache>/str/latest-updates/60/..<page>.all...json oddaje
60 rekordow z pelnymi metadanymi (tytul, slug, dlugosc, post_date, obsada,
kategorie+tagi, miniatura), wiec nie ma fetchu per scena. ~168 tys. wideo,
wgrania codziennie. Koniec katalogu = 200 z code=videos_not_found.

Stream: videofile.php -> dekod video_url -> get_file -> f=video.m3u8 -> HLS
na ahcdn (klucz time-bound, nie IP-bound: adres wybity z VPS gra z lacza
domowego). Ten tor zyl dotad w tubes/vjav.py; wyniesiony do
_txxx.extract_hls, vjav i tubepornclassic wolaja to samo. HLS zamiast mp4,
bo mp4 z tego samego get_file ma w URL CDN speed=... (~1,2 MB/s), a segmenty
HLS leca ~3,6 MB/s. Wyzwanie 429 (ciasteczko w ciele) obsluzone w
ekstraktorze, scraperze i juz wczesniej w /proxy/hls.

Bramka: 38% tytulow to generator SEO ("Fabulous Sex Movie Vintage Try To
Watch For Ever Seen"). Z doczepionym nazwiskiem scena jest odnajdywalna
i zostaje; bez obsady (22%) nie wchodzi, bo nie ma po czym jej znalezc
ani z czym zdeduplikowac. Obsada u 46% scen, studiow brak.

Pilot: 2 strony, 120 scen, 111 nowych, 9 juz w bazie, wszystkie z dlugoscia.
Odtworzenie na emulatorze: playback_events success (ttff 3,3 s), AudioTrack
started. vjav po refaktorze: ten sam wynik ekstraktora co przed.
2026-09-07 10:04:24 +02:00

54 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""vjav.com, TXXX-network JAV tube. HLS stream extractor.
vjav to SPA na silniku TXXX (ktk_player + MSE/blob src). Stream NIE jest w HTML
strony (player renderuje się JS-owo). Zamiast tego AJAX endpoint zwraca zaciemniony
URL pliku:
GET /api/videofile.php?video_id=<id>&lifetime=8640000
-> [{"format":"_hq.mp4","video_url":"<base64+cyrylica>","is_default":1, ...}]
`video_url` to base64 w DWÓCH warstwach zaciemnienia: (1) wielkie łacińskie litery
podmienione na cyrylicę-homoglif (М->M, С->C, А->A, Е->E), (2) custom alfabet base64
gdzie `,`->`/`, `~`->`=`, `-`->`+`. Po odkręceniu obu + b64decode dostajemy get_file:
# stare wideo (shared txxx pool), URL absolutny:
https://videotxxx.com/ext/get_file/9/<hash>/<floor>/<mid>/<mid>_hq.mp4/?d=..&br=..&ti=..
# nowe wideo (własna infra vjav, server N), URL RELATYWNY (prepend https://vjav.com):
/get_file/3/<hash>/<floor>/<mid>/<mid>_hq.mp4/?d=..&br=..&ti=..
Dopisanie `&f=video.m3u8` -> 302 chain -> finalny HLS na *.ahcdn.com
(`key=..,end=..,limit=3`, time-bound, NIE IP-bound, `referer=none` na whiteliscie
-> portable, gra z residential IP telefonu bez sesji vjav).
Zwracamy get_file (`?f=video.m3u8`) jako type='m3u8' + `mobile_direct_ok` -> playback.py
owija w `/proxy/hls/<token>/play.m3u8` (passthrough manifestu ~1KB przez VPS, segmenty
direct z telefonu; patrz stream_proxy.proxy_hls_manifest). Media id w get_file (318577)
!= page id (5197), dlatego MUSIMY przejść przez videofile.php. RE 2026-07-10.
Wyzwanie 429 (ciasteczko w ciele odpowiedzi, od 2026-08-21) i sam tor są w
`_txxx.extract_hls`, wspólnym z tubepornclassic.
"""
from __future__ import annotations
import logging
import re
from app.extractors._models import StreamSource
from app.extractors.tubes import _txxx
log = logging.getLogger(__name__)
_BASE = "https://vjav.com"
_VIDEO_ID_RE = re.compile(r"/videos/(\d+)/")
# Dekoder i cały tor videofile.php → get_file → HLS siedzą w `_txxx.py`, bo od 2026-09-07
# ten sam silnik obsługuje też tubepornclassic. Alias zachowany dla ewentualnych importów.
_decode_video_url = _txxx.decode_video_url
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
m = _VIDEO_ID_RE.search(page_url)
if not m:
log.info("vjav: brak video id w %s", page_url)
return None
return _txxx.extract_hls(_BASE, page_url, m.group(1), tag="vjav", timeout=timeout)