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 # đ Session ID ááŻááşáááşáĄáá˝ááş Import ááŻááşááźááşá¸ from google import genai from datetime import datetime, date from PIL import Image, ImageDraw, ImageFont # ===================================================================== # âď¸ SECURITY SETTINGS & LOGIN CONFIG # ===================================================================== MAX_SUBTITLE_DURATION_SECONDS = 7.0 # đĽ LOGIN ACCOUNTS CONFIG (áĄááąáŹááˇáşáá áşááŻáááşá¸ááᯠáá°áĄááťáŹá¸ ááťážááŻáśá¸áááŻááşááŤáááş) ACCOUNTS = { "user1": "123123", # Free User (Session/Browser áĄáááŻááş áá áşáááş á ááŻááş) "admin": "admin789" # Admin User (Unlimited) } EXPIRY_DATE_STR = "2026-8-31" # đ USER RATE LIMIT TRACKER (Session/Browser ID áĄáááŻááş ááąáá˝ááşáááş) 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) # ===================================================================== # đźď¸ 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 # ===================================================================== # ⥠DYNAMIC USER-PROVIDED API KEY TRANSLATION ENGINE # ===================================================================== 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 # ===================================================================== # đŹ TEXT WRAPPING & RENDER 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): 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()) # ===================================================================== # đŹ HIGH-SPEED PRODUCTION AUTOMATION ENGINE # ===================================================================== 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, # đ Browser Session State 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 ACCOUNT & SESSION ID áá°ááźááşá¸ login_username = request.username if (request and hasattr(request, 'username')) else "user1" if login_username == "admin": user_identifier = "admin" else: # Browser Session ID ááźááˇáş áá°áá áşáŚá¸á áŽááᯠááŽá¸ááźáŹá¸ áá˝á˛ááźáŹá¸áááş user_identifier = f"session_{session_id}" current_timestamp = time.time() print(f"đ¤ áĄááŻáśá¸ááźáŻáá° Account: {login_username} | Identifier: {user_identifier}") # Admin áááŻááşááŤá Session/Browser áĄáááŻááş á áááş (á) ááŻááş Limit á á áşááąá¸áááş 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 >= 90: # á ááŻááş ááźááˇáşááŤá ááŹá¸ááŽá¸áááş 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) # đ ááŽááŽááᯠáĄáąáŹááşááźááşá á˝áŹ ááŻááşáá°ááźáŽá¸áážáᏠááᯠSession á Count ááᯠá áááŻá¸áááş 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)}") # ===================================================================== # đ¨ GRADIO INTERFACE # ===================================================================== def create_main_app_interface(): is_valid, msg = check_app_expiry() with gr.Blocks() as main_app: # đ áĄááŻáśá¸ááźáŻáá°áááŻááşá¸áĄáá˝ááş ááŽá¸áááˇáş Session ID ááŻááşááąá¸ááźááşá¸ session_id_state = gr.State(lambda: str(uuid.uuid4())) gr.Markdown("