sdv2500/progettojava
0
1import re2import urllib.parse3from urllib.parse import urljoin4import xml.etree.ElementTree as ET5import logging6 7logger = logging.getLogger(__name__)8 9# Conditional import for DLHD detection10try:11 from extractors.dlhd import DLHDExtractor12except ImportError:13 DLHDExtractor = None14 15class ManifestRewriter:16 @staticmethod17 def rewrite_mpd_manifest(manifest_content: str, base_url: str, proxy_base: str, stream_headers: dict, clearkey_param: str = None, api_password: str = None) -> str:18 """Riscrive i manifest MPD (DASH) per passare attraverso il zenith."""19 try:20 # Aggiungiamo il namespace di default se non presente, per ET21 if 'xmlns' not in manifest_content:22 manifest_content = manifest_content.replace('<MPD', '<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"', 1)23 24 root = ET.fromstring(manifest_content)25 ns = {'mpd': 'urn:mpeg:dash:schema:mpd:2011', 'cenc': 'urn:mpeg:cenc:2013', 'dashif': 'http://dashif.org/guidelines/clearKey'}26 27 # Registra i namespace per evitare prefissi ns028 ET.register_namespace('', ns['mpd'])29 ET.register_namespace('cenc', ns['cenc'])30 ET.register_namespace('dashif', ns['dashif'])31 32 # Includiamo tutti gli header rilevanti passati dall'estrattore33 header_params = "".join([f"&h_{urllib.parse.quote(key)}={urllib.parse.quote(value)}" for key, value in stream_headers.items()])34 35 if api_password:36 header_params += f"&api_password={api_password}"37 38 def create_proxy_url(relative_url):39 absolute_url = urljoin(base_url, relative_url)40 encoded_url = urllib.parse.quote(absolute_url, safe='')41 return f"{proxy_base}/zenith/mpd/manifest.m3u8?d={encoded_url}{header_params}"42 43 # --- GESTIONE CLEARKEY STATICA ---44 if clearkey_param:45 try:46 kid_hex, key_hex = clearkey_param.split(':')47 48 # Crea l'elemento ContentProtection per ClearKey49 cp_element = ET.Element('ContentProtection')50 cp_element.set('schemeIdUri', 'urn:uuid:e2719d58-a985-b3c9-781a-007147f192ec')51 cp_element.set('value', 'ClearKey')52 53 # Puntiamo al nostro endpoint /license54 license_url = f"{proxy_base}/license?clearkey={clearkey_param}"55 if api_password:56 license_url += f"&api_password={api_password}"57 58 # 1. Laurl standard (namespace MPD)59 laurl_element = ET.SubElement(cp_element, '{urn:mpeg:dash:schema:mpd:2011}Laurl')60 laurl_element.text = license_url61 62 # 2. dashif:Laurl (namespace DashIF)63 laurl_dashif = ET.SubElement(cp_element, '{http://dashif.org/guidelines/clearKey}Laurl')64 laurl_dashif.text = license_url65 66 # 3. Aggiungi cenc:default_KID67 if len(kid_hex) == 32:68 kid_guid = f"{kid_hex[:8]}-{kid_hex[8:12]}-{kid_hex[12:16]}-{kid_hex[16:20]}-{kid_hex[20:]}"69 cp_element.set('{urn:mpeg:cenc:2013}default_KID', kid_guid)70 71 # Inietta ContentProtection72 adaptation_sets = root.findall('.//mpd:AdaptationSet', ns)73 logger.info(f"π Trovati {len(adaptation_sets)} AdaptationSet nel manifest.")74 75 for adaptation_set in adaptation_sets:76 # RIMUOVI altri ContentProtection (es. Widevine)77 for cp in adaptation_set.findall('mpd:ContentProtection', ns):78 scheme = cp.get('schemeIdUri', '').lower()79 if 'e2719d58-a985-b3c9-781a-007147f192ec' not in scheme:80 adaptation_set.remove(cp)81 logger.info(f"ποΈ Rimosso ContentProtection conflittuale: {scheme}")82 83 # Verifica se esiste giΓ ClearKey84 existing_cp = False85 for cp in adaptation_set.findall('mpd:ContentProtection', ns):86 if cp.get('schemeIdUri') == 'urn:uuid:e2719d58-a985-b3c9-781a-007147f192ec':87 existing_cp = True88 break89 90 if not existing_cp:91 adaptation_set.insert(0, cp_element)92 logger.info(f"π Iniettato ContentProtection ClearKey statico in AdaptationSet")93 94 except Exception as e:95 logger.error(f"β Errore nel parsing del parametro clearkey: {e}")96 97 # --- GESTIONE PROXY LICENZE ESISTENTI ---98 for cp in root.findall('.//mpd:ContentProtection', ns):99 for child in cp:100 if 'Laurl' in child.tag and child.text:101 original_license_url = child.text102 encoded_license_url = urllib.parse.quote(original_license_url, safe='')103 proxy_license_url = f"{proxy_base}/license?url={encoded_license_url}{header_params}"104 child.text = proxy_license_url105 logger.info(f"π Redirected License URL: {original_license_url} -> {proxy_license_url}")106 107 # Riscrive gli attributi URL108 for template_tag in root.findall('.//mpd:SegmentTemplate', ns):109 for attr in ['media', 'initialization']:110 if template_tag.get(attr):111 template_tag.set(attr, create_proxy_url(template_tag.get(attr)))112 113 for seg_url_tag in root.findall('.//mpd:SegmentURL', ns):114 if seg_url_tag.get('media'):115 seg_url_tag.set('media', create_proxy_url(seg_url_tag.get('media')))116 117 for base_url_tag in root.findall('.//mpd:BaseURL', ns):118 if base_url_tag.text:119 base_url_tag.text = create_proxy_url(base_url_tag.text)120 121 return ET.tostring(root, encoding='unicode', method='xml')122 123 except Exception as e:124 logger.error(f"β Errore durante la riscrittura del manifest MPD: {e}")125 return manifest_content126 127 @staticmethod128 async def rewrite_manifest_urls(manifest_content: str, base_url: str, proxy_base: str, stream_headers: dict, original_channel_url: str = '', api_password: str = None, get_extractor_func=None) -> str:129 """β
AGGIORNATA: Riscrive gli URL nei manifest HLS per passare attraverso il zenith (incluse chiavi AES)"""130 lines = manifest_content.split('\n')131 rewritten_lines = []132 133 # Determina se Γ¨ VixSrc o DLHD134 is_vixsrc_stream = False135 is_dlhd_stream = False136 logger.info(f"Manifest rewriter called with base_url: {base_url}")137 138 try:139 if get_extractor_func:140 original_request_url = stream_headers.get('referer') or stream_headers.get('Referer') or base_url141 extractor = await get_extractor_func(original_request_url, {})142 143 if hasattr(extractor, 'is_vixsrc') and extractor.is_vixsrc:144 is_vixsrc_stream = True145 logger.info("Rilevato stream VixSrc.")146 elif DLHDExtractor and isinstance(extractor, DLHDExtractor):147 is_dlhd_stream = True148 logger.info(f"β
Rilevato stream DLHD. SarΓ proxato completamente.")149 except Exception as e:150 logger.error(f"Error in extractor detection: {e}")151 pass152 153 # Logica speciale SOLO per VixSrc (filtro qualitΓ )154 if is_vixsrc_stream:155 streams = []156 for i, line in enumerate(lines):157 if line.startswith('#EXT-X-STREAM-INF:'):158 bandwidth_match = re.search(r'BANDWIDTH=(\d+)', line)159 if bandwidth_match:160 bandwidth = int(bandwidth_match.group(1))161 streams.append({'bandwidth': bandwidth, 'inf': line, 'url': lines[i+1]})162 163 if streams:164 highest_quality_stream = max(streams, key=lambda x: x['bandwidth'])165 logger.info(f"VixSrc: Selezionata bandwidth {highest_quality_stream['bandwidth']}.")166 167 rewritten_lines.append('#EXTM3U')168 for line in lines:169 if line.startswith('#EXT-X-MEDIA:') or line.startswith('#EXT-X-STREAM-INF:') or (line and not line.startswith('#')):170 continue 171 172 rewritten_lines.extend([line for line in lines if line.startswith('#EXT-X-MEDIA:')])173 rewritten_lines.append(highest_quality_stream['inf'])174 rewritten_lines.append(highest_quality_stream['url'])175 return '\n'.join(rewritten_lines)176 177 # --- Logica Standard (incluso DLHD) ---178 header_params = "".join([f"&h_{urllib.parse.quote(key)}={urllib.parse.quote(value)}" for key, value in stream_headers.items()])179 180 if api_password:181 header_params += f"&api_password={api_password}"182 183 # Estrai query params dal base_url per ereditarli se necessario184 base_parsed = urllib.parse.urlparse(base_url)185 base_query = base_parsed.query186 187 for line in lines:188 line = line.strip()189 190 # 1. GESTIONE CHIAVI AES-128191 if line.startswith('#EXT-X-KEY:') and 'URI=' in line:192 uri_start = line.find('URI="') + 5193 uri_end = line.find('"', uri_start)194 195 if uri_start > 4 and uri_end > uri_start:196 original_key_url = line[uri_start:uri_end]197 absolute_key_url = urljoin(base_url, original_key_url)198 199 encoded_key_url = urllib.parse.quote(absolute_key_url, safe='')200 encoded_original_channel_url = urllib.parse.quote(original_channel_url, safe='')201 202 # Zenith KEY URL203 proxy_key_url = f"{proxy_base}/key?key_url={encoded_key_url}&original_channel_url={encoded_original_channel_url}"204 205 # Aggiungi header206 key_header_params = "".join(207 [f"&h_{urllib.parse.quote(key)}={urllib.parse.quote(value)}" 208 for key, value in stream_headers.items()]209 )210 proxy_key_url += key_header_params211 212 if api_password:213 proxy_key_url += f"&api_password={api_password}"214 215 new_line = line[:uri_start] + proxy_key_url + line[uri_end:]216 rewritten_lines.append(new_line)217 logger.info(f"π Redirected AES key: {absolute_key_url} -> {proxy_key_url}")218 else:219 rewritten_lines.append(line)220 221 # 2. GESTIONE MEDIA (Sottotitoli, Audio secondario)222 elif line.startswith('#EXT-X-MEDIA:') and 'URI=' in line:223 uri_start = line.find('URI="') + 5224 uri_end = line.find('"', uri_start)225 226 if uri_start > 4 and uri_end > uri_start:227 original_media_url = line[uri_start:uri_end]228 absolute_media_url = urljoin(base_url, original_media_url)229 encoded_media_url = urllib.parse.quote(absolute_media_url, safe='')230 231 # Usa endpoint manifest232 proxy_media_url = f"{proxy_base}/zenith/hls/manifest.m3u8?d={encoded_media_url}{header_params}"233 new_line = line[:uri_start] + proxy_media_url + line[uri_end:]234 rewritten_lines.append(new_line)235 logger.info(f"π Redirected Media URL: {absolute_media_url} -> {proxy_media_url}")236 else:237 rewritten_lines.append(line)238 239 # 3. GESTIONE MAP (Init Segment per fMP4)240 elif line.startswith('#EXT-X-MAP:') and 'URI=' in line:241 uri_start = line.find('URI="') + 5242 uri_end = line.find('"', uri_start)243 244 if uri_start > 4 and uri_end > uri_start:245 original_map_url = line[uri_start:uri_end]246 absolute_map_url = urljoin(base_url, original_map_url)247 encoded_map_url = urllib.parse.quote(absolute_map_url, safe='')248 249 # Usa endpoint segment.mp4250 proxy_map_url = f"{proxy_base}/zenith/hls/segment.mp4?d={encoded_map_url}{header_params}"251 252 new_line = line[:uri_start] + proxy_map_url + line[uri_end:]253 rewritten_lines.append(new_line)254 logger.info(f"π Redirected MAP URL: {absolute_map_url} -> {proxy_map_url}")255 else:256 rewritten_lines.append(line)257 258 # 4. GESTIONE SEGMENTI E SUB-MANIFEST259 elif line and not line.startswith('#'):260 absolute_url = urljoin(base_url, line) if not line.startswith('http') else line261 262 # Eredita query params (es. token)263 if base_query and '?' not in absolute_url:264 absolute_url += f"?{base_query}"265 266 encoded_url = urllib.parse.quote(absolute_url, safe='')267 268 # Se Γ¨ .m3u8 usa /zenith/manifest.m3u8, altrimenti determina estensione269 if '.m3u8' in absolute_url:270 proxy_url = f"{proxy_base}/zenith/manifest.m3u8?url={encoded_url}{header_params}"271 else:272 # β
FIX: Determina estensione corretta per il segmento273 # Se l'URL originale ha estensione mp4/m4s, usa .mp4, altrimenti default a .ts274 # Questo aiuta i player a distinguere tra TS e fMP4275 path = urllib.parse.urlparse(absolute_url).path276 ext = '.ts'277 if path.endswith('.m4s') or path.endswith('.mp4') or path.endswith('.m4v'):278 ext = '.mp4'279 280 proxy_url = f"{proxy_base}/zenith/hls/segment{ext}?d={encoded_url}{header_params}"281 282 rewritten_lines.append(proxy_url)283 284 else:285 # Tutti gli altri tag (es. #EXTINF, #EXT-X-ENDLIST)286 rewritten_lines.append(line)287 288 return '\n'.join(rewritten_lines)289 