mirror of
https://github.com/UrloMythus/UnHided.git
synced 2026-04-11 11:50:51 +00:00
update
This commit is contained in:
@@ -1,9 +1,68 @@
|
||||
import json
|
||||
from typing import Literal, Dict, Any, Optional
|
||||
import re
|
||||
from typing import Annotated, Literal, Dict, Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, IPvAnyAddress, ConfigDict, field_validator
|
||||
|
||||
|
||||
def validate_resolution_format(value: str) -> str:
|
||||
"""Validate and normalize resolution format (e.g., '1080p', '720p')."""
|
||||
if not re.match(r"^\d+p$", value):
|
||||
raise ValueError(f"Invalid resolution format '{value}'. Expected format: '1080p', '720p', etc.")
|
||||
return value
|
||||
|
||||
|
||||
def parse_skip_segments(skip_str: str) -> list[dict]:
|
||||
"""
|
||||
Parse compact skip segment format into list of segment dicts.
|
||||
|
||||
Format: "start-end,start-end,..." (e.g., "0-112,280-300")
|
||||
|
||||
Args:
|
||||
skip_str: Comma-separated list of start-end ranges in seconds.
|
||||
|
||||
Returns:
|
||||
List of dicts with 'start' and 'end' keys.
|
||||
|
||||
Raises:
|
||||
ValueError: If format is invalid or end <= start.
|
||||
"""
|
||||
if not skip_str or not skip_str.strip():
|
||||
return []
|
||||
|
||||
segments = []
|
||||
for part in skip_str.split(","):
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
|
||||
if "-" not in part:
|
||||
raise ValueError(f"Invalid segment format '{part}'. Expected 'start-end' (e.g., '0-112')")
|
||||
|
||||
# Handle negative numbers by splitting only on the last hyphen for end
|
||||
# But since times are always positive, we can split on first hyphen
|
||||
parts = part.split("-", 1)
|
||||
if len(parts) != 2:
|
||||
raise ValueError(f"Invalid segment format '{part}'. Expected 'start-end' (e.g., '0-112')")
|
||||
|
||||
try:
|
||||
start = float(parts[0])
|
||||
end = float(parts[1])
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid segment format '{part}'. Start and end must be numbers.")
|
||||
|
||||
if start < 0:
|
||||
raise ValueError(f"Start time cannot be negative: {start}")
|
||||
if end < 0:
|
||||
raise ValueError(f"End time cannot be negative: {end}")
|
||||
if end <= start:
|
||||
raise ValueError(f"End time ({end}) must be greater than start time ({start})")
|
||||
|
||||
segments.append({"start": start, "end": end})
|
||||
|
||||
return segments
|
||||
|
||||
|
||||
class GenerateUrlRequest(BaseModel):
|
||||
mediaflow_proxy_url: str = Field(..., description="The base URL for the mediaflow proxy.")
|
||||
endpoint: Optional[str] = Field(None, description="The specific endpoint to be appended to the base URL.")
|
||||
@@ -15,7 +74,14 @@ class GenerateUrlRequest(BaseModel):
|
||||
)
|
||||
request_headers: Optional[dict] = Field(default_factory=dict, description="Headers to be included in the request.")
|
||||
response_headers: Optional[dict] = Field(
|
||||
default_factory=dict, description="Headers to be included in the response."
|
||||
default_factory=dict, description="Headers to be included in the response (r_ prefix, manifest only)."
|
||||
)
|
||||
propagate_response_headers: Optional[dict] = Field(
|
||||
default_factory=dict,
|
||||
description="Response headers that propagate to segments (rp_ prefix). Useful for overriding content-type on segment requests.",
|
||||
)
|
||||
remove_response_headers: Optional[list[str]] = Field(
|
||||
default_factory=list, description="List of response header names to remove from the proxied response."
|
||||
)
|
||||
expiration: Optional[int] = Field(
|
||||
None, description="Expiration time for the URL in seconds. If not provided, the URL will not expire."
|
||||
@@ -40,7 +106,14 @@ class MultiUrlRequestItem(BaseModel):
|
||||
)
|
||||
request_headers: Optional[dict] = Field(default_factory=dict, description="Headers to be included in the request.")
|
||||
response_headers: Optional[dict] = Field(
|
||||
default_factory=dict, description="Headers to be included in the response."
|
||||
default_factory=dict, description="Headers to be included in the response (r_ prefix, manifest only)."
|
||||
)
|
||||
propagate_response_headers: Optional[dict] = Field(
|
||||
default_factory=dict,
|
||||
description="Response headers that propagate to segments (rp_ prefix). Useful for overriding content-type on segment requests.",
|
||||
)
|
||||
remove_response_headers: Optional[list[str]] = Field(
|
||||
default_factory=list, description="List of response header names to remove from the proxied response."
|
||||
)
|
||||
filename: Optional[str] = Field(None, description="Filename to be preserved for media players like Infuse.")
|
||||
|
||||
@@ -62,7 +135,7 @@ class GenericParams(BaseModel):
|
||||
|
||||
|
||||
class HLSManifestParams(GenericParams):
|
||||
destination: str = Field(..., description="The URL of the HLS manifest.", alias="d")
|
||||
destination: Annotated[str, Field(description="The URL of the HLS manifest.", alias="d")]
|
||||
key_url: Optional[str] = Field(
|
||||
None,
|
||||
description="The HLS Key URL to replace the original key URL. Defaults to None. (Useful for bypassing some sneaky protection)",
|
||||
@@ -83,19 +156,95 @@ class HLSManifestParams(GenericParams):
|
||||
False,
|
||||
description="If true, redirects to the highest resolution stream in the manifest.",
|
||||
)
|
||||
resolution: Optional[str] = Field(
|
||||
None,
|
||||
description="Select a specific resolution stream (e.g., '1080p', '720p', '480p'). Falls back to closest lower resolution if exact match not found.",
|
||||
)
|
||||
skip: Optional[str] = Field(
|
||||
None,
|
||||
description="Time segments to skip, in compact format: 'start-end,start-end,...' (e.g., '0-112,280-300'). Segments are in seconds.",
|
||||
)
|
||||
start_offset: Optional[float] = Field(
|
||||
None,
|
||||
description="Injects #EXT-X-START:TIME-OFFSET into the playlist. Use negative values for live streams to start behind the live edge (e.g., -18 to start 18 seconds behind). Enables prebuffer to work on live streams by creating headroom.",
|
||||
)
|
||||
transformer: Optional[str] = Field(
|
||||
None,
|
||||
description="Stream transformer ID for host-specific content manipulation (e.g., 'ts_stream' for PNG/padding stripping).",
|
||||
)
|
||||
|
||||
@field_validator("resolution", mode="before")
|
||||
@classmethod
|
||||
def validate_resolution(cls, value: Any) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
return validate_resolution_format(str(value))
|
||||
|
||||
def get_skip_segments(self) -> Optional[list[dict]]:
|
||||
"""Parse and return skip segments as a list of dicts with 'start' and 'end' keys."""
|
||||
if self.skip is None:
|
||||
return None
|
||||
return parse_skip_segments(self.skip)
|
||||
|
||||
|
||||
class MPDManifestParams(GenericParams):
|
||||
destination: str = Field(..., description="The URL of the MPD manifest.", alias="d")
|
||||
destination: Annotated[str, Field(description="The URL of the MPD manifest.", alias="d")]
|
||||
key_id: Optional[str] = Field(None, description="The DRM key ID (optional).")
|
||||
key: Optional[str] = Field(None, description="The DRM key (optional).")
|
||||
resolution: Optional[str] = Field(
|
||||
None,
|
||||
description="Select a specific resolution stream (e.g., '1080p', '720p', '480p'). Falls back to closest lower resolution if exact match not found.",
|
||||
)
|
||||
skip: Optional[str] = Field(
|
||||
None,
|
||||
description="Time segments to skip, in compact format: 'start-end,start-end,...' (e.g., '0-112,280-300'). Segments are in seconds.",
|
||||
)
|
||||
start_offset: Optional[float] = Field(
|
||||
None,
|
||||
description="Injects #EXT-X-START:TIME-OFFSET into live playlists. Use negative values for live streams to start behind the live edge (e.g., -18 to start 18 seconds behind). Enables prebuffer to work on live streams.",
|
||||
)
|
||||
remux_to_ts: Optional[bool] = Field(
|
||||
None,
|
||||
description="Override global REMUX_TO_TS setting per-request. true = force TS remuxing, false = force fMP4 passthrough, omit = use server default.",
|
||||
)
|
||||
|
||||
@field_validator("resolution", mode="before")
|
||||
@classmethod
|
||||
def validate_resolution(cls, value: Any) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
return validate_resolution_format(str(value))
|
||||
|
||||
def get_skip_segments(self) -> Optional[list[dict]]:
|
||||
"""Parse and return skip segments as a list of dicts with 'start' and 'end' keys."""
|
||||
if self.skip is None:
|
||||
return None
|
||||
return parse_skip_segments(self.skip)
|
||||
|
||||
|
||||
class MPDPlaylistParams(GenericParams):
|
||||
destination: str = Field(..., description="The URL of the MPD manifest.", alias="d")
|
||||
destination: Annotated[str, Field(description="The URL of the MPD manifest.", alias="d")]
|
||||
profile_id: str = Field(..., description="The profile ID to generate the playlist for.")
|
||||
key_id: Optional[str] = Field(None, description="The DRM key ID (optional).")
|
||||
key: Optional[str] = Field(None, description="The DRM key (optional).")
|
||||
skip: Optional[str] = Field(
|
||||
None,
|
||||
description="Time segments to skip, in compact format: 'start-end,start-end,...' (e.g., '0-112,280-300'). Segments are in seconds.",
|
||||
)
|
||||
start_offset: Optional[float] = Field(
|
||||
None,
|
||||
description="Injects #EXT-X-START:TIME-OFFSET into the playlist. Use negative values for live streams to start behind the live edge (e.g., -18 to start 18 seconds behind). Enables prebuffer to work on live streams.",
|
||||
)
|
||||
remux_to_ts: Optional[bool] = Field(
|
||||
None,
|
||||
description="Override global REMUX_TO_TS setting per-request. true = force TS remuxing, false = force fMP4 passthrough, omit = use server default.",
|
||||
)
|
||||
|
||||
def get_skip_segments(self) -> Optional[list[dict]]:
|
||||
"""Parse and return skip segments as a list of dicts with 'start' and 'end' keys."""
|
||||
if self.skip is None:
|
||||
return None
|
||||
return parse_skip_segments(self.skip)
|
||||
|
||||
|
||||
class MPDSegmentParams(GenericParams):
|
||||
@@ -104,14 +253,57 @@ class MPDSegmentParams(GenericParams):
|
||||
mime_type: str = Field(..., description="The MIME type of the segment.")
|
||||
key_id: Optional[str] = Field(None, description="The DRM key ID (optional).")
|
||||
key: Optional[str] = Field(None, description="The DRM key (optional).")
|
||||
is_live: Optional[bool] = Field(None, alias="is_live", description="Whether the parent MPD is live.")
|
||||
is_live: Annotated[
|
||||
Optional[bool], Field(default=None, alias="is_live", description="Whether the parent MPD is live.")
|
||||
]
|
||||
init_range: Optional[str] = Field(
|
||||
None, description="Byte range for the initialization segment (e.g., '0-11568'). Used for SegmentBase MPDs."
|
||||
)
|
||||
use_map: Optional[bool] = Field(
|
||||
False,
|
||||
description="Whether EXT-X-MAP is used (init sent separately). If true, don't concatenate init with segment.",
|
||||
)
|
||||
|
||||
|
||||
class MPDInitParams(GenericParams):
|
||||
init_url: str = Field(..., description="The URL of the initialization segment.")
|
||||
mime_type: str = Field(..., description="The MIME type of the segment.")
|
||||
key_id: Optional[str] = Field(None, description="The DRM key ID (optional).")
|
||||
key: Optional[str] = Field(None, description="The DRM key (optional).")
|
||||
is_live: Annotated[
|
||||
Optional[bool], Field(default=None, alias="is_live", description="Whether the parent MPD is live.")
|
||||
]
|
||||
init_range: Optional[str] = Field(
|
||||
None, description="Byte range for the initialization segment (e.g., '0-11568'). Used for SegmentBase MPDs."
|
||||
)
|
||||
|
||||
|
||||
class ExtractorURLParams(GenericParams):
|
||||
host: Literal[
|
||||
"Doodstream", "FileLions", "FileMoon", "F16Px", "Mixdrop", "Uqload", "Streamtape", "StreamWish", "Supervideo", "VixCloud", "Okru", "Maxstream", "LiveTV", "LuluStream", "DLHD", "Fastream", "TurboVidPlay", "Vidmoly", "Vidoza", "Voe", "Sportsonline"
|
||||
"Doodstream",
|
||||
"FileLions",
|
||||
"FileMoon",
|
||||
"F16Px",
|
||||
"Mixdrop",
|
||||
"Gupload",
|
||||
"Uqload",
|
||||
"Streamtape",
|
||||
"StreamWish",
|
||||
"Supervideo",
|
||||
"VixCloud",
|
||||
"Okru",
|
||||
"Maxstream",
|
||||
"LiveTV",
|
||||
"LuluStream",
|
||||
"DLHD",
|
||||
"Fastream",
|
||||
"TurboVidPlay",
|
||||
"Vidmoly",
|
||||
"Vidoza",
|
||||
"Voe",
|
||||
"Sportsonline",
|
||||
] = Field(..., description="The host to extract the URL from.")
|
||||
destination: str = Field(..., description="The URL of the stream.", alias="d")
|
||||
destination: Annotated[str, Field(description="The URL of the stream.", alias="d")]
|
||||
redirect_stream: bool = Field(False, description="Whether to redirect to the stream endpoint automatically.")
|
||||
extra_params: Dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
|
||||
Reference in New Issue
Block a user