talk-dial / app.py
suprimedev's picture
Update app.py
fb5eee4 verified
import gradio as gr
from pydub import AudioSegment
import requests
import os
import uuid
import re
# مسیر ذخیره فایل‌های موقت
TEMP_DIR = "temp_audio"
if not os.path.exists(TEMP_DIR):
os.makedirs(TEMP_DIR)
def download_file(url, output_path):
"""فایل را از یک URL دانلود می‌کند."""
try:
response = requests.get(url, stream=True)
response.raise_for_status()
with open(output_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return True
except requests.exceptions.RequestException as e:
print(f"Error downloading {url}: {e}")
return False
except Exception as e:
print(f"An unexpected error occurred during download of {url}: {e}")
return False
def get_audio_from_input(input_source):
"""منبع ورودی را پردازش کرده و یک شی AudioSegment برمی‌گرداند."""
unique_filename = os.path.join(TEMP_DIR, str(uuid.uuid4()))
# Try to determine if it's a URL or a local path.
# For Gradio's file input, it provides a local path.
# For direct text input (like in the tts part), it's a URL.
is_url = input_source.startswith("http://") or input_source.startswith("https://")
audio_path = None # Initialize audio_path to handle finally block
downloaded_temp_filepath = None
if is_url:
file_extension = os.path.splitext(input_source.split('?')[0])[1]
if not file_extension:
file_extension = ".mp3" # Default if no extension found in URL
downloaded_temp_filepath = unique_filename + "_downloaded" + file_extension
if not download_file(input_source, downloaded_temp_filepath):
return None, f"خطا در دانلود فایل از لینک: {input_source}"
audio_path = downloaded_temp_filepath
else: # Assume it's a local file path provided directly or by Gradio
audio_path = input_source
try:
if not os.path.exists(audio_path):
return None, f"فایل پیدا نشد: {audio_path}"
audio = AudioSegment.from_file(audio_path)
return audio, None
except Exception as e:
return None, f"خطا در بارگذاری فایل صوتی ({audio_path}): {e}. مطمئن شوید فایل MP3 یا WAV معتبر است."
finally:
# Clean up downloaded temporary file if it was created
if downloaded_temp_filepath and os.path.exists(downloaded_temp_filepath):
try:
os.remove(downloaded_temp_filepath)
except OSError as e:
print(f"Error removing temporary file {downloaded_temp_filepath}: {e}")
def merge_audio_files(input_sources):
"""چندین فایل صوتی را ادغام می‌کند و یک فایل MP3 خروجی می‌دهد."""
if not input_sources:
return None, "لیست ورودی‌های صوتی خالی است."
combined_audio = AudioSegment.empty()
errors = []
for source in input_sources:
# get_audio_from_input can now handle direct file paths from Gradio or URLs
audio_segment, error = get_audio_from_input(source)
if audio_segment:
combined_audio += audio_segment
else:
errors.append(error)
print(f"Skipping {source} due to error: {error}")
if not combined_audio.duration_seconds > 0:
return None, "هیچ فایل صوتی معتبری برای ادغام پیدا نشد. " + "\n".join(errors) if errors else ""
output_filename = os.path.join(TEMP_DIR, f"merged_audio_{uuid.uuid4()}.mp3")
try:
combined_audio.export(output_filename, format="mp3")
return output_filename, "عملیات موفقیت آمیز بود!"
except Exception as e:
return None, f"خطا در ذخیره فایل خروجی: {e}"
def add_intro_outro_and_background(podcast_audio, intro_audio_url, background_audio_url, outro_audio_url):
"""افکت‌های صوتی را به پادکست اضافه می‌کند."""
intro_audio, intro_error = get_audio_from_input(intro_audio_url)
if intro_audio is None:
return None, f"خطا در دانلود یا بارگذاری صدای ابتدایی: {intro_error}"
background_audio, background_error = get_audio_from_input(background_audio_url)
if background_audio is None:
return None, f"خطا در دانلود یا بارگذاری موزیک پس‌زمینه: {background_error}"
outro_audio, outro_error = get_audio_from_input(outro_audio_url)
if outro_audio is None:
return None, f"خطا در دانلود یا بارگذاری صدای انتهایی: {outro_error}"
# طول کل صدای نهایی برای تکرار موزیک پس‌زمینه
total_duration_estimate = len(intro_audio) + len(podcast_audio) + len(outro_audio)
# مطمئن شوید موزیک پس‌زمینه به اندازه کافی طولانی است یا تکرار شود
if len(background_audio) < total_duration_estimate:
background_audio = background_audio * (int(total_duration_estimate / len(background_audio)) + 1)
# Trim background audio to total duration
background_audio = background_audio[:total_duration_estimate]
# تنظیم شدت صدای پس‌زمینه (gain reduction)
# این مقدار را می‌توانید تغییر دهید. -18dB معمولاً خوب است، اما بسته به نوع موسیقی متغیر است.
# هرچه عدد منفی‌تر باشد، صدا کمتر می‌شود.
background_audio = background_audio - 18 # کاهش مثلاً 18 دسی‌بل از صدای پس‌زمینه
# اعمال fade-in و fade-out برای موزیک پس‌زمینه کلی
fade_duration = 3000 # 3 ثانیه برای fade-in و fade-out کلی
background_audio = background_audio.fade_in(fade_duration).fade_out(fade_duration)
# ایجاد یک قطعه صوتی ساکت به اندازه طول کل پادکست
mixed_audio = AudioSegment.silent(duration=total_duration_estimate)
# 1. افزودن موزیک پس‌زمینه به mixed_audio (این موزیک قبلاً کاهش گین و fade شده است)
mixed_audio = mixed_audio.overlay(background_audio, position=0)
# 2. افزودن Intro Audio (خوش آمد گویی) در ابتدای پادکست
mixed_audio = mixed_audio.overlay(intro_audio, position=0)
# 3. افزودن Podcast Audio (دیالوگ‌ها) به mixed_audio، بلافاصله پس از اینترو
mixed_audio = mixed_audio.overlay(podcast_audio, position=len(intro_audio))
# 4. افزودن Outro Audio (پایان پادکست) به mixed_audio، بلافاصله پس از پادکست
mixed_audio = mixed_audio.overlay(outro_audio, position=len(intro_audio) + len(podcast_audio))
return mixed_audio, None
def tts_and_merge_with_effects(text_input):
"""متن ورودی را پردازش کرده و فایل صوتی با افکت‌های صوتی تولید می‌کند."""
if not text_input.strip():
return None, "لطفاً متنی برای پردازش وارد کنید."
# تجزیه متن و تولید پادکست اصلی
lines = text_input.strip().split('\n')
audio_urls_to_merge = []
errors = []
for line in lines:
match = re.match(r'^\s*\((\d+)\)(.*)$', line)
if match:
speaker_number = match.group(1)
text_for_tts = match.group(2).strip()
if not text_for_tts:
errors.append(f"خطا: متن خالی برای گوینده {speaker_number} در خط '{line}'")
continue
# ساخت URL برای Talkbot API
# Encode the text for URL safety
encoded_text = requests.utils.quote(text_for_tts)
tts_url = f"https://talkbot.ir/api/TTS-S{speaker_number}?text={encoded_text}"
print(f"درخواست TTS برای گوینده {speaker_number}: {tts_url}")
try:
response = requests.get(tts_url)
response.raise_for_status()
audio_link = response.text.strip()
if audio_link.startswith("http"):
audio_urls_to_merge.append(audio_link)
else:
errors.append(f"API برای گوینده {speaker_number} لینک معتبری برنگرداند: '{audio_link}'")
except requests.exceptions.RequestException as e:
errors.append(f"خطا در ارتباط با Talkbot API برای گوینده {speaker_number}: {e}")
except Exception as e:
errors.append(f"خطای غیرمنتظره در پردازش Talkbot API برای گوینده {speaker_number}: {e}")
else:
if line.strip(): # Only add error if line is not empty
errors.append(f"فرمت نامعتبر در خط: '{line}'. انتظار می‌رود (شماره)متن.")
if not audio_urls_to_merge:
# Return specific errors if any occurred, otherwise a generic message
return None, "هیچ فایل صوتی برای ادغام تولید نشد." + ("\n" + "\n".join(errors) if errors else "")
# ادغام فایل‌های صوتی تولید شده برای ساخت پادکست اصلی (دیالوگ‌ها)
podcast_audio_path, merge_message = merge_audio_files(audio_urls_to_merge)
if not podcast_audio_path:
return None, merge_message
# بارگذاری پادکست اصلی (دیالوگ‌ها) به عنوان AudioSegment
try:
podcast_audio = AudioSegment.from_file(podcast_audio_path)
except Exception as e:
# Clean up the temporary merged file if it exists
if os.path.exists(podcast_audio_path):
os.remove(podcast_audio_path)
return None, f"خطا در بارگذاری پادکست اصلی از مسیر موقت ({podcast_audio_path}): {e}"
finally:
# Always try to remove the temporary podcast_audio_path file
if os.path.exists(podcast_audio_path):
try:
os.remove(podcast_audio_path)
except OSError as e:
print(f"Error removing temporary podcast audio file {podcast_audio_path}: {e}")
# افزودن افکت‌های صوتی (اینترو، اوترو، پس‌زمینه)
final_audio, error = add_intro_outro_and_background(
podcast_audio,
intro_audio_url="https://suprime.ir/example/effect-podcast/wk.mp3", # Intro audio URL
background_audio_url="https://suprime.ir/example/effect-podcast/bk2.mp3", # Background music URL
outro_audio_url="https://suprime.ir/example/effect-podcast/1.mp3" # Outro audio URL
)
if not final_audio:
return None, error
# ذخیره فایل نهایی
output_filename = os.path.join(TEMP_DIR, f"final_podcast_{uuid.uuid4()}.mp3")
try:
final_audio.export(output_filename, format="mp3")
return output_filename, "پادکست با افکت‌های صوتی با موفقیت تولید شد!"
except Exception as e:
return None, f"خطا در ذخیره پادکست نهایی: {e}"
# ایجاد رابط کاربری Gradio
with gr.Blocks() as demo:
gr.Markdown(
"""
# ابزار ادغام فایل‌های صوتی و تولید پادکست از متن
در اینجا می‌توانید فایل‌های صوتی را ادغام کنید یا از متن با Talkbot API پادکست بسازید.
**بخش‌ها:**
1. **ادغام فایل‌های صوتی موجود:** چندین لینک یا مسیر فایل صوتی را وارد کرده و آن‌ها را ادغام کنید.
2. **تولید پادکست از متن با Talkbot API و افکت‌های صوتی:** متنی با فرمت گوینده (مثال: `(1)سلام`) را وارد کنید تا به پادکست تبدیل شود. این پادکست شامل افکت‌های صوتی (اینترو، پس‌زمینه، اوترو) با تنظیم بلندی صدای پس‌زمینه خواهد بود.
"""
)
with gr.Tab("ادغام فایل‌های صوتی موجود"):
gr.Markdown("## ادغام فایل‌های صوتی موجود (از لینک یا فایل محلی)")
audio_links_input = gr.Textbox(
label="لینک یا مسیر فایل‌های صوتی (هر کدام در یک خط جدید)",
placeholder="مثال:\nhttps://example.com/audio1.mp3\n./local_audio.wav\nhttps://example.com/audio2.wav",
lines=10
)
audio_merge_output_message = gr.Textbox(label="پیام", interactive=False)
audio_merge_output_audio = gr.Audio(label="فایل صوتی ادغام شده", type="filepath")
merge_button = gr.Button("ادغام فایل‌های صوتی")
merge_button.click(
fn=lambda x: merge_audio_files([s.strip() for s in x.split('\n') if s.strip()]),
inputs=[audio_links_input],
outputs=[audio_merge_output_audio, audio_merge_output_message]
)
# Examples for audio merging
gr.Examples(
examples=[
["https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3\nhttps://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3"],
],
inputs=audio_links_input,
label="نمونه‌ها"
)
with gr.Tab("تولید پادکست از متن (Talkbot API) و افکت‌های صوتی"):
gr.Markdown("## تولید پادکست با Talks (Talkbot API) و افکت‌های صوتی")
tts_text_input = gr.Textbox(
label="متن برای تولید پادکست (فرمت: (شماره)متن - هر پرسوناژ در یک خط جدید)",
placeholder="(1)سلام این تست صحبت اولین نفر است.\n(2)سلام، بله این هم یک تست است و من کاراکتر دوم هستم.\n(1)خب از کجا شروع کنیم\n(2)بهتره از اول شروع کنیم",
lines=10
)
tts_output_message = gr.Textbox(label="پیام", interactive=False)
tts_output_audio = gr.Audio(label="فایل پادکست تولید شده", type="filepath")
tts_merge_button = gr.Button("تولید پادکست")
tts_merge_button.click(
fn=tts_and_merge_with_effects,
inputs=[tts_text_input],
outputs=[tts_output_audio, tts_output_message]
)
# Examples for TTS
gr.Examples(
examples=[
["(1)سلام این تست صحبت اولین نفر است.\n(2)سلام، بله این هم یک تست است و من کاراکتر دوم هستم."],
["(1)امروز هوا چطوره؟\n(2)فکر کنم آفتابیه."]
],
inputs=tts_text_input,
label="نمونه‌ها"
)
if __name__ == "__main__":
demo.launch() # برای اجرا در لوکال
# demo.launch(share=True) # برای اشتراک‌گذاری موقت در یک لینک عمومی