# core/api_clients.py import os import time import random import requests import shutil import subprocess import uuid import base64 from threading import Lock from gradio_client import Client class AwaazAPI: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://awaaz-j36o.onrender.com/api" self.headers = { "X-API-Key": self.api_key, "Content-Type": "application/json" } def enhance_script(self, text): print("-> ЁЯОн Awaaz API рд╕реЗ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ рдЗрдореЛрд╢рди рдЯреИрдЧреНрд╕ рдЬреЛрдбрд╝реЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ...") try: response = requests.post(f"{self.base_url}/proxy-enhance", json={"text": text}, headers=self.headers, timeout=60) response.raise_for_status() enhanced = response.json().get("enhanced_text", text) print("-> тЬЕ Script Enhanced!") return enhanced except Exception as e: print(f"ЁЯЪи Awaaz Enhance Error (Skipping Enhancement): {e}") return text def generate_audio(self, text, output_path): print("-> ЁЯОЩя╕П Awaaz API (Custom AI TTS) рд╕реЗ рдЙрдЪреНрдЪ-рдЧреБрдгрд╡рддреНрддрд╛ рдСрдбрд┐рдпреЛ рдмрдирд╛рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ...") try: response = requests.post(f"{self.base_url}/proxy-tts", json={"text": text}, headers=self.headers, timeout=300) if response.status_code == 403: raise Exception("Awaaz API Key is invalid! (403 Forbidden)") response.raise_for_status() with open(output_path, "wb") as f: f.write(response.content) print(f"-> тЬЕ Custom AI Audio Saved: {output_path}") return output_path except Exception as e: raise Exception(f"ЁЯЪи Awaaz TTS Generation Error: {e}") class GroqAPI: def __init__(self, api_keys): self.api_keys = api_keys self.audio_url = "https://api.groq.com/openai/v1/audio/transcriptions" self.chat_url = "https://api.groq.com/openai/v1/chat/completions" self.audio_model = "whisper-large-v3" self.chat_model = "meta-llama/llama-4-scout-17b-16e-instruct" self._key_index = 0 self._lock = Lock() def get_next_key(self): with self._lock: key = self.api_keys[self._key_index % len(self.api_keys)] self._key_index += 1 return key def transcribe_audio(self, audio_path): if not self.api_keys: raise Exception("Groq API key not found.") for attempt in range(len(self.api_keys) * 2): api_key = self.get_next_key() data = {'model': self.audio_model, 'response_format': 'verbose_json', 'timestamp_granularities[]': 'word'} headers = {'Authorization': f'Bearer {api_key}'} try: with open(audio_path, 'rb') as audio_file: mime_type = 'audio/wav' if audio_path.endswith('.wav') else 'audio/mpeg' files = {'file': (os.path.basename(audio_path), audio_file, mime_type)} print(f"-> Groq API рдХреЛ рдЯреНрд░рд╛рдВрд╕рдХреНрд░рд┐рдкреНрд╢рди рднреЗрдЬрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ...") response = requests.post(self.audio_url, headers=headers, data=data, files=files, timeout=120) if response.status_code == 429: time.sleep(2 ** (attempt % 3)) continue response.raise_for_status() return response.json().get('words', []) except Exception as e: time.sleep(1) raise Exception("Groq Transcription Error: All keys failed.") def translate_to_english(self, hindi_text): if not hindi_text or hindi_text == "[PAUSE]": return "" api_key = self.get_next_key() headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} system_prompt = "You are a highly accurate Hindi to English translator. Translate the given Hindi text to English. Output ONLY the English translation, without any quotes, explanations, or extra text." payload = { "model": self.chat_model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": hindi_text} ], "temperature": 0.3, "max_tokens": 100 } try: response = requests.post(self.chat_url, headers=headers, json=payload, timeout=15) response.raise_for_status() return response.json()['choices'][0]['message']['content'].strip().strip('"').strip("'") except Exception as e: print(f"ЁЯЪи Groq Translation Error: {e}") return hindi_text def generate_visual_prompt(self, script_line, media_type): api_key = self.get_next_key() headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } if media_type == "png": rules = "Write a LITERAL prompt for an ISOLATED object or character against a SOLID WHITE background (for easy removal). Focus solely on the subject details, textures, and lighting. Describe it as a highly detailed cutout. NO scene background, NO context, NO metaphors." elif media_type == "video": rules = "Write a highly detailed, LITERAL text-to-video prompt IN ENGLISH ONLY. Describe motion, camera angle, lighting, and subject exactly. NO metaphors. NO split screens, NO text." else: rules = "Write a highly detailed, LITERAL oil-painting style prompt IN ENGLISH ONLY. Focus on dramatic lighting, epic scale, and rich colors. NO metaphors. NO frames, NO text." system_prompt = f"You are a strict Literal Visual Prompt Engineer. {rules} Output ONLY the English prompt string, nothing else." payload = { "model": self.chat_model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Scene context: {script_line}"} ], "temperature": 0.7, "max_tokens": 100 } try: response = requests.post(self.chat_url, headers=headers, json=payload, timeout=30) response.raise_for_status() prompt_result = response.json()['choices'][0]['message']['content'].strip() return prompt_result.strip('"').strip("'") except Exception as e: print(f"ЁЯЪи Groq Prompt Engineer Error: {e}") return script_line class StockClipAPI: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://scfai-f.vercel.app" self.headers = { "X-API-Key": self.api_key, "Content-Type": "application/json" } self.ui_logger = None def set_logger(self, logger_func): self.ui_logger = logger_func # ЁЯТб рдбрд┐рдлрд╝реЙрд▓реНрдЯ рдкреНрд░реЛрдЧреНрд░реЗрд╕ 35 рд╕реЗрдЯ рдХрд░ рджрд┐рдпрд╛ рд╣реИ, рддрд╛рдХрд┐ рдбреЗрдЯрд╛рдмреЗрд╕ рдХрднреА рдЦрд╛рд▓реА (NULL) рди рдЬрд╛рдП def log(self, message, progress=35): if self.ui_logger: self.ui_logger(message, progress) else: print(message) def search_and_download_batch(self, query, download_dir, chunk_idx, scene_index, orientation="vertical", quality="1080p+", top_n=3): """ рд╕рд░реНрдЪ рдЗрдирд┐рд╢рд┐рдПрдЯ рдХрд░рддрд╛ рд╣реИ рдФрд░ рдЯреЙрдк N рдХреНрд▓рд┐рдкреНрд╕ рдХреЛ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдХреЗ рдЙрдирдХреЗ рдкрд╛рдереНрд╕ рдХреА рд▓рд┐рд╕реНрдЯ рджреЗрддрд╛ рд╣реИред рдЕрдЧрд░ 90 рд╕реЗрдХрдВрдб рддрдХ рд░рд┐рдЬрд▓реНрдЯ рдирд╣реАрдВ рдорд┐рд▓рддрд╛, рддреЛ рдлреЙрд▓рдмреИрдХ рдХреЗ рд▓рд┐рдП рдЦрд╛рд▓реА рд▓рд┐рд╕реНрдЯ рд░рд┐рдЯрд░реНрди рдХрд░рддрд╛ рд╣реИред """ self.log(f"-> ЁЯМР StockClip AI: '{query}' рдХреЗ рд▓рд┐рдП рд╣рд╛рдИ-рдХреНрд╡рд╛рд▓рд┐рдЯреА рдХреНрд▓рд┐рдкреНрд╕ рдЦреЛрдЬреА рдЬрд╛ рд░рд╣реА рд╣реИрдВ...", 32) # API рдбреЙрдХреНрдпреВрдореЗрдВрдЯреЗрд╢рди рдХреЗ рд╣рд┐рд╕рд╛рдм рд╕реЗ рдУрд░рд┐рдПрдВрдЯреЗрд╢рди рд╕реЗрдЯ рдХрд░рдирд╛ ori = "portrait" if orientation == "vertical" else "landscape" payload = { "query": query, "orientation": ori, "quality": quality } try: # 1. ЁЯЪА Task Initiate рдХрд░рдирд╛ init_resp = requests.post(f"{self.base_url}/api/search", json=payload, headers=self.headers, timeout=60) init_resp.raise_for_status() task_id = init_resp.json().get("task_id") if not task_id: self.log("ЁЯЪи StockClip AI Error: Task ID рдирд╣реАрдВ рдорд┐рд▓рд╛!", 32) return [] self.log(f"-> тП│ StockClip Task [{task_id[:6]}] рд╢реБрд░реВ! рдХреНрд▓рд┐рдкреНрд╕ рдвреВрдБрдвреА рдЬрд╛ рд░рд╣реА рд╣реИрдВ...", 33) # рд▓реЙрдЧ рд╕реНрдкреИрдо рд░реЛрдХрдиреЗ рдХреЗ рд▓рд┐рдП рдЯреНрд░реИрдХрд░ last_logged_status = None # ЁЯТб Hard Timeout рд╕реЗрдЯрдЕрдк (90 рд╕реЗрдХрдВрдб = 1.5 рдорд┐рдирдЯ) start_time = time.time() max_polling_time = 160 # 2. ЁЯФД Task Status Polling while True: # ЁЯЫС Timeout Check: рдЕрдЧрд░ 90 рд╕реЗрдХрдВрдб рд╕реЗ рдЬрд╝реНрдпрд╛рджрд╛ рд╣реЛ рдЧрдпрд╛, рддреЛ Fallback рдЯреНрд░рд┐рдЧрд░ рдХрд░реЛ elapsed_time = time.time() - start_time if elapsed_time > max_polling_time: self.log(f"ЁЯЪи StockClip AI Timeout Error: 90 рд╕реЗрдХрдВрдб рддрдХ рдХреЛрдИ рдЬрд╡рд╛рдм рдирд╣реАрдВ рдорд┐рд▓рд╛! Fallback рдЯреНрд░рд┐рдЧрд░ рдХрд░ рд░рд╣реЗ рд╣реИрдВ...", 35) return [] # рдЦрд╛рд▓реА рд▓рд┐рд╕реНрдЯ рднреЗрдЬрдиреЗ рд╕реЗ рддреБрдореНрд╣рд╛рд░рд╛ Meta Shield рдЕрдкрдиреЗ рдЖрдк рдЯреНрд░рд┐рдЧрд░ рд╣реЛ рдЬрд╛рдПрдЧрд╛ time.sleep(5.5) # Polling Interval try: status_resp = requests.get(f"{self.base_url}/api/status/{task_id}", headers=self.headers, timeout=60) # 504 Gateway Timeout (Cold Boot) Handling if status_resp.status_code == 504: if last_logged_status != "504": self.log("-> ЁЯЪж AI Backend Cold Boot (504). рд╕рд░реНрд╡рд░ рд╡рд╛рд░реНрдо-рдЕрдк рд╣реЛ рд░рд╣рд╛ рд╣реИ...", 33) last_logged_status = "504" continue # 401 Unauthorized if status_resp.status_code == 401: self.log("ЁЯЪи StockClip AI Auth Error (401)! API Key рдЪреЗрдХ рдХрд░реЗрдВред", 33) return [] # 404 Not Found (Task Syncing) if status_resp.status_code == 404: if last_logged_status != "404": self.log("-> ЁЯФД Task рд╕рд░реНрд╡рд░ рдкрд░ рдкреНрд░реЛрд╕реЗрд╕ рд╣реЛ рд░рд╣рд╛ рд╣реИ... рдХреГрдкрдпрд╛ рдкреНрд░рддреАрдХреНрд╖рд╛ рдХрд░реЗрдВ...", 33) last_logged_status = "404" continue # рд╕рдлрд▓рддрд╛ рдпрд╛ рдЕрдиреНрдп рд╕рд░реНрд╡рд░ рд░рд┐рд╕реНрдкрд╛рдВрд╕ status_resp.raise_for_status() data = status_resp.json() task_info = data.get("task", {}) status = task_info.get("status") if status == "completed": clips_data = task_info.get("data", []) if not clips_data: self.log(f"-> тЪая╕П StockClip AI: '{query}' рдХреЗ рд▓рд┐рдП рдХреЛрдИ рдХреНрд▓рд┐рдк рдирд╣реАрдВ рдорд┐рд▓реАред", 35) return [] self.log(f"-> тЬЕ StockClip AI рдиреЗ {len(clips_data)} рдХреНрд▓рд┐рдкреНрд╕ рдвреВрдВрдвреАрдВ! рдЯреЙрдк {top_n} рдбрд╛рдЙрдирд▓реЛрдб рдХреА рдЬрд╛ рд░рд╣реА рд╣реИрдВ...", 35) downloaded_paths = [] # рд╕рд┐рд░реНрдл рдЯреЙрдк N рдХреНрд▓рд┐рдкреНрд╕ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ (Gemini рдХреЗ рдЪреБрдирдиреЗ рдХреЗ рд▓рд┐рдП) for i, clip in enumerate(clips_data[:top_n]): dl_url = clip.get("download_url") if not dl_url: continue clip_path = os.path.join(download_dir, f"chunk_{chunk_idx}_scene_{scene_index+1}_stockclip_{i}.mp4") dl_resp = requests.get(dl_url, stream=True, timeout=120) dl_resp.raise_for_status() with open(clip_path, 'wb') as f: for chunk in dl_resp.iter_content(chunk_size=8192): f.write(chunk) downloaded_paths.append(clip_path) return downloaded_paths elif status in ["failed", "error"]: self.log("ЁЯЪи StockClip AI Task Failed!", 35) return [] else: # queued, processing, fetching, ranking, vision if last_logged_status != status: self.log(f"-> ЁЯФД StockClip AI Status: {str(status).upper()}...", 34) last_logged_status = status except requests.exceptions.RequestException as e: if last_logged_status != "network_error": self.log(f"тЪая╕П StockClip Network Wait: рд╕рд░реНрд╡рд░ рд╕реЗ рдЬреБреЬ рд░рд╣реЗ рд╣реИрдВ...", 34) last_logged_status = "network_error" except Exception as e: self.log(f"ЁЯЪи StockClip AI Generate Error: {str(e)}", 35) return [] class HuggingFacePNGAPI: def __init__(self, space_name, master_key): self.space_name = space_name self.master_key = master_key self.client = None self._lock = Lock() def generate_and_download(self, prompt, output_path): print(f"-> Gradio Client рдХреЗ рдЬрд╝рд░рд┐рдП HF Space '{self.space_name}' рд╕реЗ AI PNG рдмрдирд╛рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ: '{prompt}'") try: with self._lock: if self.client is None: print(f"-> ЁЯФМ PNG API рдкрд╣рд▓реА рдмрд╛рд░ рдХрдиреЗрдХреНрдЯ рд╣реЛ рд░рд╣рд╛ рд╣реИ ({self.space_name})...") self.client = Client(self.space_name) result_path = self.client.predict( prompt=prompt, secret_key=self.master_key, api_name="/generate_png" ) shutil.copy(result_path, output_path) print(f"-> тЬЕ AI PNG рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╕рд╣реЗрдЬрд╛ рдЧрдпрд╛: {output_path}") return output_path except Exception as e: print(f"ЁЯЪи AI PNG рдЬрдирд░реЗрд╢рди рдореЗрдВ рддреНрд░реБрдЯрд┐ (Gradio Client): {e}") return None # ============================================================================== # # 3. Custom AI Video/Image Generators (Meta Spark Studio & Sparkling API) # ============================================================================== # class CustomImageGenAPI: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://imgen-f.vercel.app" self.headers = { "X-API-Key": self.api_key, "Content-Type": "application/json" } def _save_base64_image(self, b64_string, file_path): """Base64 рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХреЛ рдбрд┐рдХреЛрдб рдХрд░рдХреЗ рдлрд╛рдЗрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рд╕реЗрд╡ рдХрд░рдиреЗ рдХрд╛ рд╣реЗрд▓реНрдкрд░""" if "," in b64_string: b64_string = b64_string.split(",")[1] image_data = base64.b64decode(b64_string) with open(file_path, "wb") as f: f.write(image_data) def generate_and_download(self, prompt, download_path, orientation="vertical"): print(f"-> ЁЯОи Sparkling API рд╕реЗ AI Image рдмрдирд╛рдИ рдЬрд╛ рд░рд╣реА рд╣реИ: '{prompt[:50]}...'") # ЁЯТб API Documentation рдХреЗ рд╣рд┐рд╕рд╛рдм рд╕реЗ рд░реЗрд╢реНрдпреЛ рд╕реЗрдЯ рдХрд░рдирд╛ ratio_str = "9:16" if orientation == "vertical" else "16:9" payload = { "prompt": prompt, "user_negative": "text, watermark, ugly, deformed, blurry, bad anatomy", "style_name": "Oil Painting", # "Cinematic", "Anime", "Cosmic" рднреА рдЗрд╕реНрддреЗрдорд╛рд▓ рдХрд░ рд╕рдХрддреЗ рд╣реЛ "ratio": ratio_str } try: # 1. ЁЯЪА Task Initiate рдХрд░рдирд╛ (Headers рдХреЗ рд╕рд╛рде) response = requests.post(f"{self.base_url}/api/generate", json=payload, headers=self.headers, timeout=160) response.raise_for_status() data = response.json() # тЪб Magic Cache Hit (рдЕрдЧрд░ рдЗрдореЗрдЬ рдкрд╣рд▓реЗ рд╕реЗ рдореМрдЬреВрдж рд╣реИ) if data.get("cached") and data.get("image"): print("-> тЪб Magic Cache Hit! Sparkling API рдиреЗ рддреБрд░рдВрдд рдЗрдореЗрдЬ рд▓реМрдЯрд╛ рджреАред") self._save_base64_image(data["image"], download_path) return download_path task_id = data.get("task_id") if not task_id: print("ЁЯЪи Sparkling Image API Error: Task ID рдирд╣реАрдВ рдорд┐рд▓рд╛!") return None print(f"-> тП│ Task [{task_id[:6]}] рдХрддрд╛рд░ рдореЗрдВ рд╣реИред Status Polling рд╢реБрд░реВ...") # 2. ЁЯФД Task Status Polling (Headers рдХреЗ рд╕рд╛рде) while True: status_res = requests.get(f"{self.base_url}/api/status/{task_id}", headers=self.headers, timeout=160) if status_res.status_code == 404: print("ЁЯЪи Sparkling API Task Error: Task 404 Not Found рдпрд╛ Expire рд╣реЛ рдЧрдпрд╛!") return None status_res.raise_for_status() res_data = status_res.json() status = res_data.get("status") if status == "completed": print("-> тЬЕ Sparkling API Image рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЬрдирд░реЗрдЯ рд╣реЛ рдЧрдИ!") self._save_base64_image(res_data["image"], download_path) return download_path elif status in ["failed", "error"]: print(f"ЁЯЪи Sparkling API Task Failed: {res_data.get('error', 'Unknown Error')}") return None else: # 'queued' рдпрд╛ 'processing' рд╕реНрдЯреЗрдЯрд╕ рдореЗрдВ 2.5 рд╕реЗрдХрдВрдб рд░реБрдХрдирд╛ time.sleep(2.5) except Exception as e: print(f"ЁЯЪи Custom Image Gen API Error: {e}") return None class MetaSparkStudioAPI: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://meta-genai.vercel.app".rstrip("/") self.headers = { "x-api-key": self.api_key, "Content-Type": "application/json" } self.ui_logger = None def set_logger(self, logger_func): self.ui_logger = logger_func def log(self, message): if self.ui_logger: self.ui_logger(message, 60) else: print(message) def _process_and_download_urls(self, urls, download_path, is_image_task): if not urls: self.log("ЁЯЪи MSS API Error: URL рд▓рд┐рд╕реНрдЯ рдЦрд╛рд▓реА рд╣реИ!") return None if is_image_task: self.log(f"-> ЁЯУе MSS рдиреЗ {len(urls)} рдЗрдореЗрдЬреЗрд╕ рджреА рд╣реИрдВред рдкрд╣рд▓реА рд╡рд╛рд▓реА рдХреЛ рдЪреБрдирд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ...") dl = requests.get(urls[0], stream=True, timeout=None) dl.raise_for_status() with open(download_path, 'wb') as f: for chunk in dl.iter_content(chunk_size=8192): f.write(chunk) self.log("-> тЬЕ ЁЯЫбя╕П Ultimate Shield: 1 рд╢рд╛рдирджрд╛рд░ рдЗрдореЗрдЬ рд╕рд╣реЗрдЬреА рдЧрдИ!") return download_path elif len(urls) >= 2: self.log(f"-> ЁЯУе MSS рдиреЗ {len(urls)} рдХреНрд▓рд┐рдкреНрд╕ рджреА рд╣реИрдВред рдкрд╣рд▓реА рджреЛ (5s+5s) рдХреЛ рдлреЗрд╡рд┐рдХреЛрд▓ рд╕реЗ рдЪрд┐рдкрдХрд╛рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ...") base_dir = os.path.dirname(download_path) uid = uuid.uuid4().hex[:6] vid1_path = os.path.join(base_dir, f"mss_part1_{uid}.mp4") vid2_path = os.path.join(base_dir, f"mss_part2_{uid}.mp4") dl1 = requests.get(urls[0], stream=True, timeout=None) dl1.raise_for_status() with open(vid1_path, 'wb') as f: for chunk in dl1.iter_content(chunk_size=8192): f.write(chunk) dl2 = requests.get(urls[1], stream=True, timeout=None) dl2.raise_for_status() with open(vid2_path, 'wb') as f: for chunk in dl2.iter_content(chunk_size=8192): f.write(chunk) concat_txt_path = os.path.join(base_dir, f"mss_concat_{uid}.txt") with open(concat_txt_path, 'w') as f: f.write(f"file '{os.path.abspath(vid1_path)}'\n") f.write(f"file '{os.path.abspath(vid2_path)}'\n") cmd = ['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', concat_txt_path, '-c', 'copy', download_path] subprocess.run(cmd, capture_output=True, check=True) try: os.remove(vid1_path) os.remove(vid2_path) os.remove(concat_txt_path) except: pass self.log("-> тЬЕ ЁЯЫбя╕П Ultimate Shield: рджреЛ 5s рдХреНрд▓рд┐рдкреНрд╕ рд╕рдлрд▓рддрд╛ рдкреВрд░реНрд╡рдХ 10s рдореЗрдВ рдЬреБреЬ рдЧрдИрдВ!") return download_path elif len(urls) == 1: dl = requests.get(urls[0], stream=True, timeout=None) dl.raise_for_status() with open(download_path, 'wb') as f: for chunk in dl.iter_content(chunk_size=8192): f.write(chunk) self.log("-> тЬЕ ЁЯЫбя╕П Ultimate Shield: рдХреЗрд╡рд▓ 1 рдХреНрд▓рд┐рдк рдорд┐рд▓реА, рд╕рд╣реЗрдЬреА рдЧрдИ!") return download_path def generate_and_download(self, prompt, download_path, orientation="vertical"): short_prompt = prompt[:30] + "..." if prompt else "..." self.log(f"-> ЁЯЫбя╕П MSS API (Ultimate Shield) рдХреЛ рд░рд┐рдХреНрд╡реЗрд╕реНрдЯ: '{short_prompt}'") is_image_task = "STATIC IMAGE" in prompt exact_ratio = "9:16" if orientation == "vertical" else "16:9" mss_prompt = f"Generate a video in {exact_ratio} ratio. {prompt}" if not is_image_task else f"Generate an image in {exact_ratio} ratio. {prompt}" payload = { "prompt": mss_prompt, "quality": "high", "duration": 10 } try: init_resp = requests.post(f"{self.base_url}/api/generate", json=payload, headers=self.headers, timeout=60) init_resp.raise_for_status() init_data = init_resp.json() direct_urls = init_data.get("urls") or init_data.get("result", {}).get("urls") if direct_urls: self.log("-> тЪб MSS Cache Hit! рд╕реАрдзреЗ рдЬрдирд░реЗрдЯреЗрдб рдлрд╛рдЗрд▓реНрд╕ рдорд┐рд▓ рдЧрдИрдВред") return self._process_and_download_urls(direct_urls, download_path, is_image_task) task_id = init_data.get("task_id") or init_data.get("id") if not task_id: self.log(f"ЁЯЪи MSS API Error: Task ID рдпрд╛ URLs рджреЛрдиреЛрдВ рдирд╣реАрдВ рдорд┐рд▓реЗ! Response: {init_data}") return None self.log(f"-> тП│ MSS Task [{task_id[:6]}] рдЖрд░рдВрдн рд╣реБрдЖред Polling рд╢реБрд░реВ...") wait_time = 5 while True: try: status_resp = requests.get(f"{self.base_url}/api/status/{task_id}", headers=self.headers, timeout=60) if status_resp.status_code == 404: self.log("ЁЯЪи MSS Task Error: Task 404 Not Found рдпрд╛ Expire рд╣реЛ рдЧрдпрд╛!") return None status_resp.raise_for_status() data = status_resp.json() status = data.get("status") if status == "completed" or "urls" in data or ("result" in data and "urls" in data["result"]): urls = data.get("urls") or data.get("result", {}).get("urls", []) return self._process_and_download_urls(urls, download_path, is_image_task) elif status in ["failed", "error"]: self.log(f"ЁЯЪи MSS Task Failed: {data.get('error', 'Unknown Error')}") return None else: progress = data.get("progress", 0) self.log(f"-> ЁЯФД MSS Status: {str(status).upper()} | Progress: {progress}%") time.sleep(wait_time) wait_time = min(wait_time * 1.5, 30) except Exception as poll_err: self.log(f"тЪая╕П MSS Status Check Error: {poll_err}. Retrying in {wait_time}s...") time.sleep(wait_time) wait_time = min(wait_time * 1.5, 30) except Exception as e: self.log(f"ЁЯЪи MSS Generate API Error: {str(e)}") return None