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.
54 lines
2.4 KiB
Python
54 lines
2.4 KiB
Python
"""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)
|