File size: 12,464 Bytes
508a638
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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()