vacc / app.py
Winhaa's picture
Create app.py
508a638 verified
Raw
History Blame Contribute Delete
12.5 kB
import gradio as gr
import asyncio
import edge_tts
import re
import os
import uuid
try:
from pydub import AudioSegment
from pydub.silence import detect_nonsilent
except ImportError:
raise ImportError("ကျေးဇူးပြု၍ requirements.txt တွင် pydub ထည့်ပေးပါ။")
def format_srt_time(seconds):
if seconds < 0: seconds = 0
millis = int(seconds * 1000)
hours = millis // 3600000
millis %= 3600000
minutes = millis // 60000
millis %= 60000
secs = millis // 1000
millis %= 1000
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
def segment_myanmar_text(raw_text, max_chars=40):
for char in ["“", "”", '"', "‘", "’", "'", "--", "—", "…"]:
raw_text = raw_text.replace(char, " ")
raw_text = re.sub(r"[ \t]+", " ", raw_text).strip()
split_pattern = re.split(r'([။၊!?\n]+)', raw_text)
temp_chunks = []
current_chunk = ""
for item in split_pattern:
current_chunk += item
if any(p in item for p in ['။', '၊', '?', '!', '\n']):
clean_chunk = current_chunk.strip()
if len(re.sub(r'[\s၊။!?\-\"\']', '', clean_chunk)) > 0:
temp_chunks.append(clean_chunk)
current_chunk = ""
if current_chunk.strip():
clean_chunk = current_chunk.strip()
if len(re.sub(r'[\s၊။!?\-\"\']', '', clean_chunk)) > 0:
temp_chunks.append(clean_chunk)
final_segments = []
for chunk in temp_chunks:
if len(chunk) <= max_chars:
final_segments.append(chunk)
else:
sub_words = chunk.split(' ')
line_buffer = ""
for word in sub_words:
if len(line_buffer) + len(word) + 1 <= max_chars:
line_buffer += (" " if line_buffer else "") + word
else:
if line_buffer: final_segments.append(line_buffer.strip())
if len(word) > max_chars:
for i in range(0, len(word), max_chars):
final_segments.append(word[i:i+max_chars])
line_buffer = ""
else:
line_buffer = word
if line_buffer:
final_segments.append(line_buffer.strip())
return final_segments
async def process_voice_generation(
text, surveyed_text, filename, s1_voice, s2_voice, s3_voice,
style, srt_type, tone, speed, volume, progress=gr.Progress()
):
if not text.strip(): return None, None, None
processed_text = text.replace("--", " ").replace("—", " ").replace("…", " ")
if surveyed_text.strip():
for line in surveyed_text.strip().split("\n"):
if "=" in line: key, val = line.split("=", 1); processed_text = processed_text.replace(key.strip(), val.strip())
# အသံမြန်နှုန်း၊ အသံအတိုးအကျယ် ချိန်ညှိချက်
base_speed = speed + 20
speed_rate = f"{'+' if base_speed >= 0 else ''}{base_speed}%"
volume_rate = f"+{volume}%"
# ဖိုင်အမည် သတ်မှတ်ခြင်း
output_name = filename.strip() if filename.strip() else "Myanmar_TTS"
output_mp3 = f"{output_name}.mp3"
output_srt = f"{output_name}.srt"
max_srt_chars = 40 if srt_type == "TikTok" else 70
sentences = segment_myanmar_text(processed_text, max_chars=max_srt_chars)
total_sentences = len(sentences)
if total_sentences == 0:
return None, None, None
progress(0.2, desc=f"⏳ စာသားများကို အသံအဖြစ် ပြောင်းလဲနေပါသည် ({total_sentences} ကြောင်း)...")
sem = asyncio.Semaphore(15)
# Multi-voice selection logic
voice_map = {
"သီဟ (🇲🇲 - ကျား)": "my-MM-ThihaNeural",
"နီလာ (🇲🇲 - မ)": "my-MM-NilarNeural",
"စမူပိုင်ကြီး (🇲🇲 - ကျား)": "my-MM-ThihaNeural"
}
async def fetch_audio(idx, sentence):
async with sem:
try:
# စာကြောင်းအလိုက် Voice ခွဲပေးခြင်း (ဥပမာ - စာကြောင်းအလိုက် Voice 1, Voice 2 လှည့်သုံးချင်ရင်)
if idx % 3 == 0:
selected_voice = voice_map.get(s1_voice, "my-MM-ThihaNeural")
elif idx % 3 == 1:
selected_voice = voice_map.get(s2_voice, "my-MM-NilarNeural")
else:
selected_voice = voice_map.get(s3_voice, "my-MM-ThihaNeural")
communicate = edge_tts.Communicate(
text=sentence, voice=selected_voice, rate=speed_rate, volume=volume_rate
)
chunk_audio = bytearray()
async for msg in communicate.stream():
if msg["type"] == "audio":
chunk_audio.extend(msg["data"])
return idx, chunk_audio
except Exception as e:
print(f"Skipping server error on sentence {idx}: {e}")
return idx, b""
tasks = [fetch_audio(idx, s) for idx, s in enumerate(sentences)]
audio_results = await asyncio.gather(*tasks)
audio_results.sort(key=lambda x: x[0])
progress(0.7, desc="⚙️ အသံနှင့် စာတန်းထိုးကို အချောသတ် ချိန်ညှိနေပါသည်...")
combined_audio = AudioSegment.empty()
subtitles = []
current_time_sec = 0.0
natural_pause = AudioSegment.silent(duration=150)
# အချင်းချင်း ဖိုင်နာမည် မထပ်အောင် User session အလိုက် random id သုံးပေးထားပါတယ်
session_id = str(uuid.uuid4())[:8]
for idx, chunk_audio in audio_results:
if not chunk_audio:
continue
temp_file = f"temp_{session_id}_{idx}.mp3"
with open(temp_file, "wb") as f:
f.write(chunk_audio)
try:
segment = AudioSegment.from_mp3(temp_file)
nonsilent_ranges = detect_nonsilent(segment, min_silence_len=50, silence_thresh=-50)
if nonsilent_ranges:
start_trim = nonsilent_ranges[0][0]
end_trim = nonsilent_ranges[-1][1]
segment = segment[start_trim:end_trim]
segment = segment + natural_pause
duration_sec = len(segment) / 1000.0
if duration_sec > 0:
end_time = current_time_sec + duration_sec
subtitles.append({
"start": current_time_sec,
"end": end_time,
"text": sentences[idx]
})
current_time_sec = end_time
combined_audio += segment
except Exception as e:
print(f"Error processing line {idx}: {e}")
finally:
if os.path.exists(temp_file):
os.remove(temp_file)
progress(0.9, desc="💾 ဖိုင်များကို သိမ်းဆည်းနေပါသည်...")
if len(combined_audio) == 0:
return None, None, None
combined_audio.export(output_mp3, format="mp3")
with open(output_srt, "w", encoding="utf-8-sig") as f:
for i, sub in enumerate(subtitles, start=1):
f.write(f"{i}\n")
f.write(f"{format_srt_time(sub['start'])} --> {format_srt_time(sub['end'])}\n")
f.write(sub["text"].strip())
f.write("\n\n")
progress(1.0, desc="✅ အားလုံး ပြီးစီးပါပြီ!")
return output_mp3, output_mp3, output_srt
def tts_wrapper(text, rules_text, filename, s1_voice, s2_voice, s3_voice, style, srt_type, tone, speed, volume, progress=gr.Progress()):
return asyncio.run(tts_wrapper_async(text, rules_text, filename, s1_voice, s2_voice, s3_voice, style, srt_type, tone, speed, volume, progress))
async def tts_wrapper_async(text, rules_text, filename, s1_voice, s2_voice, s3_voice, style, srt_type, tone, speed, volume, progress=gr.Progress()):
return await process_voice_generation(text, surveyed_text=rules_text, filename=filename, s1_voice=s1_voice, s2_voice=s2_voice, s3_voice=s3_voice, style=style, srt_type=srt_type, tone=tone, speed=speed, volume=volume, progress=progress)
custom_css = """
.large-btn { font-size: 24px !important; font-weight: bold !important; padding: 15px !important; margin-bottom: 15px !important; }
.text-link-box { text-align: center; margin-bottom: 20px; padding: 10px; background-color: #1E293B; border-radius: 8px; }
.text-link-box p { font-size: 20px !important; margin: 5px 0 !important; font-weight: 500; }
.text-link-box a { color: #38BDF8 !important; text-decoration: underline !important; font-weight: bold !important; font-size: 24px !important; }
"""
with gr.Blocks(title="Myanmar Audio Studio", css=custom_css) as demo:
gr.Markdown("<h1 style='text-align: center; color: #4F46E5;'>🎙️ Myanmar Audio & SRT Studio</h1>")
with gr.Column():
free_status_btn = gr.Button("🆓 TTS and SRT Free Version", variant="primary", interactive=False, elem_classes=["large-btn"])
gr.HTML(
"<div class='text-link-box'>"
"<p style='color: #FFFFFF;'>💬 User များစကားပြောရန် Group</p>"
"<p><a href='https://t.me/paingttsandsrt' target='_blank'>@PAINGTTSANDSRT</a></p>"
"</div>"
)
gr.Markdown("<hr style='margin: 20px 0;'>")
with gr.Group() as tts_content:
with gr.Accordion("🔧 အသံထွက် ပြင်ဆင်ရန် (Pronunciation Rules)", open=False):
rules = gr.TextArea(value="", placeholder="ဥပမာ - စာသား = အသံထွက်", lines=4, show_label=False)
file_name = gr.Textbox(label="💾 သိမ်းဆည်းမည့် ဖိုင်အမည်", value="Myanmar_TTS")
voice_choices = ["သီဟ (🇲🇲 - ကျား)", "နီလာ (🇲🇲 - မ)", "စမူပိုင်ကြီး (🇲🇲 - ကျား)"]
with gr.Row():
s1_voice = gr.Dropdown(voice_choices, label="🎙️ [S1] အသံ", value="သီဟ (🇲🇲 - ကျား)")
s2_voice = gr.Dropdown(voice_choices, label="🎙️ [S2] အသံ", value="နီလာ (🇲🇲 - မ)")
s3_voice = gr.Dropdown(voice_choices, label="🎙️ [S3] အသံ", value="စမူပိုင်ကြီး (🇲🇲 - ကျား)")
with gr.Row():
style = gr.Dropdown(["Normal (ပုံမှန်)"], label="🎭 အသံစတိုင်လ်", value="Normal (ပုံမှန်)")
srt_type = gr.Radio(["TikTok", "YouTube"], label="စာတန်းထိုး အမျိုးအစား", value="TikTok")
with gr.Accordion("⚙️ အဆင့်မြင့် ဆက်တင်များ", open=False):
tone = gr.Slider(minimum=-50, maximum=50, value=0, step=1, label="Tone")
speed = gr.Slider(minimum=-50, maximum=50, value=0, step=1, label="Speed")
volume = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="Volume (+%)")
input_text = gr.Textbox(label="စာသား", placeholder="မင်္ဂလာပါ။", lines=4)
generate_btn = gr.Button("🚀 အသံဖိုင် ဖန်တီးမည် (Generate)", variant="primary")
output_audio = gr.Audio(label="🎧 ထွက်လာသော အသံဖိုင်", type="filepath")
output_mp3_file = gr.File(label="💾 MP3 ဒေါင်းလုဒ်")
output_srt_file = gr.File(label="📝 SRT ဒေါင်းလုဒ်")
generate_btn.click(
fn=tts_wrapper,
inputs=[input_text, rules, file_name, s1_voice, s2_voice, s3_voice, style, srt_type, tone, speed, volume],
outputs=[output_audio, output_mp3_file, output_srt_file]
)
demo.launch()