mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-16 14:12:20 +02:00
139 lines
5.3 KiB
Python
139 lines
5.3 KiB
Python
# streaming_providers/base/utils/video_quality.py
|
|
from dataclasses import dataclass
|
|
from typing import Optional, Tuple, List
|
|
import xml.etree.ElementTree as ET
|
|
from .logger import logger
|
|
|
|
|
|
@dataclass
|
|
class VideoRepresentation:
|
|
"""Represents a video representation with its quality metrics."""
|
|
element: ET.Element
|
|
adaptation_set: ET.Element
|
|
period: ET.Element
|
|
bandwidth: int
|
|
width: Optional[int] = None
|
|
height: Optional[int] = None
|
|
frame_rate: Optional[float] = None
|
|
representation_id: str = ""
|
|
period_id: str = ""
|
|
as_id: str = ""
|
|
|
|
def get_quality_score(self) -> Tuple[int, int, int]:
|
|
"""
|
|
Returns a tuple for comparison: (resolution_pixels, bandwidth, frame_rate_score)
|
|
Higher values = better quality
|
|
"""
|
|
resolution = (self.width or 0) * (self.height or 0)
|
|
frame_rate_score = int((self.frame_rate or 0) * 100)
|
|
return resolution, self.bandwidth, frame_rate_score
|
|
|
|
|
|
class VideoQualityFilter:
|
|
"""Handles video quality filtering and highest quality selection."""
|
|
|
|
NAMESPACE = {"mpd": "urn:mpeg:dash:schema:mpd:2011"}
|
|
|
|
@staticmethod
|
|
def is_video_adaptation_set(adaptation_set: ET.Element) -> bool:
|
|
"""Determine if an AdaptationSet is video based on mimeType or contentType."""
|
|
mime_type = adaptation_set.get("mimeType", "")
|
|
content_type = adaptation_set.get("contentType", "")
|
|
|
|
# Check AdaptationSet level
|
|
if mime_type.startswith("video/") or content_type == "video":
|
|
return True
|
|
|
|
# Check Representation level if not specified at AdaptationSet level
|
|
for representation in adaptation_set.findall("mpd:Representation", VideoQualityFilter.NAMESPACE):
|
|
rep_mime = representation.get("mimeType", "")
|
|
if rep_mime.startswith("video/"):
|
|
return True
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def parse_video_representation(
|
|
representation: ET.Element,
|
|
adaptation_set: ET.Element,
|
|
period: ET.Element,
|
|
period_id: str,
|
|
as_id: str
|
|
) -> Optional[VideoRepresentation]:
|
|
"""Parse a Representation element and extract video quality information."""
|
|
try:
|
|
# Bandwidth is required
|
|
bandwidth = int(representation.get("bandwidth", "0"))
|
|
if bandwidth == 0:
|
|
return None
|
|
|
|
# Width and height (can be on Representation or AdaptationSet)
|
|
width = representation.get("width") or adaptation_set.get("width")
|
|
height = representation.get("height") or adaptation_set.get("height")
|
|
|
|
width = int(width) if width else None
|
|
height = int(height) if height else None
|
|
|
|
# Frame rate (can be on Representation or AdaptationSet)
|
|
frame_rate_str = representation.get("frameRate") or adaptation_set.get("frameRate")
|
|
frame_rate = None
|
|
if frame_rate_str:
|
|
# Handle both "30" and "30000/1001" formats
|
|
if "/" in frame_rate_str:
|
|
num, denom = frame_rate_str.split("/")
|
|
frame_rate = float(num) / float(denom)
|
|
else:
|
|
frame_rate = float(frame_rate_str)
|
|
|
|
representation_id = representation.get("id", "")
|
|
|
|
return VideoRepresentation(
|
|
element=representation,
|
|
adaptation_set=adaptation_set,
|
|
period=period,
|
|
bandwidth=bandwidth,
|
|
width=width,
|
|
height=height,
|
|
frame_rate=frame_rate,
|
|
representation_id=representation_id,
|
|
period_id=period_id,
|
|
as_id=as_id
|
|
)
|
|
except (ValueError, AttributeError) as e:
|
|
logger.warning(f"Failed to parse video representation: {e}")
|
|
return None
|
|
|
|
@classmethod
|
|
def find_best_video(cls, root: ET.Element) -> Optional[VideoRepresentation]:
|
|
"""Find the highest quality video representation across all periods."""
|
|
all_video_reps: List[VideoRepresentation] = []
|
|
|
|
# Scan all periods and adaptation sets to find video representations
|
|
for period in root.findall(".//mpd:Period", cls.NAMESPACE):
|
|
period_id = period.get("id", "")
|
|
|
|
for adaptation_set in period.findall("mpd:AdaptationSet", cls.NAMESPACE):
|
|
if not cls.is_video_adaptation_set(adaptation_set):
|
|
continue
|
|
|
|
as_id = adaptation_set.get("id", str(id(adaptation_set)))
|
|
|
|
# Collect all representations from this video adaptation set
|
|
for representation in adaptation_set.findall("mpd:Representation", cls.NAMESPACE):
|
|
video_rep = cls.parse_video_representation(
|
|
representation, adaptation_set, period, period_id, as_id
|
|
)
|
|
if video_rep:
|
|
all_video_reps.append(video_rep)
|
|
|
|
if not all_video_reps:
|
|
logger.warning("No video representations found for filtering")
|
|
return None
|
|
|
|
best_video = max(all_video_reps, key=lambda v: v.get_quality_score())
|
|
logger.debug(
|
|
f"Found {len(all_video_reps)} video representations, "
|
|
f"best: {best_video.width}x{best_video.height} @ {best_video.bandwidth}bps"
|
|
)
|
|
|
|
return best_video |