File size: 28,586 Bytes
d6242f8
 
 
 
 
 
 
 
 
 
 
 
39188aa
d6242f8
 
 
 
39188aa
d6242f8
 
 
94801ab
d6242f8
 
 
39188aa
d6242f8
94801ab
4a163ab
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39188aa
d6242f8
94801ab
d6242f8
 
 
94801ab
d6242f8
 
 
 
 
 
 
b9959fd
 
 
 
39188aa
d6242f8
94801ab
b9959fd
d6242f8
 
 
 
 
94801ab
d6242f8
 
 
 
 
 
 
 
 
 
b9959fd
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39188aa
d6242f8
 
 
 
 
 
94801ab
d6242f8
b9959fd
d6242f8
 
39188aa
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39188aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
39188aa
 
 
 
d6242f8
 
39188aa
 
d6242f8
 
39188aa
d6242f8
 
b9959fd
39188aa
d6242f8
 
 
 
 
 
6f26852
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94801ab
b9959fd
39188aa
b9959fd
39188aa
b9959fd
94801ab
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39188aa
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39188aa
 
 
 
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a163ab
 
 
 
 
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a163ab
d6242f8
 
 
 
 
 
622eda2
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9959fd
d6242f8
 
622eda2
39188aa
 
d6242f8
 
 
 
 
 
 
39188aa
d6242f8
 
 
 
 
39188aa
 
d6242f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7dc4cc5
d6242f8
 
 
 
 
 
 
 
 
 
a9d7368
 
39188aa
 
a9d7368
d6242f8
 
 
 
 
 
 
 
39188aa
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
import gradio as gr
from faster_whisper import WhisperModel
import os
import subprocess
import cv2
import asyncio
import edge_tts
import shutil
import time
import numpy as np
import re
import json
import threading
from google import genai
from PIL import Image, ImageDraw, ImageFont

# =====================================================================
# ⚙️ SETTINGS & CONFIG (Login & Limits Removed)
# =====================================================================
MAX_SUBTITLE_DURATION_SECONDS = 7.0

SYSTEM_INSTRUCTION = "You are a professional movie recap writer. Translate movie subtitle lines into natural, engaging, and thrilling Burmese movie recap style. Keep it concise."
MODEL_NAME = "gemini-2.5-flash"

def check_app_expiry():
    return True, "Active"

print("Loading Multilingual Faster-Whisper Base Model...")
model = WhisperModel("base", device="cpu", compute_type="int8", cpu_threads=4)

# =====================================================================
# 🖼️ REAL-TIME INTERACTIVE PREVIEW GENERATOR
# =====================================================================
def update_preview_image(video_path, blur_y_percent, blur_strength):
    if not video_path: return None
    try:
        cap = cv2.VideoCapture(video_path)
        ret, frame = cap.read()
        cap.release()
        if not ret: return None
            
        h_o, w_o, _ = frame.shape
        b_h = int(h_o * 0.12)
        b_y = max(0, min(int(h_o * (blur_y_percent / 100)) - (b_h // 2), h_o - b_h))
        
        k_size = int(blur_strength)
        if k_size % 2 == 0: k_size += 1
            
        preview_frame = frame.copy()
        
        roi = preview_frame[b_y:b_y+b_h, 0:w_o]
        if roi.shape[0] > 0 and roi.shape[1] > 0:
            small_roi = cv2.resize(roi, (w_o // 4, b_h // 4), interpolation=cv2.INTER_LINEAR)
            blurred_small = cv2.GaussianBlur(small_roi, (k_size // 4 | 1, k_size // 4 | 1), 0)
            preview_frame[b_y:b_y+b_h, 0:w_o] = cv2.resize(blurred_small, (w_o, b_h), interpolation=cv2.INTER_LINEAR)
            cv2.rectangle(preview_frame, (0, b_y), (w_o, b_y+b_h), (0, 0, 255), 3)
            
        preview_rgb = cv2.cvtColor(preview_frame, cv2.COLOR_BGR2RGB)
        return Image.fromarray(preview_rgb)
    except Exception as e:
        print(f"Preview Error: {e}")
        return None

# =====================================================================
# ⚡ TRANSLATION ENGINE
# =====================================================================
def google_backup_translate(text, source_lang="en"):
    from urllib.parse import quote
    import urllib.request
    try:
        url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl={source_lang}&tl=my&dt=t&q={quote(text)}"
        req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        response = urllib.request.urlopen(req, timeout=5).read().decode('utf-8')
        result = json.loads(response)
        return result[0][0][0] or text
    except:
        return text

def translate_segments_batch(segments, user_api_key, source_lang="en"):
    if not segments: return segments
    
    if not user_api_key or not user_api_key.strip():
        print(f"⚠️ User က Gemini API Key မထည့်ထားပါ။ Google Translate Backup စနစ်ဖြင့် ဘာသာပြန်နေပါသည်။")
        for seg in segments:
            seg['mm_text'] = google_backup_translate(seg['text'], source_lang=source_lang)
        return segments

    payload_dict = {str(idx): seg['text'] for idx, seg in enumerate(segments)}
    large_prompt_text = json.dumps(payload_dict, ensure_ascii=False, indent=2)

    prompt = f"""
    You are an expert movie recap translator. Translate the following movie subtitle lines (which are originally in '{source_lang}' language) into thrilling, natural, and engaging Burmese movie recap style.
    
    CRITICAL: You MUST respond in valid JSON format only, keeping the exact same keys (0, 1, 2, etc.) as the input. The values should be the translated Burmese text.
    Do NOT include any markdown formatting like ```json or ``` in your response. Respond with pure JSON raw string only.
    
    Input Data:
    {large_prompt_text}
    """
    
    translated_map = {}
    try:
        client = genai.Client(api_key=user_api_key.strip())
        response = client.models.generate_content(
            model=MODEL_NAME,
            contents=prompt,
            config={
                "system_instruction": SYSTEM_INSTRUCTION,
                "temperature": 0.3,
            }
        )
        response_text = response.text.strip()
        
        if response_text.startswith("```"):
            response_text = response_text.split("\n", 1)[1].rsplit("\n", 1)[0].strip()
        if response_text.startswith("json"):
            response_text = response_text.split("\n", 1)[1].strip()
            
        translated_map = json.loads(response_text)
    except Exception as e:
        print(f"⚠️ Gemini API Error: {e} -> Google Translate သို့ ပြောင်းလဲနေသည်။")

    for idx, seg in enumerate(segments):
        key_str = str(idx)
        if key_str in translated_map and translated_map[key_str]:
            seg['mm_text'] = translated_map[key_str]
        else:
            seg['mm_text'] = google_backup_translate(seg['text'], source_lang=source_lang)
            
    return segments

# =====================================================================
# 🎬 TEXT WRAPPING & VOICE GENERATION SYSTEMS
# =====================================================================
def segment_myanmar_syllables(text):
    return re.findall(r'[a-zA-Z0-9\s\-\.,!\?]+|[\u1000-\u102a\u103f\u1040-\u1049]+[\u102b-\u103e\u1060-\u109f]*|[^\s]', text)

def wrap_text_myanmar_smart(text, font, max_width, draw):
    cleaned_text = text.replace(" ြ", "ြ").replace("ြ ", "ြ").strip()
    tokens = segment_myanmar_syllables(cleaned_text)
    lines, current_line = [], ""
    for token in tokens:
        test_line = current_line + token
        bbox = draw.textbbox((0, 0), test_line, font=font)
        if (bbox[2] - bbox[0]) <= max_width:
            current_line = test_line
        else:
            if current_line: lines.append(current_line.strip())
            current_line = token
    if current_line: lines.append(current_line.strip())
    return lines

def hex_to_rgb(hex_str):
    if not hex_str: return (255, 255, 0)
    hex_str = hex_str.lstrip('#')
    return tuple(int(hex_str[i:i+2], 16) for i in (0, 2, 4))

def draw_line_perfect_rendering(draw, position, text, font_primary, fill_color, stroke_w, stroke_c):
    x, y = position
    clean_text = text.replace(" ြ", "ြ").replace("ြ ", "ြ")
    draw.text((x, y), clean_text, font=font_primary, fill=fill_color, stroke_width=stroke_w, stroke_fill=stroke_c)

def generate_voice_sync(text, voice_id, filename, desired_speed, target_duration_sec=None, user_api_key=None, voice_engine="Edge-TTS"):
    # Google AI Studio Audio (Gemini Native Audio) အသုံးပြုလိုပါက
    if voice_engine == "Google AI Studio Audio" and user_api_key and user_api_key.strip():
        try:
            client = genai.Client(api_key=user_api_key.strip())
            prompt = f"Read the following Burmese movie recap text naturally and clearly with an engaging narrative tone. Generate audio output:\n{text}"
            response = client.models.generate_content(
                model=MODEL_NAME,
                contents=prompt,
                config={
                    "response_mime_type": "audio/mp3",
                }
            )
            audio_saved = False
            for candidate in response.candidates:
                for part in candidate.content.parts:
                    if hasattr(part, 'inline_data') and part.inline_data:
                        with open(filename, "wb") as f:
                            f.write(part.inline_data.data)
                        audio_saved = True
                        break
                if audio_saved: break
            if audio_saved and os.path.exists(filename) and os.path.getsize(filename) > 0:
                return
        except Exception as e:
            print(f"⚠️ Google AI Studio Audio Error: {e} -> Edge-TTS သို့ အလိုအလျောက် ပြောင်းလဲနေသည်။")

    # Default: Edge-TTS
    rate_percentage = int((desired_speed - 1.0) * 100)
    rate_str = f"{'+' if rate_percentage >= 0 else ''}{rate_percentage}%"
    
    if target_duration_sec and target_duration_sec > 0:
        char_count = len(text)
        cps = char_count / target_duration_sec
        if cps > 15: rate_str = f"+{rate_percentage + 30}%"
        elif cps > 11: rate_str = f"+{rate_percentage + 15}%"
        elif cps > 7: rate_str = f"+{rate_percentage + 5}%"
            
    async def _async_gen():
        communicate = edge_tts.Communicate(text, voice_id, rate=rate_str)
        await communicate.save(filename)
    
    try:
        def run_in_thread():
            asyncio.run(_async_gen())
        t = threading.Thread(target=run_in_thread)
        t.start()
        t.join()
    except Exception as e:
        print(f"Audio Generation Error: {e}")

# =====================================================================
# 🎬 PRODUCTION AUTOMATION ENGINE (Limits Removed)
# =====================================================================
def process_magic_recap_video(
    video_path, user_api_key, ratio_select, background_fill, enable_zoom, zoom_level,
    logo_file, mirror_flip, filter_color, voice_gender, voice_engine_select, tone_style,
    text_color, stroke_color, blur_y_percent, blur_strength, sub_pos_percent, desired_speed,
    progress=gr.Progress(track_tqdm=True)
):
    is_valid, msg = check_app_expiry()
    if not is_valid: raise gr.Error(msg)
    if not video_path: return None

    temp_dir = "temp_space_workspace"
    if os.path.exists(temp_dir): shutil.rmtree(temp_dir)
    os.makedirs(temp_dir, exist_ok=True)
    
    try:
        progress(0.10, desc="🎙️ Faster-Whisper ဖြင့် စာသားဖတ်နေပါသည်...")
        segments_raw, info = model.transcribe(video_path, beam_size=1) 
        raw_segments = [{"start": seg.start, "end": seg.end, "text": seg.text} for seg in segments_raw]
        
        cap = cv2.VideoCapture(video_path)
        fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
        orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        video_duration = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) / fps
        
        if not raw_segments:
            raw_segments = [{'start': 0.0, 'end': min(6.0, video_duration), 'text': "Welcome to this movie recap."}]

        segments = []
        for seg in raw_segments:
            s_start = seg['start']
            s_end = seg['end']
            s_text = seg['text'].strip()
            if not s_text: continue
            
            dur = s_end - s_start
            if dur > MAX_SUBTITLE_DURATION_SECONDS and MAX_SUBTITLE_DURATION_SECONDS > 0:
                words = s_text.split()
                chunks_count = int(np.ceil(dur / MAX_SUBTITLE_DURATION_SECONDS))
                words_per_chunk = int(np.ceil(len(words) / chunks_count))
                for i in range(chunks_count):
                    w_sub = words[i*words_per_chunk : (i+1)*words_per_chunk]
                    if not w_sub: continue
                    segments.append({'start': s_start + (i * (dur / chunks_count)), 'end': min(s_end, s_start + ((i+1) * (dur / chunks_count))), 'text': " ".join(w_sub)})
            else:
                segments.append({'start': s_start, 'end': s_end, 'text': s_text})

        detected_lang = info.language
        if user_api_key and user_api_key.strip():
            progress(0.30, desc=f"⚡ ထည့်သွင်းထားသော Gemini API စနစ်ဖြင့် ဘာသာပြန်နေပါသည်...")
        else:
            progress(0.30, desc=f"🌐 Google Translate Backup စနစ်ဖြင့် ဘာသာပြန်နေပါသည်...")
            
        segments = translate_segments_batch(segments, user_api_key, source_lang=detected_lang)

        if ratio_select == "9:16 (Tiktok/Reels)": target_w, target_h = 720, 1280
        else: target_w, target_h = 1280, 720

        logo_img = None
        if logo_file:
            try:
                logo_cv = cv2.imread(logo_file.name, cv2.IMREAD_UNCHANGED)
                if logo_cv is not None:
                    l_w = int(target_w * 0.16)
                    logo_img = cv2.resize(logo_cv, (l_w, int(l_w * (logo_cv.shape[0] / logo_cv.shape[1]))))
            except: pass

        progress(0.50, desc="🎙️ AI အသံများ စတင်ဖန်တီးနေပါသည်...")
        audio_segments = []
        python_srt_segments = []
        voice_id = "my-MM-NilarNeural" if "မိန်းကလေး" in voice_gender else "my-MM-ThihaNeural"
        v_segments_time_map = []
        total_adjusted_duration = 0.0

        for idx, seg in enumerate(segments):
            mm_text = seg.get('mm_text', seg['text'])
            mm_text = mm_text.replace(" ြ", "ြ").replace("ြ ", "ြ").strip()
            
            orig_start, orig_end = float(seg.get('start', 0.0)), float(seg.get('end', 0.0))
            orig_dur = orig_end - orig_start if (orig_end - orig_start) > 0 else 2.0
            
            raw_seg_filename = os.path.join(temp_dir, f"raw_{idx}.mp3")
            fixed_seg_filename = os.path.join(temp_dir, f"fixed_{idx}.mp3")
            
            generate_voice_sync(mm_text, voice_id, raw_seg_filename, 1.15, orig_dur, user_api_key=user_api_key, voice_engine=voice_engine_select)
            
            if os.path.exists(raw_seg_filename) and os.path.getsize(raw_seg_filename) > 0:
                subprocess.run([
                    'ffmpeg', '-y', '-i', raw_seg_filename, 
                    '-filter:a', f"atempo={desired_speed}", 
                    fixed_seg_filename
                ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            
            if os.path.exists(fixed_seg_filename) and os.path.getsize(fixed_seg_filename) > 0:
                probe_res = subprocess.run(['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', fixed_seg_filename], stdout=subprocess.PIPE, text=True)
                try: audio_dur = float(probe_res.stdout.strip())
                except: audio_dur = orig_dur / desired_speed

                python_srt_segments.append({'start': total_adjusted_duration, 'end': total_adjusted_duration + audio_dur, 'text': mm_text})
                audio_segments.append(fixed_seg_filename)
                v_segments_time_map.append({'orig_start': orig_start, 'orig_end': orig_end, 'new_start': total_adjusted_duration, 'new_end': total_adjusted_duration + audio_dur, 'pts_ratio': audio_dur / orig_dur})
                total_adjusted_duration += audio_dur

        progress(0.70, desc="⚡ Render ဗီဒီယိုနှင့် စာတန်းထိုးများ ပေါင်းစပ်နေပါသည်...")
        output_video_path = os.path.abspath("magic_recap_output.mp4")
        if os.path.exists(output_video_path): os.remove(output_video_path)
            
        final_burn_temp = os.path.join(temp_dir, "final_burn_temp.mp4")
        video_writer = cv2.VideoWriter(final_burn_temp, cv2.VideoWriter_fourcc(*'mp4v'), fps, (target_w, target_h))
        
        font_size = int(target_h * 0.038)
        font_path = "Myanmar font.ttf"
        if os.path.exists(font_path):
            font_primary = ImageFont.truetype(font_path, font_size)
        else:
            font_primary = ImageFont.load_default()

        t_color, s_color = hex_to_rgb(text_color), hex_to_rgb(stroke_color)
        b_h = int(target_h * 0.12)  
        b_y = max(0, min(int(target_h * (blur_y_percent / 100)) - (b_h // 2), target_h - b_h))
        m_w, s_w = int(target_w * 0.90), max(2, int(font_size * 0.12))
        k_size = int(blur_strength) | 1

        total_output_frames = int(total_adjusted_duration * fps)

        for f_out_idx in range(total_output_frames):
            c_sec = f_out_idx / fps
            target_orig_sec = 0.0
            for mapping in v_segments_time_map:
                if mapping['new_start'] <= c_sec <= mapping['new_end']:
                    target_orig_sec = mapping['orig_start'] + ((c_sec - mapping['new_start']) / mapping['pts_ratio'])
                    break
            else:
                if v_segments_time_map: target_orig_sec = v_segments_time_map[-1]['orig_end']
            
            target_frame_idx = int(target_orig_sec * fps)
            
            cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame_idx)
            ret, orig_frame = cap.read()
            if not ret or orig_frame is None:
                orig_frame = np.zeros((orig_h, orig_w, 3), dtype=np.uint8)
            
            if background_fill == "Blur Background (အနောက်ခံ ဝါးမည်)":
                small_bg = cv2.resize(orig_frame, (target_w // 4, target_h // 4), interpolation=cv2.INTER_LINEAR)
                blurred_small_bg = cv2.blur(small_bg, (11, 11))
                bg_layer = cv2.resize(blurred_small_bg, (target_w, target_h), interpolation=cv2.INTER_LINEAR)
            else:
                bg_layer = np.zeros((target_h, target_w, 3), dtype=np.uint8)

            if enable_zoom and zoom_level > 1.0:
                fg_cropped = orig_frame[int((orig_h - orig_h/zoom_level)//2):int((orig_h + orig_h/zoom_level)//2), int((orig_w - orig_w/zoom_level)//2):int((orig_w + orig_w/zoom_level)//2)]
            else:
                fg_cropped = orig_frame

            fg_w = target_w
            fg_h = int(fg_w / (fg_cropped.shape[1] / fg_cropped.shape[0]))
            if fg_h > target_h:
                fg_h = target_h
                fg_w = int(fg_h * (fg_cropped.shape[1] / fg_cropped.shape[0]))
                
            fg_resized = cv2.flip(cv2.resize(fg_cropped, (fg_w, fg_h)), 1) if mirror_flip else cv2.resize(fg_cropped, (fg_w, fg_h))

            if filter_color == "Chrome Cool": fg_resized = cv2.convertScaleAbs(fg_resized, alpha=1.0, beta=15)
            elif filter_color == "Warm Cinema": fg_resized = cv2.convertScaleAbs(fg_resized, alpha=1.05, beta=5)
            else: fg_resized = cv2.convertScaleAbs(fg_resized, alpha=0.99, beta=2) 

            bg_layer[(target_h - fg_h)//2:(target_h - fg_h)//2+fg_h, (target_w - fg_w)//2:(target_w - fg_w)//2+fg_w] = fg_resized
            frame = bg_layer

            if logo_img is not None:
                ly, lx = 25, target_w - logo_img.shape[1] - 25
                if logo_img.shape[2] == 4:
                    alpha_l = logo_img[:, :, 3] / 255.0
                    for c in range(3): frame[ly:ly+logo_img.shape[0], lx:lx+logo_img.shape[1], c] = alpha_l * logo_img[:, :, c] + (1.0 - alpha_l) * frame[ly:ly+logo_img.shape[0], lx:lx+logo_img.shape[1], c]
                else: frame[ly:ly+logo_img.shape[0], lx:lx+logo_img.shape[1]] = logo_img[:, :, :3]

            if b_h > 0 and (b_y + b_h) <= target_h:
                roi = frame[b_y:b_y+b_h, 0:target_w]
                if roi.shape[0] > 0 and roi.shape[1] > 0:
                    roi_small = cv2.resize(roi, (target_w // 4, b_h // 4), interpolation=cv2.INTER_LINEAR)
                    roi_blur = cv2.GaussianBlur(roi_small, (k_size // 4 | 1, k_size // 4 | 1), 0)
                    frame[b_y:b_y+b_h, 0:target_w] = cv2.resize(roi_blur, (target_w, b_h), interpolation=cv2.INTER_LINEAR)

            text_str = ""
            for s in python_srt_segments:
                if s['start'] <= c_sec <= s['end']:
                    text_str = s['text']
                    break

            if text_str and isinstance(font_primary, ImageFont.FreeTypeFont):
                pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
                draw = ImageDraw.Draw(pil_img)
                sub_lines = wrap_text_myanmar_smart(text_str, font_primary, m_w, draw)
                total_text_height = sum([draw.textbbox((0, 0), l, font=font_primary)[3] - draw.textbbox((0, 0), l, font=font_primary)[1] for l in sub_lines])
                curr_y = int(target_h - total_text_height - (target_h * (sub_pos_percent / 100)))
                for line in sub_lines:
                    clean_line = line.replace(" ြ", "ြ").replace("ြ ", "ြ")
                    bbox = draw.textbbox((0, 0), clean_line, font=font_primary)
                    draw_line_perfect_rendering(draw, ((target_w - (bbox[2] - bbox[0])) // 2, curr_y), clean_line, font_primary, t_color, s_w, s_color)
                    curr_y += (bbox[3] - bbox[1]) + 10
                frame = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
            video_writer.write(frame)
            
        cap.release()
        video_writer.release()

        merged_audio_path = os.path.join(temp_dir, "final_speech_track.mp3")
        if audio_segments:
            inputs_cmd = []
            for idx, audio_file in enumerate(audio_segments): inputs_cmd.extend(['-i', audio_file])
            subprocess.run(['ffmpeg', '-y'] + inputs_cmd + ['-filter_complex', f"concat=n={len(audio_segments)}:v=0:a=1[outa]", '-map', '[outa]', '-c:a', 'libmp3lame', '-b:a', '192k', merged_audio_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            has_audio = os.path.exists(merged_audio_path) and os.path.getsize(merged_audio_path) > 0
        else: has_audio = False

        progress(0.90, desc="⚡ ရုပ်သံနှင့် အသံလှိုင်းများကို အပြီးသတ် ပေါင်းစပ်နေပါသည်...")
        if has_audio:
            subprocess.run(['ffmpeg', '-y', '-i', final_burn_temp, '-i', merged_audio_path, '-c:v', 'libx264', '-preset', 'ultrafast', '-threads', '0', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-b:a', '192k', '-map', '0:v:0', '-map', '1:a:0', '-shortest', output_video_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        else:
            subprocess.run(['ffmpeg', '-y', '-i', final_burn_temp, '-c:v', 'libx264', '-preset', 'ultrafast', '-threads', '0', '-pix_fmt', 'yuv420p', output_video_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        
        if os.path.exists(temp_dir): shutil.rmtree(temp_dir)
        return output_video_path
        
    except Exception as e:
        if os.path.exists(temp_dir): shutil.rmtree(temp_dir)
        raise gr.Error(f"❌ အမှားအယွင်း တစ်ခု ဖြစ်ပွားခဲ့သည်- {str(e)}")

# =====================================================================
# 🎨 GRADIO INTERFACE
# =====================================================================
def create_main_app_interface():
    with gr.Blocks() as main_app:
        gr.Markdown("<h1 style='text-align: center; color: #5B50F3;'>✨ Video Auto Recap (Unlimited & AI Audio) ✨</h1>")
        gr.Markdown("<div style='background-color: #222; padding: 10px; border-radius: 8px; text-align: center; color: #fff;'>📢 <b>Notice:</b> Login စနစ်နှင့် ကန့်သတ်ချက်များ (Limits) အားလုံး ဖြုတ်ထားပြီးဖြစ်ပါသည်။ Google AI Studio Audio (သို့) Edge-TTS ကို ရွေးချယ်အသုံးပြုနိုင်ပါသည်။</div>")
        
        with gr.Row():
            with gr.Column(scale=2):
                video_input = gr.Video(label="🎥 ဗီဒီယို ထည့်ရန်", sources=["upload"])
                preview_image = gr.Image(label="🖼️ Interactive Blur Preview", interactive=False)
            
            with gr.Column(scale=1):
                user_api_key = gr.Textbox(label="🔑 သင်၏ Gemini API Key ထည့်ရန် (ဘာသာပြန်နှင့် Google AI Studio အသံအတွက် လိုအပ်သည်)", placeholder="AIZAcy...", type="password")
                ratio_select = gr.Dropdown(choices=["9:16 (Tiktok/Reels)", "16:9 (Landscape)"], value="9:16 (Tiktok/Reels)", label="ဗီဒီယိုအမျိုးအစား")
                background_fill = gr.Radio(choices=["Blur Background (အနောက်ခံ ဝါးမည်)", "Black Background (အမည်းရောင်ထားမည်)"], value="Blur Background (အနောက်ခံ ဝါးမည်)", label="နောက်ခံ ဖြည့်စွက်မှု")

        with gr.Column(variant="panel"):
            gr.Markdown("### 🗣️ အသံနှင့် စာတန်းထိုး ဆက်တင်များ")
            voice_engine_select = gr.Radio(choices=["Edge-TTS", "Google AI Studio Audio"], value="Google AI Studio Audio", label="🎙️ အသံထုတ်လုပ်သည့်စနစ် (Voice Engine)")
            voice_select = gr.Radio(choices=["🧕 မိန်းကလေး (Female)", "👨 ယောကျာ်လေး (Male)"], value="🧕 မိန်းကလေး (Female)", label="AI အသံ ပုံစံ (Edge-TTS အတွက်သာ)")
            tone_style = gr.Dropdown(choices=["Thriller", "Comedy", "Dramatic", "Action/Epic"], value="Thriller", label="🎬 Narrative Tone")
            text_color_input = gr.ColorPicker(label="စာလုံးအရောင်", value="#FFFF00")
            stroke_color_input = gr.ColorPicker(label="အနားသတ်အရောင်", value="#000000")
            sub_pos_percent = gr.Slider(minimum=0, maximum=100, value=15, step=1, label="မြန်မာစာတန်း တည်နေရာ %")
            blur_y_percent = gr.Slider(minimum=50, maximum=100, value=75, step=1, label="📍 မူရင်းစာတန်းဖျောက်မည့်နေရာ %")
            blur_strength = gr.Slider(minimum=5, maximum=151, value=51, step=2, label="🌫️ မူရင်းစာတန်း ဝါးမည့်ပမာဏ")
            desired_speed = gr.Slider(minimum=1.0, maximum=1.6, value=1.35, step=0.05, label="🎙️🎬 အသံနှင့် ဗီဒီယို အရှိန်မြှင့်နှုန်း")

        with gr.Accordion("⚙️ အဆင့်မြင့် ဆက်တင်များ", open=False):
            enable_zoom = gr.Checkbox(label="Zoom & Crop သုံးရန်", value=False)
            zoom_level = gr.Slider(minimum=1.0, maximum=3.0, value=1.0, step=0.1, label="Zoom Level")
            logo_file = gr.File(label="လိုဂို ထည့်ရန် (Optional)")
            mirror_flip = gr.Checkbox(label="ဘယ်ညာ ပြောင်းရန် (Mandatory Auto-Active)", value=True)
            filter_color = gr.Dropdown(choices=["None (အလိုအလျောက်ကုဒ်ပြောင်းမည်)", "Chrome Cool", "Warm Cinema"], value="None (အလိုအလျောက်ကုဒ်ပြောင်းမည်)", label="ဗီဒီယို Filter")

        submit_btn = gr.Button("🚀 Generate Video", variant="primary")

        with gr.Column(variant="panel"):
            output_video = gr.Video(label="✅ ပြီးပြည့်စုံသော ဗီဒီယို")

        video_input.change(fn=update_preview_image, inputs=[video_input, blur_y_percent, blur_strength], outputs=preview_image)
        blur_y_percent.change(fn=update_preview_image, inputs=[video_input, blur_y_percent, blur_strength], outputs=preview_image)
        blur_strength.change(fn=update_preview_image, inputs=[video_input, blur_y_percent, blur_strength], outputs=preview_image)

        submit_btn.click(
            fn=process_magic_recap_video,
            inputs=[
                video_input, user_api_key, ratio_select, background_fill, enable_zoom, zoom_level, 
                logo_file, mirror_flip, filter_color, voice_select, voice_engine_select, tone_style, text_color_input, 
                stroke_color_input, blur_y_percent, blur_strength, sub_pos_percent, desired_speed
            ],
            outputs=[output_video]
        )
        
    return main_app

demo_app = create_main_app_interface()

if __name__ == "__main__":
    demo_app.launch(theme=gr.themes.Default())