Update app.py
Browse files
app.py
CHANGED
|
@@ -9,18 +9,19 @@ import re
|
|
| 9 |
import struct
|
| 10 |
import time
|
| 11 |
import zipfile
|
| 12 |
-
import google.generativeai as genai
|
| 13 |
-
from google.generativeai import types
|
| 14 |
import threading
|
| 15 |
import logging
|
| 16 |
-
import io
|
|
|
|
| 17 |
|
| 18 |
try:
|
| 19 |
from pydub import AudioSegment
|
| 20 |
PYDUB_AVAILABLE = True
|
| 21 |
except ImportError:
|
| 22 |
PYDUB_AVAILABLE = False
|
| 23 |
-
logging.warning("⚠️ pydub نصب نشده است. قابلیت ادغام فایلهای صوتی غیرفعال خواهد بود.")
|
| 24 |
|
| 25 |
# --- START: پیکربندی لاگینگ ---
|
| 26 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
@@ -126,32 +127,30 @@ def smart_text_split(text, max_size=3800):
|
|
| 126 |
final_chunks = [c for c in chunks if c]
|
| 127 |
return final_chunks
|
| 128 |
|
| 129 |
-
def
|
| 130 |
"""
|
| 131 |
-
لیستی از بایتهای صوتی را ادغام کرده و یک
|
| 132 |
"""
|
| 133 |
if not PYDUB_AVAILABLE:
|
| 134 |
-
logging.warning("⚠️ pydub برای ادغام در دسترس نیست.
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
return None
|
| 138 |
-
|
| 139 |
try:
|
| 140 |
-
|
| 141 |
for i, audio_bytes in enumerate(audio_data_list):
|
| 142 |
audio_segment = AudioSegment.from_file(io.BytesIO(audio_bytes), format="wav")
|
| 143 |
-
|
| 144 |
if i < len(audio_data_list) - 1:
|
| 145 |
-
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
| 151 |
except Exception as e:
|
| 152 |
-
logging.error(f"❌ خطا در ادغام بایتهای صوتی: {e}")
|
| 153 |
-
if audio_data_list:
|
| 154 |
-
return io.BytesIO(audio_data_list[0])
|
| 155 |
return None
|
| 156 |
|
| 157 |
# --- START: منطق تولید صدا با قابلیت تلاش مجدد با کلیدهای چرخشی ---
|
|
@@ -186,7 +185,12 @@ def generate_audio_chunk_with_retry(chunk_text, prompt_text, voice, temp):
|
|
| 186 |
|
| 187 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
|
| 188 |
logging.info(f"✅ قطعه با موفقیت توسط کلید شماره {key_idx_display} تولید شد.")
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
else:
|
| 191 |
logging.warning(f"⚠️ پاسخ API برای قطعه با کلید شماره {key_idx_display} بدون داده صوتی بود. تلاش با کلید بعدی...")
|
| 192 |
|
|
@@ -210,22 +214,16 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
|
|
| 210 |
logging.error("❌ متن قابل پردازش به قطعات کوچکتر نیست.")
|
| 211 |
return None
|
| 212 |
|
| 213 |
-
|
| 214 |
-
last_mime_type = None
|
| 215 |
|
| 216 |
for i, chunk in enumerate(text_chunks):
|
| 217 |
logging.info(f"🔊 پردازش قطعه {i+1}/{len(text_chunks)}...")
|
| 218 |
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
if "audio/L" in inline_data.mime_type:
|
| 226 |
-
data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
|
| 227 |
-
|
| 228 |
-
generated_audio_data_list.append(data_buffer)
|
| 229 |
else:
|
| 230 |
logging.error(f"🛑 فرآیند متوقف شد زیرا تولید قطعه {i+1} با تمام کلیدهای موجود ناموفق بود.")
|
| 231 |
break
|
|
@@ -233,27 +231,46 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
|
|
| 233 |
if i < len(text_chunks) - 1 and len(text_chunks) > 1:
|
| 234 |
time.sleep(sleep_time)
|
| 235 |
|
| 236 |
-
if not
|
| 237 |
logging.error(f"❌ هیچ داده صوتی تولید نشد.")
|
| 238 |
return None
|
| 239 |
|
| 240 |
-
|
| 241 |
|
| 242 |
-
if len(
|
| 243 |
-
logging.info("♻️ ادغام قطعات صوتی...")
|
| 244 |
-
|
| 245 |
-
if
|
| 246 |
-
logging.info("✅ ادغام با موفقیت انجام شد.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
else:
|
| 248 |
-
logging.
|
| 249 |
-
|
| 250 |
-
elif len(generated_audio_data_list) == 1:
|
| 251 |
-
logging.info("✅ تنها یک قطعه صوتی تولید شد. نیازی به ادغام نیست.")
|
| 252 |
-
final_audio_bytes_io = io.BytesIO(generated_audio_data_list[0])
|
| 253 |
|
| 254 |
-
if
|
| 255 |
logging.info("✅ عملیات تولید صدا با موفقیت کامل شد.")
|
| 256 |
-
return
|
| 257 |
else:
|
| 258 |
logging.error("❓ وضعیت نامشخص برای خروجی نهایی صدا.")
|
| 259 |
return None
|
|
@@ -273,8 +290,9 @@ def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_pr
|
|
| 273 |
actual_text = text_to_speak
|
| 274 |
if not actual_text or not actual_text.strip(): logging.warning("❌ متن ورودی برای تبدیل خالی است."); return None
|
| 275 |
|
| 276 |
-
|
| 277 |
-
|
|
|
|
| 278 |
|
| 279 |
# --- تابع جدید برای ریست خودکار هر 24 ساعت ---
|
| 280 |
def auto_restart_service():
|
|
@@ -382,8 +400,8 @@ with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), c
|
|
| 382 |
|
| 383 |
generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
|
| 384 |
|
| 385 |
-
# مهم: type="
|
| 386 |
-
output_audio = gr.Audio(label=" ", type="
|
| 387 |
|
| 388 |
generate_button.click(
|
| 389 |
fn=gradio_tts_interface,
|
|
|
|
| 9 |
import struct
|
| 10 |
import time
|
| 11 |
import zipfile
|
| 12 |
+
import google.generativeai as genai
|
| 13 |
+
from google.generativeai import types
|
| 14 |
import threading
|
| 15 |
import logging
|
| 16 |
+
import io
|
| 17 |
+
import numpy as np # جدید: این خط را اضافه کنید
|
| 18 |
|
| 19 |
try:
|
| 20 |
from pydub import AudioSegment
|
| 21 |
PYDUB_AVAILABLE = True
|
| 22 |
except ImportError:
|
| 23 |
PYDUB_AVAILABLE = False
|
| 24 |
+
logging.warning("⚠️ pydub نصب نشده است. قابلیت ادغام فایلهای صوتی و تبدیل به NumPy غیرفعال خواهد بود.")
|
| 25 |
|
| 26 |
# --- START: پیکربندی لاگینگ ---
|
| 27 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
|
|
| 127 |
final_chunks = [c for c in chunks if c]
|
| 128 |
return final_chunks
|
| 129 |
|
| 130 |
+
def merge_audio_bytes_to_numpy(audio_data_list: list[bytes]) -> tuple[int, np.ndarray] | None:
|
| 131 |
"""
|
| 132 |
+
لیستی از بایتهای صوتی WAV را ادغام کرده و یک تاپل (sample_rate, numpy_array) برمیگرداند.
|
| 133 |
"""
|
| 134 |
if not PYDUB_AVAILABLE:
|
| 135 |
+
logging.warning("⚠️ pydub برای ادغام و تبدیل به NumPy در دسترس نیست.")
|
| 136 |
+
return None # در این حالت نمیتوانیم خروجی NumPy بدهیم
|
| 137 |
+
|
|
|
|
|
|
|
| 138 |
try:
|
| 139 |
+
combined_audio_segment = AudioSegment.empty()
|
| 140 |
for i, audio_bytes in enumerate(audio_data_list):
|
| 141 |
audio_segment = AudioSegment.from_file(io.BytesIO(audio_bytes), format="wav")
|
| 142 |
+
combined_audio_segment += audio_segment
|
| 143 |
if i < len(audio_data_list) - 1:
|
| 144 |
+
combined_audio_segment += AudioSegment.silent(duration=150) # 150 میلیثانیه سکوت
|
| 145 |
|
| 146 |
+
# استخراج نرخ نمونه و دادههای صوتی به عنوان آرایه NumPy
|
| 147 |
+
sample_rate = combined_audio_segment.frame_rate
|
| 148 |
+
# pydub به صورت پیشفرض دادهها را به int16 تبدیل میکند، مناسب برای NumPy
|
| 149 |
+
audio_array = np.array(combined_audio_segment.get_array_of_samples())
|
| 150 |
+
|
| 151 |
+
return (sample_rate, audio_array)
|
| 152 |
except Exception as e:
|
| 153 |
+
logging.error(f"❌ خطا در ادغام بایتهای صوتی و تبدیل به NumPy: {e}")
|
|
|
|
|
|
|
| 154 |
return None
|
| 155 |
|
| 156 |
# --- START: منطق تولید صدا با قابلیت تلاش مجدد با کلیدهای چرخشی ---
|
|
|
|
| 185 |
|
| 186 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
|
| 187 |
logging.info(f"✅ قطعه با موفقیت توسط کلید شماره {key_idx_display} تولید شد.")
|
| 188 |
+
# همیشه داده را به صورت بایت WAV برمیگرداند.
|
| 189 |
+
data_buffer = response.candidates[0].content.parts[0].inline_data.data
|
| 190 |
+
mime_type = response.candidates[0].content.parts[0].inline_data.mime_type
|
| 191 |
+
if "audio/L" in mime_type:
|
| 192 |
+
data_buffer = convert_to_wav(data_buffer, mime_type)
|
| 193 |
+
return data_buffer
|
| 194 |
else:
|
| 195 |
logging.warning(f"⚠️ پاسخ API برای قطعه با کلید شماره {key_idx_display} بدون داده صوتی بود. تلاش با کلید بعدی...")
|
| 196 |
|
|
|
|
| 214 |
logging.error("❌ متن قابل پردازش به قطعات کوچکتر نیست.")
|
| 215 |
return None
|
| 216 |
|
| 217 |
+
generated_wav_bytes_list = [] # لیست حاوی دادههای صوتی هر قطعه (بایت WAV)
|
|
|
|
| 218 |
|
| 219 |
for i, chunk in enumerate(text_chunks):
|
| 220 |
logging.info(f"🔊 پردازش قطعه {i+1}/{len(text_chunks)}...")
|
| 221 |
|
| 222 |
+
# generate_audio_chunk_with_retry اکنون مستقیماً بایتهای WAV را برمیگرداند
|
| 223 |
+
wav_data_for_chunk = generate_audio_chunk_with_retry(chunk, prompt_input, selected_voice, temperature_val)
|
| 224 |
+
|
| 225 |
+
if wav_data_for_chunk:
|
| 226 |
+
generated_wav_bytes_list.append(wav_data_for_for_chunk)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
else:
|
| 228 |
logging.error(f"🛑 فرآیند متوقف شد زیرا تولید قطعه {i+1} با تمام کلیدهای موجود ناموفق بود.")
|
| 229 |
break
|
|
|
|
| 231 |
if i < len(text_chunks) - 1 and len(text_chunks) > 1:
|
| 232 |
time.sleep(sleep_time)
|
| 233 |
|
| 234 |
+
if not generated_wav_bytes_list:
|
| 235 |
logging.error(f"❌ هیچ داده صوتی تولید نشد.")
|
| 236 |
return None
|
| 237 |
|
| 238 |
+
final_audio_output = None
|
| 239 |
|
| 240 |
+
if len(generated_wav_bytes_list) > 1:
|
| 241 |
+
logging.info("♻️ ادغام قطعات صوتی و تبدیل به NumPy...")
|
| 242 |
+
final_audio_output = merge_audio_bytes_to_numpy(generated_wav_bytes_list)
|
| 243 |
+
if final_audio_output:
|
| 244 |
+
logging.info("✅ ادغام و تبدیل به NumPy با موفقیت انجام شد.")
|
| 245 |
+
else:
|
| 246 |
+
logging.warning("⚠️ ادغام ناموفق بود یا pydub در دسترس نیست. تلاش برای بازگرداندن اولین قطعه به عنوان NumPy...")
|
| 247 |
+
if generated_wav_bytes_list and PYDUB_AVAILABLE:
|
| 248 |
+
try:
|
| 249 |
+
# اگر ادغام به مشکل خورد، سعی میکنیم حداقل اولین قطعه را به NumPy تبدیل کنیم
|
| 250 |
+
single_audio_segment = AudioSegment.from_file(io.BytesIO(generated_wav_bytes_list[0]), format="wav")
|
| 251 |
+
final_audio_output = (single_audio_segment.frame_rate, np.array(single_audio_segment.get_array_of_samples()))
|
| 252 |
+
except Exception as e:
|
| 253 |
+
logging.error(f"❌ خطا در تبدیل اولین قطعه به NumPy: {e}")
|
| 254 |
+
return None
|
| 255 |
+
else:
|
| 256 |
+
return None # هیچ راهی برای بازگرداندن NumPy بدون pydub/داده وجود ندارد
|
| 257 |
+
|
| 258 |
+
elif len(generated_wav_bytes_list) == 1:
|
| 259 |
+
logging.info("✅ تنها یک قطعه صوتی تولید شد. تبدیل مستقیم به NumPy.")
|
| 260 |
+
if PYDUB_AVAILABLE:
|
| 261 |
+
try:
|
| 262 |
+
single_audio_segment = AudioSegment.from_file(io.BytesIO(generated_wav_bytes_list[0]), format="wav")
|
| 263 |
+
final_audio_output = (single_audio_segment.frame_rate, np.array(single_audio_segment.get_array_of_samples()))
|
| 264 |
+
except Exception as e:
|
| 265 |
+
logging.error(f"❌ خطا در تبدیل قطعه تکی به NumPy: {e}")
|
| 266 |
+
return None
|
| 267 |
else:
|
| 268 |
+
logging.error("❌ pydub برای تبدیل قطعه تکی به NumPy در دسترس نیست.")
|
| 269 |
+
return None # نمیتوانیم خروجی numpy بدهیم
|
|
|
|
|
|
|
|
|
|
| 270 |
|
| 271 |
+
if final_audio_output:
|
| 272 |
logging.info("✅ عملیات تولید صدا با موفقیت کامل شد.")
|
| 273 |
+
return final_audio_output
|
| 274 |
else:
|
| 275 |
logging.error("❓ وضعیت نامشخص برای خروجی نهایی صدا.")
|
| 276 |
return None
|
|
|
|
| 290 |
actual_text = text_to_speak
|
| 291 |
if not actual_text or not actual_text.strip(): logging.warning("❌ متن ورودی برای تبدیل خالی است."); return None
|
| 292 |
|
| 293 |
+
# core_generate_audio اکنون یک تاپل (sample_rate, numpy_array) برمیگرداند
|
| 294 |
+
output_audio_data_numpy = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature)
|
| 295 |
+
return output_audio_data_numpy
|
| 296 |
|
| 297 |
# --- تابع جدید برای ریست خودکار هر 24 ساعت ---
|
| 298 |
def auto_restart_service():
|
|
|
|
| 400 |
|
| 401 |
generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
|
| 402 |
|
| 403 |
+
# مهم: type="numpy" را برای خروجی صوتی تنظیم کنید
|
| 404 |
+
output_audio = gr.Audio(label=" ", type="numpy", elem_id="output_audio_player_alpha_v3")
|
| 405 |
|
| 406 |
generate_button.click(
|
| 407 |
fn=gradio_tts_interface,
|