import os import re import subprocess import gradio as gr CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) WORKING_DIR = os.path.dirname(CURRENT_DIR) # ViralCutter root import sys sys.path.append(WORKING_DIR) from i18n.i18n import I18nAuto i18n = I18nAuto() # Subtitle Presets SUBTITLE_PRESETS = { "MrBeast Clean Hook": { "font_name": "Montserrat-ExtraBold", "font_size": 32, "base_color": "#FFFFFF", "highlight_color": "#FFD700", "outline_color": "#000000", "outline_thickness": 3, "shadow_color": "#000000", "shadow_size": 2, "bold": True, "italic": False, "uppercase": True, "highlight_size": 38, "words_per_block": 3, "gap_limit": 0.25, "mode": "highlight", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 180, "alignment": 2, "remove_punctuation": True }, "Hormozi (Classic)": { "font_name": "Montserrat-ExtraBold", "font_size": 30, "base_color": "#FFFFFF", "highlight_color": "#00FF00", "outline_color": "#000000", "outline_thickness": 3, "shadow_color": "#000000", "shadow_size": 0, "bold": True, "italic": False, "uppercase": True, "highlight_size": 35, "words_per_block": 2, "gap_limit": 0.5, "mode": "highlight", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 200, "alignment": 2, "remove_punctuation": True }, "Beasty (Loud)": { "font_name": "Arial", "font_size": 34, "base_color": "#FFFFFF", "highlight_color": "#FF0000", "outline_color": "#000000", "outline_thickness": 3, "shadow_color": "#000000", "shadow_size": 3, "bold": True, "italic": False, "uppercase": True, "highlight_size": 40, "words_per_block": 3, "gap_limit": 0.4, "mode": "highlight", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 190, "alignment": 2, "remove_punctuation": True }, "Word Killer (TikTok)": { "font_name": "Impact", "font_size": 38, "base_color": "#FF0000", "highlight_color": "#FF0000", "outline_color": "#000000", "outline_thickness": 3, "shadow_color": "#000000", "shadow_size": 3, "bold": True, "italic": False, "uppercase": True, "highlight_size": 45, "words_per_block": 1, "gap_limit": 0.2, "mode": "word_by_word", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 210, "alignment": 2, "remove_punctuation": True }, "Rapid Fire (Sprint)": { "font_name": "Impact", "font_size": 36, "base_color": "#FFFF00", "highlight_color": "#FFFF00", "outline_color": "#000000", "outline_thickness": 2, "shadow_color": "#000000", "shadow_size": 2, "bold": True, "italic": True, "uppercase": True, "highlight_size": 42, "words_per_block": 1, "gap_limit": 0.3, "mode": "word_by_word", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 210, "alignment": 2, "remove_punctuation": True }, "Educational Fast": { "font_name": "Roboto-Bold", "font_size": 28, "base_color": "#FFFFFF", "highlight_color": "#00BFFF", "outline_color": "#000000", "outline_thickness": 2, "shadow_color": "#000000", "shadow_size": 1, "bold": True, "italic": False, "uppercase": False, "highlight_size": 34, "words_per_block": 3, "gap_limit": 0.45, "mode": "highlight", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 220, "alignment": 2, "remove_punctuation": False }, "Podcast Viral (Centered)": { "font_name": "Arial", "font_size": 26, "base_color": "#FFFFFF", "highlight_color": "#00FFAA", "outline_color": "#000000", "outline_thickness": 2, "shadow_color": "#000000", "shadow_size": 1, "bold": True, "italic": False, "uppercase": False, "highlight_size": 30, "words_per_block": 4, "gap_limit": 0.55, "mode": "highlight", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 240, "alignment": 2, "remove_punctuation": True }, "Drama Emocional": { "font_name": "Arial", "font_size": 28, "base_color": "#EAEAEA", "highlight_color": "#FF5555", "outline_color": "#000000", "outline_thickness": 2, "shadow_color": "#000000", "shadow_size": 2, "bold": True, "italic": False, "uppercase": False, "highlight_size": 34, "words_per_block": 2, "gap_limit": 0.6, "mode": "highlight", "underline": False, "strikeout": False, "border_style": 1, "vertical_position": 235, "alignment": 2, "remove_punctuation": True }, "Story Subtitle (Netflix Style)": { "font_name": "Arial", "font_size": 24, "base_color": "#FFFFFF", "highlight_color": "#FFFFFF", "outline_color": "#000000", "outline_thickness": 0, "shadow_color": "#000000", "shadow_size": 1, "bold": True, "italic": False, "uppercase": False, "highlight_size": 24, "words_per_block": 7, "gap_limit": 0.7, "mode": "no_highlight", "underline": False, "strikeout": False, "border_style": 3, "vertical_position": 250, "alignment": 2, "remove_punctuation": False }, "Neon Cyber": { "font_name": "Arial", "font_size": 30, "base_color": "#FF00FF", "highlight_color": "#00FFFF", "outline_color": "#FFFFFF", "outline_thickness": 1, "shadow_color": "#000000", "shadow_size": 3, "bold": True, "italic": False, "uppercase": True, "highlight_size": 36, "words_per_block": 2, "gap_limit": 0.5, "mode": "highlight", "underline": True, "strikeout": False, "border_style": 1, "vertical_position": 205, "alignment": 2, "remove_punctuation": True }, "Retro Pixel": { "font_name": "Consolas", "font_size": 26, "base_color": "#00FF00", "highlight_color": "#00FF00", "outline_color": "#000000", "outline_thickness": 2, "shadow_color": "#000000", "shadow_size": 0, "bold": False, "italic": False, "uppercase": True, "highlight_size": 26, "words_per_block": 1, "gap_limit": 0.5, "mode": "word_by_word", "underline": False, "strikeout": False, "border_style": 3, "vertical_position": 215, "alignment": 2 } } def generate_preview_html(font, size, color, highlight, outline, outline_thick, shadow, shadow_sz, bold, italic, upper, h_size, w_block, gap, mode, under, strike, border_s, vert_pos, align, remove_punc): # Debug inputs #print(f"DEBUG_HTML: Inputs - Color: {color}, Highlight: {highlight}, Outline: {outline}") def sanitize_color(c): if not c: return "#FFFFFF" clean = c.lstrip('#').strip() # Handle RGB/RGBA if clean.lower().startswith("rgb"): try: nums = re.findall(r"[\d\.]+", clean) if len(nums) >= 3: r = int(float(nums[0])) g = int(float(nums[1])) b = int(float(nums[2])) r = max(0, min(255, r)) g = max(0, min(255, g)) b = max(0, min(255, b)) ret = f"#{r:02X}{g:02X}{b:02X}" # print(f"DEBUG_HTML: Sanitized {c} -> {ret}") return ret except Exception as e: print(f"DEBUG_HTML: Sanitize Error: {e}") pass # Ensure # prefix for standard hex if missing if not c.startswith("#") and not c.startswith("rgb"): return f"#{c}" return c color = sanitize_color(color) highlight = sanitize_color(highlight) outline = sanitize_color(outline) shadow = sanitize_color(shadow) #print(f"DEBUG_HTML: Final Colors - Color: {color}, Highlight: {highlight}") weight = "bold" if bold else "normal" style = "italic" if italic else "normal" transform = "uppercase" if upper else "none" decorations = [] if under: decorations.append("underline") if strike: decorations.append("line-through") decoration = " ".join(decorations) if decorations else "none" # Force larger preview size regardless of input size # We maintain ratio between highlight and base base_preview_px = 40 ratio = 1.0 if size > 0: ratio = h_size / size highlight_preview_px = base_preview_px * ratio # Avoid extreme ratios in preview if highlight_preview_px > base_preview_px * 2: highlight_preview_px = base_preview_px * 2 # Border Style 3 is Opaque Box usually in ASS, here we can simulate background bg_style = "background-color: rgba(0,0,0,0.6); padding: 5px 10px; border-radius: 4px;" if border_s == 3 else "" # Handle Content based on Mode # Handle Content based on Mode content_html = "" preview_word = i18n("PREVIEW") if mode == "word_by_word": # Only show the active word content_html = f'{preview_word}' elif mode == "no_highlight": # No highlight difference span_html = f'{preview_word}' content_html = i18n("This is a {} of your subtitles").format(span_html) else: # Default Highlight mode span_html = f'{preview_word}' content_html = i18n("This is a {} of your subtitles").format(span_html) html = f"""