Update app.py
Browse files
app.py
CHANGED
|
@@ -115,17 +115,18 @@ def get_user_ip():
|
|
| 115 |
return request.headers.getlist("X-Forwarded-For")[0].split(',')[0].strip()
|
| 116 |
return request.remote_addr
|
| 117 |
|
| 118 |
-
# --- TTS HELPER FUNCTIONS (
|
| 119 |
def call_worker(index, chunk_payload):
|
| 120 |
raw_text = chunk_payload.get("text", "")
|
| 121 |
-
clean_text = raw_text.strip()
|
| 122 |
|
| 123 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
if not clean_text:
|
| 125 |
-
logging.warning(f"⚠️ [قطعه {index+1}] متن کاملا خالی بود.")
|
| 126 |
return index, AudioSegment.silent(duration=500)
|
| 127 |
|
| 128 |
-
# زنده بودن مدل برای متون زیر 600 حرف
|
| 129 |
use_live = True if len(clean_text) <= 600 else False
|
| 130 |
|
| 131 |
target_speaker = chunk_payload.get("speaker")
|
|
@@ -141,7 +142,7 @@ def call_worker(index, chunk_payload):
|
|
| 141 |
"fallback_to_live": True
|
| 142 |
}
|
| 143 |
|
| 144 |
-
#
|
| 145 |
max_attempts = 50
|
| 146 |
|
| 147 |
for attempt in range(max_attempts):
|
|
@@ -150,7 +151,7 @@ def call_worker(index, chunk_payload):
|
|
| 150 |
|
| 151 |
for worker_url in workers:
|
| 152 |
try:
|
| 153 |
-
logging.info(f"▶️ [قطعه {index+1}] ارسال
|
| 154 |
response = requests.post(worker_url, json=worker_payload, headers=STANDARD_HEADERS, timeout=120)
|
| 155 |
|
| 156 |
if response.status_code == 200:
|
|
@@ -164,16 +165,15 @@ def call_worker(index, chunk_payload):
|
|
| 164 |
continue
|
| 165 |
|
| 166 |
except Exception as e:
|
| 167 |
-
logging.error(f"❌ [قطعه {index+1}] قطعی ارتباط
|
| 168 |
continue
|
| 169 |
|
| 170 |
sleep_time = 3 + (attempt * 1.5) + random.uniform(0.5, 2.0)
|
| 171 |
-
logging.warning(f"🔄 [قطعه {index+1}]
|
| 172 |
time.sleep(sleep_time)
|
| 173 |
|
| 174 |
-
# اگر
|
| 175 |
-
|
| 176 |
-
return index, None
|
| 177 |
|
| 178 |
# --- AI PODCAST SCRIPT LOGIC (Cohere Labs Space) ---
|
| 179 |
def generate_podcast_in_background(task_id, system_prompt, safety_settings):
|
|
@@ -246,7 +246,7 @@ def generate_podcast_in_background(task_id, system_prompt, safety_settings):
|
|
| 246 |
except Exception as e:
|
| 247 |
with tasks_lock: tasks[task_id].update({'status': 'failed', 'error': str(e)})
|
| 248 |
|
| 249 |
-
# --- FULL AUTO PODCAST LOGIC ---
|
| 250 |
def generate_full_podcast_audio_background(task_id, prompt, speakers):
|
| 251 |
try:
|
| 252 |
logging.info(f"🚀 [پروژه {task_id}] عملیات ساخت پادکست آغاز شد.")
|
|
@@ -255,20 +255,21 @@ def generate_full_podcast_audio_background(task_id, prompt, speakers):
|
|
| 255 |
|
| 256 |
spk_text = "\n".join([f"- {s['id']}: {s['name']}" for s in speakers])
|
| 257 |
|
| 258 |
-
#
|
| 259 |
system_prompt = f"""Act as a Professional Podcast Producer.
|
| 260 |
Topic: "{prompt}"
|
| 261 |
Speakers Available:
|
| 262 |
{spk_text}
|
| 263 |
|
| 264 |
-
CRITICAL
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
|
| 270 |
Output ONLY valid JSON.
|
| 271 |
-
Format: {{"selected_speakers": ["id1", "id2"], "script": [{{"speaker_id": "id1", "dialogue": "..."}}]}}
|
|
|
|
| 272 |
|
| 273 |
AYA_SPACE_URL = "https://coherelabs-aya-expanse.hf.space/gradio_api"
|
| 274 |
MAX_ATTEMPTS = 50
|
|
@@ -319,13 +320,14 @@ Format: {{"selected_speakers": ["id1", "id2"], "script": [{{"speaker_id": "id1",
|
|
| 319 |
for t in data["script"]:
|
| 320 |
if "dialogue" in t: t["dialogue"] = re.sub(r'\[.*?\]|\(.*?\)', '', t["dialogue"]).strip()
|
| 321 |
|
| 322 |
-
|
|
|
|
| 323 |
total_turns = len(script_turns)
|
| 324 |
|
| 325 |
-
logging.info(f"✅ سناریو
|
| 326 |
|
| 327 |
with tasks_lock:
|
| 328 |
-
tasks[task_id] = {'status': 'generating_audio', 'progress': f'در حال تولید
|
| 329 |
|
| 330 |
audio_results = [None] * total_turns
|
| 331 |
completed_count = 0
|
|
@@ -339,22 +341,29 @@ Format: {{"selected_speakers": ["id1", "id2"], "script": [{{"speaker_id": "id1",
|
|
| 339 |
raise ValueError(f"خطا در تولید صدای نوبت {index+1}")
|
| 340 |
return idx, audio_seg
|
| 341 |
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
| 346 |
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
| 351 |
audio_results[idx] = audio_seg
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
|
|
|
|
|
|
| 358 |
|
| 359 |
logging.info("✂️ تمام قطعات ساخته شد. در حال ادغام (میکس) فایلها...")
|
| 360 |
combined_audio = AudioSegment.empty()
|
|
@@ -492,7 +501,8 @@ def create_full_podcast():
|
|
| 492 |
|
| 493 |
safety = [{"category": c, "threshold": "BLOCK_NONE"} for c in ["HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"]]
|
| 494 |
spk_text = "\n".join([f"- {s['id']}: {s['name']}" for s in speakers])
|
| 495 |
-
|
|
|
|
| 496 |
|
| 497 |
threading.Thread(target=generate_podcast_in_background, args=(task_id, sys_prompt, safety)).start()
|
| 498 |
return jsonify({"task_id": task_id}), 202
|
|
|
|
| 115 |
return request.headers.getlist("X-Forwarded-For")[0].split(',')[0].strip()
|
| 116 |
return request.remote_addr
|
| 117 |
|
| 118 |
+
# --- TTS HELPER FUNCTIONS (تمیزکننده و سازنده پرقدرت) ---
|
| 119 |
def call_worker(index, chunk_payload):
|
| 120 |
raw_text = chunk_payload.get("text", "")
|
|
|
|
| 121 |
|
| 122 |
+
# 🔴 پاکسازی متن از تمام کاراکترهای سمی (مارکداون، براکت و ...)
|
| 123 |
+
clean_text = re.sub(r'[*_`~#]', '', raw_text)
|
| 124 |
+
clean_text = re.sub(r'\[.*?\]|\(.*?\)', '', clean_text)
|
| 125 |
+
clean_text = clean_text.strip()
|
| 126 |
+
|
| 127 |
if not clean_text:
|
|
|
|
| 128 |
return index, AudioSegment.silent(duration=500)
|
| 129 |
|
|
|
|
| 130 |
use_live = True if len(clean_text) <= 600 else False
|
| 131 |
|
| 132 |
target_speaker = chunk_payload.get("speaker")
|
|
|
|
| 142 |
"fallback_to_live": True
|
| 143 |
}
|
| 144 |
|
| 145 |
+
# تلاش مستمر و بدون جا انداختن حتی یک کلمه
|
| 146 |
max_attempts = 50
|
| 147 |
|
| 148 |
for attempt in range(max_attempts):
|
|
|
|
| 151 |
|
| 152 |
for worker_url in workers:
|
| 153 |
try:
|
| 154 |
+
logging.info(f"▶️ [قطعه {index+1}] ارسال به {worker_url} (تلاش {attempt+1}/{max_attempts})")
|
| 155 |
response = requests.post(worker_url, json=worker_payload, headers=STANDARD_HEADERS, timeout=120)
|
| 156 |
|
| 157 |
if response.status_code == 200:
|
|
|
|
| 165 |
continue
|
| 166 |
|
| 167 |
except Exception as e:
|
| 168 |
+
logging.error(f"❌ [قطعه {index+1}] قطعی ارتباط: {str(e)[:40]}")
|
| 169 |
continue
|
| 170 |
|
| 171 |
sleep_time = 3 + (attempt * 1.5) + random.uniform(0.5, 2.0)
|
| 172 |
+
logging.warning(f"🔄 [قطعه {index+1}] کارگرها شلوغ/خطا دادند. استراحت به مدت {sleep_time:.1f} ثانیه...")
|
| 173 |
time.sleep(sleep_time)
|
| 174 |
|
| 175 |
+
# اگر بعد از 50 بار ساخته نشد، ارور صادر میکنه تا پادکست ناقص نمونه
|
| 176 |
+
raise ValueError(f"قطعه {index+1} پس از 50 بار تلاش ساخته نشد.")
|
|
|
|
| 177 |
|
| 178 |
# --- AI PODCAST SCRIPT LOGIC (Cohere Labs Space) ---
|
| 179 |
def generate_podcast_in_background(task_id, system_prompt, safety_settings):
|
|
|
|
| 246 |
except Exception as e:
|
| 247 |
with tasks_lock: tasks[task_id].update({'status': 'failed', 'error': str(e)})
|
| 248 |
|
| 249 |
+
# --- FULL AUTO PODCAST LOGIC (پردازش دستهای 7تایی) ---
|
| 250 |
def generate_full_podcast_audio_background(task_id, prompt, speakers):
|
| 251 |
try:
|
| 252 |
logging.info(f"🚀 [پروژه {task_id}] عملیات ساخت پادکست آغاز شد.")
|
|
|
|
| 255 |
|
| 256 |
spk_text = "\n".join([f"- {s['id']}: {s['name']}" for s in speakers])
|
| 257 |
|
| 258 |
+
# 🔴 پرامپت دقیقاً منطبق با سایت شد + دستور اکید برای نوشتن فقط به زبان فارسی تا کارگر ارور نده
|
| 259 |
system_prompt = f"""Act as a Professional Podcast Producer.
|
| 260 |
Topic: "{prompt}"
|
| 261 |
Speakers Available:
|
| 262 |
{spk_text}
|
| 263 |
|
| 264 |
+
CRITICAL INSTRUCTION: You must create a VERY LONG, in-depth, and highly detailed podcast script.
|
| 265 |
+
- Do NOT write a short summary.
|
| 266 |
+
- The conversation must deeply explore the topic. Make it a very long and detailed conversation, as long as necessary to fully cover the topic.
|
| 267 |
+
- Make the dialogue engaging and informative.
|
| 268 |
+
- 🔴 IMPORTANT: Write the ENTIRE script ONLY in Persian (Farsi) language. Do NOT use English characters.
|
| 269 |
|
| 270 |
Output ONLY valid JSON.
|
| 271 |
+
Format: {{"selected_speakers": ["id1", "id2"], "script": [{{"speaker_id": "id1", "dialogue": "..."}}]}}
|
| 272 |
+
Dialogue rules: No stage directions like [laugh], (sigh). Just spoken words."""
|
| 273 |
|
| 274 |
AYA_SPACE_URL = "https://coherelabs-aya-expanse.hf.space/gradio_api"
|
| 275 |
MAX_ATTEMPTS = 50
|
|
|
|
| 320 |
for t in data["script"]:
|
| 321 |
if "dialogue" in t: t["dialogue"] = re.sub(r'\[.*?\]|\(.*?\)', '', t["dialogue"]).strip()
|
| 322 |
|
| 323 |
+
# فقط نوبتهایی که متن دارن رو پردازش میکنیم
|
| 324 |
+
script_turns = [t for t in data.get("script", []) if str(t.get("dialogue", "")).strip()]
|
| 325 |
total_turns = len(script_turns)
|
| 326 |
|
| 327 |
+
logging.info(f"✅ سناریو استخراج شد! شامل {total_turns} نوبت گفتگو.")
|
| 328 |
|
| 329 |
with tasks_lock:
|
| 330 |
+
tasks[task_id] = {'status': 'generating_audio', 'progress': f'در حال تولید صداها (0 از {total_turns} تکمیل شده)'}
|
| 331 |
|
| 332 |
audio_results = [None] * total_turns
|
| 333 |
completed_count = 0
|
|
|
|
| 341 |
raise ValueError(f"خطا در تولید صدای نوبت {index+1}")
|
| 342 |
return idx, audio_seg
|
| 343 |
|
| 344 |
+
# 🌟 استراتژی طلایی دستهبندی (7 تا 7 تا) که پیشنهاد خودت بود 🌟
|
| 345 |
+
batch_size = 7
|
| 346 |
+
batches = [script_turns[i:i + batch_size] for i in range(0, total_turns, batch_size)]
|
| 347 |
+
|
| 348 |
+
for batch_index, batch in enumerate(batches):
|
| 349 |
+
logging.info(f"🔄 شروع پردازش دستهی {batch_index+1} از {len(batches)} (شامل {len(batch)} دیالوگ)...")
|
| 350 |
|
| 351 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=len(batch)) as executor:
|
| 352 |
+
start_idx = batch_index * batch_size
|
| 353 |
+
future_to_index = {executor.submit(process_single_chunk, start_idx + i, turn): start_idx + i for i, turn in enumerate(batch)}
|
| 354 |
+
|
| 355 |
+
for future in concurrent.futures.as_completed(future_to_index):
|
| 356 |
+
try:
|
| 357 |
+
idx, audio_seg = future.result()
|
| 358 |
audio_results[idx] = audio_seg
|
| 359 |
+
completed_count += 1
|
| 360 |
+
with tasks_lock:
|
| 361 |
+
tasks[task_id]['progress'] = f'در حال تولید صداها ({completed_count} از {total_turns} تکمیل شده)'
|
| 362 |
+
except Exception as exc:
|
| 363 |
+
logging.error(f"❌ خطای بحرانی: {str(exc)}")
|
| 364 |
+
raise Exception(str(exc))
|
| 365 |
+
|
| 366 |
+
logging.info(f"✔️ دستهی {batch_index+1} با موفقیت تمام شد.")
|
| 367 |
|
| 368 |
logging.info("✂️ تمام قطعات ساخته شد. در حال ادغام (میکس) فایلها...")
|
| 369 |
combined_audio = AudioSegment.empty()
|
|
|
|
| 501 |
|
| 502 |
safety = [{"category": c, "threshold": "BLOCK_NONE"} for c in ["HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"]]
|
| 503 |
spk_text = "\n".join([f"- {s['id']}: {s['name']}" for s in speakers])
|
| 504 |
+
|
| 505 |
+
sys_prompt = f"""Act as a Professional Podcast Producer.\nTopic: "{prompt}"\nSpeakers Available:\n{spk_text}\nCRITICAL INSTRUCTION: You must create a VERY LONG, in-depth, and highly detailed podcast script.\n- Do NOT write a short summary.\n- The conversation must deeply explore the topic.\n- Make the dialogue engaging and informative.\n- 🔴 IMPORTANT: Write the ENTIRE script ONLY in Persian (Farsi) language. Do NOT use English characters.\n\nOutput ONLY valid JSON.\nFormat: {{"selected_speakers": ["id1", "id2"], "script": [{{"speaker_id": "id1", "dialogue": "..."}}]}}\nDialogue rules: No stage directions like [laugh], (sigh). Just spoken words."""
|
| 506 |
|
| 507 |
threading.Thread(target=generate_podcast_in_background, args=(task_id, sys_prompt, safety)).start()
|
| 508 |
return jsonify({"task_id": task_id}), 202
|