| 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 uuid |
| from google import genai |
| from datetime import datetime, date |
| from PIL import Image, ImageDraw, ImageFont |
|
|
| |
| |
| |
| MAX_SUBTITLE_DURATION_SECONDS = 7.0 |
|
|
| |
| ACCOUNTS = { |
| "user1": "123123", |
| "admin": "admin789" |
| } |
|
|
| EXPIRY_DATE_STR = "2026-8-31" |
|
|
| |
| USER_LIMIT_TRACKER = {} |
|
|
| 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(): |
| try: |
| expiry_date = datetime.strptime(EXPIRY_DATE_STR, "%Y-%m-%d").date() |
| current_date = datetime.now().date() |
| if current_date > expiry_date: |
| return False, f"❌ ဤ App သည် သက်တမ်းကုန်ဆုံးသွားပါပြီ ({EXPIRY_DATE_STR})။" |
| return True, "Active" |
| except Exception as e: |
| return False, f"လုံခြုံရေး စစ်ဆေးမှု မှားယွင်းနေပါသည်- {str(e)}" |
|
|
| print("Loading Multilingual Faster-Whisper Base Model...") |
| model = WhisperModel("base", device="cpu", compute_type="int8", cpu_threads=4) |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| def google_backup_translate(text, source_lang="en"): |
| from urllib.parse import quote |
| import urllib.request |
| import json |
| 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 စနစ်ဖြင့် ဘာသာပြန်နေပါသည်။ (Source Lang: {source_lang})") |
| 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"⚠️ User Gemini API Error သို့မဟုတ် Format 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 |
|
|
| |
| |
| |
| 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): |
| 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: loop = asyncio.get_event_loop() |
| except RuntimeError: |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| |
| if loop.is_running(): |
| import threading |
| t = threading.Thread(target=lambda: asyncio.run(_async_gen())) |
| t.start() |
| t.join() |
| else: |
| loop.run_until_complete(_async_gen()) |
|
|
| |
| |
| |
| 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, tone_style, |
| text_color, stroke_color, blur_y_percent, blur_strength, sub_pos_percent, desired_speed, |
| session_id, |
| request: gr.Request, |
| 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 |
| |
| |
| login_username = request.username if (request and hasattr(request, 'username')) else "user1" |
| |
| if login_username == "admin": |
| user_identifier = "admin" |
| else: |
| |
| user_identifier = f"session_{session_id}" |
|
|
| current_timestamp = time.time() |
| print(f"👤 အသုံးပြုသူ Account: {login_username} | Identifier: {user_identifier}") |
|
|
| |
| if user_identifier != "admin": |
| if user_identifier in USER_LIMIT_TRACKER: |
| user_data = USER_LIMIT_TRACKER[user_identifier] |
| first_time = user_data["first_time"] |
| count = user_data["count"] |
| elapsed_time = current_timestamp - first_time |
|
|
| if elapsed_time < 86400: |
| if count >= 2: |
| remaining_seconds = 86400 - elapsed_time |
| rem_hours = int(remaining_seconds // 3600) |
| rem_mins = int((remaining_seconds % 3600) // 60) |
| raise gr.Error(f"❌ ခွင့်ပြုချက်ကျော်လွန်နေပါသည်။ သင့် Browser မှ တစ်ရက်လျှင် ဗီဒီယို (၂) ပုဒ်သာ ထုတ်ယူခွင့်ရှိသည်။ ပြန်လည်စမ်းသပ်ရန် {rem_hours} နာရီ {rem_mins} မိနစ် လိုအပ်ပါသေးသည်။") |
| else: |
| USER_LIMIT_TRACKER[user_identifier] = {"count": 0, "first_time": current_timestamp} |
| else: |
| USER_LIMIT_TRACKER[user_identifier] = {"count": 0, "first_time": current_timestamp} |
|
|
| 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 ({detected_lang}) စနစ်ဖြင့် ဘာသာပြန်နေပါသည်...") |
| else: |
| progress(0.30, desc=f"🌐 Google Translate Backup ({detected_lang}) စနစ်ဖြင့် ဘာသာပြန်နေပါသည်...") |
| |
| 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) |
| |
| 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" |
| font_primary = ImageFont.truetype(font_path, font_size) if os.path.exists(font_path) else 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) |
| |
| |
| if user_identifier != "admin": |
| USER_LIMIT_TRACKER[user_identifier]["count"] += 1 |
| print(f"✅ [{user_identifier}] ဗီဒီယိုထုတ်ယူမှု အောင်မြင်ပါသည်။ ယနေ့ ထုတ်ယူပြီးစီးမှု: {USER_LIMIT_TRACKER[user_identifier]['count']} ကြိမ်") |
|
|
| return output_video_path |
| |
| except Exception as e: |
| if os.path.exists(temp_dir): shutil.rmtree(temp_dir) |
| raise gr.Error(f"❌ အမှားအယွင်း တစ်ခု ဖြစ်ပွားခဲ့သည်- {str(e)}") |
|
|
| |
| |
| |
| def create_main_app_interface(): |
| is_valid, msg = check_app_expiry() |
| |
| with gr.Blocks() as main_app: |
| |
| session_id_state = gr.State(lambda: str(uuid.uuid4())) |
| |
| gr.Markdown("<h1 style='text-align: center; color: #5B50F3;'>✨ Video Auto Recap (Multi-Account) ✨</h1>") |
| if not is_valid: |
| gr.Markdown(f"### {msg}") |
| return main_app |
| |
| gr.Markdown("<div style='background-color: #333; padding: 10px; border-radius: 8px; text-align: center; color: #fff;'>📢 <b>Free App Rules:</b> တစ်ရက်လျှင် Video (၂) ပုဒ် အခမဲ့ ထုတ်ယူခွင့် ရှိပါသည်</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 Translate ဖြင့် လုပ်ဆောင်မည်)", placeholder="AIZAcy... (Optional)", 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_select = gr.Radio(choices=["🧕 မိန်းကလေး (Female)", "👨 ယောကျာ်လေး (Male)"], value="🧕 မိန်းကလေး (Female)", label="AI အသံ အမျိုးအစား") |
| 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, tone_style, text_color_input, |
| stroke_color_input, blur_y_percent, blur_strength, sub_pos_percent, desired_speed, |
| session_id_state |
| ], |
| outputs=[output_video] |
| ) |
| |
| return main_app |
|
|
| def verify_member(username, password): |
| if username in ACCOUNTS and ACCOUNTS[username] == password: |
| return True |
| return False |
|
|
| demo_app = create_main_app_interface() |
|
|
| if __name__ == "__main__": |
| demo_app.launch(auth=verify_member, theme=gr.themes.Default()) |