Files
AIOStremio/utils/stream_formatter.py
2025-02-18 14:07:52 +00:00

137 lines
6.0 KiB
Python

from typing import List, Dict, Any
from utils.config import config
from utils.url_processor import URLProcessor
from utils.video_info import VideoInfoParser
import copy
class StreamFormatter:
def __init__(self, url_processor: URLProcessor):
self.url_processor = url_processor
self.video_parser = VideoInfoParser()
async def process_streams(self, streams: List[Dict[str, Any]], user_path: str, proxy_streams: bool, meta_id: str, username: str = None) -> List[Dict[str, Any]]:
"""Process streams with URL processing and formatting."""
if not streams:
return []
regular_streams = [s for s in streams if s.get("name") != "Error"]
streams_to_return = {"streams": copy.deepcopy(regular_streams)}
await self.url_processor.process_stream_urls(
streams_to_return["streams"],
user_path,
proxy_streams,
meta_id=meta_id
)
self._process_stream_formatting(streams_to_return["streams"], username)
return streams_to_return["streams"]
def filter_streams_by_services(self, streams: List[Dict[str, Any]], enabled_services: List[str]) -> List[Dict[str, Any]]:
if not enabled_services:
return streams
return [s for s in streams if s.get("service") in enabled_services]
def vidi_format(self, streams: List[Dict[str, Any]], username: str = None) -> None:
for stream in streams:
stream_name = stream.get('name', stream['service'])
stream_name = ' '.join(stream_name.split())
if 'title' in stream and stream['title']:
stream['title'] = f"{stream_name}\n{stream['title']}"
elif 'description' in stream and stream['description']:
stream['description'] = f"{stream_name}\n{stream['description'].lstrip()}"
else:
stream['description'] = stream_name
def simple_format(self, streams: List[Dict[str, Any]], username: str = None) -> None:
for stream in streams:
info = self.video_parser.parse(stream)
formatted_info = info['formatted_description']
stream['name'] = stream.get('service', 'Unknown')
if 'title' in stream and stream['title']:
stream['title'] = formatted_info
elif 'description' in stream and stream['description']:
stream['description'] = formatted_info
else:
stream['description'] = formatted_info
def one_per_quality(self, streams: List[Dict[str, Any]], username: str = None) -> None:
"""Filter streams to keep only the best quality stream for each resolution.
The best quality is determined by:
0. Cache status (cached streams preferred)
1. HDR presence (DV > HDR10+ > HDR10 > HDR > None)
2. Codec (AV1 > H265 > VP9 > H264 > VP8 > MPEG-2 > MP4)
3. Audio quality (Atmos > TrueHD > DTS-HD > DTS > DD+ > DD > AAC > MP3)
4. File size (larger is assumed better quality)
"""
if not streams:
return
# Group streams by resolution
resolution_groups = {}
for stream in streams:
info = self.video_parser.parse(stream)
resolution = info['raw_info']['resolution']
if resolution not in resolution_groups:
resolution_groups[resolution] = []
resolution_groups[resolution].append((stream, info['raw_info']))
# Sort resolutions by quality (8K > 4K > 1080p > 720p > 480p > 360p > Unknown)
sorted_resolutions = sorted(
resolution_groups.keys(),
key=lambda x: self.video_parser.RESOLUTION_PRIORITY.get(x, -1) if x != 'Unknown' else -999,
reverse=True
)
# For each resolution, find the best quality stream
best_streams = []
for resolution in sorted_resolutions:
stream_infos = resolution_groups[resolution]
sorted_streams = sorted(stream_infos, key=lambda x: (
# Cache status
1000 if (x[0].get('cached', False) or x[1]['is_cached']) else 0,
# HDR
max([self.video_parser.HDR_PRIORITY.get(hdr, 0) for hdr in x[1]['hdr']]) if x[1]['hdr'] else 0,
# Codec
self.video_parser.CODEC_PRIORITY.get(x[1]['codec'].split()[0], 0),
# Audio
max([self.video_parser.AUDIO_PRIORITY.get(audio.split()[0], 0) for audio in x[1]['audio']]) if x[1]['audio'] else 0,
# Size (convert to bytes for comparison)
float(x[1]['size'].split()[0]) if x[1]['size'] and x[1]['size'].split()[0].replace('.', '').isdigit() else 0
), reverse=True)
if sorted_streams:
best_streams.append(sorted_streams[0][0])
streams[:] = best_streams
def _process_stream_formatting(self, streams: List[Dict[str, Any]], username: str = None) -> None:
cached_only = config.get_user_cached_only(username) if username else False
one_per_quality = config.get_user_one_per_quality(username) if username else False
simple_mode = config.get_user_simple_format(username) if username else False
vidi_mode = config.get_user_vidi_mode(username) if username else False
if not streams:
return
watchhub_streams = [s for s in streams if s.get("service") == "WatchHub"]
filtered_streams = [s for s in streams if s.get("service") != "WatchHub"]
if filtered_streams:
if cached_only:
filtered_streams = [s for s in filtered_streams if s.get("is_cached", False)]
if one_per_quality:
self.one_per_quality(filtered_streams, username)
if simple_mode:
self.simple_format(filtered_streams, username)
if vidi_mode:
self.vidi_format(filtered_streams, username)
streams[:] = watchhub_streams + filtered_streams