Spaces:
Running
Running
File size: 15,490 Bytes
f969cbd fb5eee4 f969cbd |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
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) # برای اشتراکگذاری موقت در یک لینک عمومی
|