import gradio as gr import yt_dlp import google.generativeai as genai import edge_tts import asyncio import pysubs2 import json import os import shutil # --- CẤU HÌNH HỆ THỐNG --- STORAGE_DIR = "projects" if not os.path.exists(STORAGE_DIR): os.makedirs(STORAGE_DIR) # --- HÀM XỬ LÝ LOGIC --- # 1. Quét Video từ URL import socket def scan_video(url): # Cấu hình "lì lợm" nhất để vượt rào DNS và chặn truy cập ydl_opts = { 'quiet': True, 'no_warnings': True, 'noplaylist': True, 'nocheckcertificate': True, # Bỏ qua kiểm tra SSL (tránh lỗi cert) 'check_formats': False, # Không kiểm tra định dạng trước (tăng tốc) 'source_address': '0.0.0.0', # Ép sử dụng IPv4 vì IPv6 thường lỗi DNS trên HF # Giả lập trình duyệt Chrome mới nhất để YouTube không nghi ngờ 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', # Các tham số giúp xử lý kết nối kém 'socket_timeout': 30, 'retries': 5, 'dynamic_mpd': False, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: try: # Kiểm tra đầu vào if not url or "http" not in url: return "⚠️ Vui lòng nhập link URL hợp lệ!", None, [] # Trích xuất thông tin video (download=False để chỉ lấy thông tin) info = ydl.extract_info(url, download=False) # Xử lý lấy danh sách Phụ đề subs_found = [] # 1. Phụ đề do người dùng upload (Manually uploaded) if 'subtitles' in info and info['subtitles']: for lang in info['subtitles']: subs_found.append(f"Sub: {lang}") # 2. Phụ đề tự động (Automatic captions) if 'automatic_captions' in info and info['automatic_captions']: for lang in info['automatic_captions']: subs_found.append(f"Auto: {lang}") title = info.get('title', 'Không có tiêu đề') thumbnail = info.get('thumbnail', None) return title, thumbnail, subs_found except Exception as e: # Trả về thông báo lỗi chi tiết để anh em mình dễ bắt bệnh error_msg = str(e) if "Name or service not known" in error_msg: return "❌ Lỗi DNS: Máy chủ HF không tìm thấy YouTube. Anh hãy thử Restart Space hoặc tạo Space mới ở Region khác.", None, [] return f"⚠️ Lỗi: {error_msg}", None, [] # 2. Dịch Subtitle với Gemini async def translate_sub_ai(api_key, sub_content, style_choice, custom_prompt): if not api_key: return "Thiếu Gemini API Key!" genai.configure(api_key=api_key) model = genai.GenerativeModel('gemini-1.5-flash') prompts = { "Mặc định": "Dịch phụ đề sau sang tiếng Việt tự nhiên, giữ nguyên định dạng thời gian. Trả về định dạng JSON: [{'start':..., 'end':..., 'text':..., 'gender': 'male'/'female'}]", "Hài hước": "Dịch phụ đề sau sang tiếng Việt phong cách hài hước, lầy lội, dùng trend. Trả về định dạng JSON: [{'start':..., 'end':..., 'text':..., 'gender': 'male'/'female'}]", "Tùy chỉnh": f"{custom_prompt}. Trả về định dạng JSON: [{'start':..., 'end':..., 'text':..., 'gender': 'male'/'female'}]" } prompt = prompts.get(style_choice, prompts["Mặc định"]) response = model.generate_content(f"{prompt}\n\nNội dung sub:\n{sub_content[:5000]}") # Giới hạn demo return response.text # 3. Tạo Audio Thuyết minh (Edge-TTS) async def create_dubbing(sub_json_str): sub_data = json.loads(sub_json_str) # Demo tạo 1 đoạn audio ngắn communicate = edge_tts.Communicate(sub_data[0]['text'], "vi-VN-HoaiMiNeural") await communicate.save("preview.mp3") return "preview.mp3" # --- GIAO DIỆN GRADIO --- with gr.Blocks(theme=gr.themes.Monochrome()) as demo: gr.Markdown("# 🎬 AI VIDEO DUBBING STUDIO PRO") api_key_state = gr.State("") with gr.Accordion("⚙️ SETTINGS", open=False): input_api = gr.Textbox(label="Gemini API Key", type="password") sys_token = gr.Textbox(label="Mã bảo mật hệ thống", type="password") input_api.change(lambda x: x, input_api, api_key_state) with gr.Tabs() as tabs: # TAB 1: NGUỒN VÀO with gr.Tab("📥 1. SOURCE"): url = gr.Textbox(label="URL Video (Youtube, FB, TikTok...)") with gr.Row(): btn_scan = gr.Button("QUÉT NGUỒN", variant="primary") with gr.Row(): v_title = gr.Markdown("### Tên phim: ...") v_thumb = gr.Image(label="Thumbnail", width=300) sub_list = gr.Dropdown(label="Danh sách Phụ đề tìm thấy", choices=[]) # TAB 2: PHỤ ĐỀ & AI DỊCH with gr.Tab("📝 2. SUBTITLE & AI"): with gr.Row(): with gr.Column(scale=2): v_preview = gr.Video(label="Quick Sync Preview") sub_editor = gr.TextArea(label="Sub Editor (SRT Format)", lines=12) with gr.Column(scale=1): style = gr.Radio(["Mặc định", "Hài hước", "Tùy chỉnh"], label="Phong cách dịch", value="Mặc định") c_prompt = gr.Textbox(label="Custom Prompt", placeholder="Nhập yêu cầu riêng...") btn_trans = gr.Button("🤖 DỊCH VỚI GEMINI", variant="primary") trans_output = gr.JSON(label="Kết quả dịch & Phân vai") # TAB 3: THUYẾT MINH & PREVIEW with gr.Tab("🎧 3. DUBBING PREVIEW"): with gr.Row(): voice_m = gr.Dropdown(["vi-VN-NamMinhNeural"], label="Giọng Nam", value="vi-VN-NamMinhNeural") voice_f = gr.Dropdown(["vi-VN-HoaiMiNeural"], label="Giọng Nữ", value="vi-VN-HoaiMiNeural") with gr.Row(): btn_preview_audio = gr.Button("NGHE THỬ LIVE DUBBING") audio_player = gr.Audio(label="Bản nghe thử") gr.Markdown("*(Tính năng Pre-fetch đang chạy ngầm...)*") # TAB 4: ĐÓNG GÓI (RENDER) with gr.Tab("🏗️ 4. RENDER"): render_fmt = gr.Radio(["MKV (Đa kênh)", "MP4 (Gộp cứng)"], label="Định dạng xuất") btn_render = gr.Button("BẮT ĐẦU ĐÓNG GÓI", variant="primary") final_video = gr.File(label="Tải video hoàn chỉnh") # TAB 5: QUẢN LÝ with gr.Tab("📂 5. PROJECTS"): explorer = gr.FileExplorer(root_dir=STORAGE_DIR, label="Quản lý dự án") btn_clear = gr.Button("Dọn dẹp bộ nhớ") # --- KẾT NỐI SỰ KIỆN --- btn_scan.click(scan_video, inputs=url, outputs=[v_title, v_thumb, sub_list]) btn_trans.click( fn=lambda key, content, st, cp: asyncio.run(translate_sub_ai(key, content, st, cp)), inputs=[api_key_state, sub_editor, style, c_prompt], outputs=trans_output ) btn_preview_audio.click( fn=lambda data: asyncio.run(create_dubbing(data)), inputs=trans_output, outputs=audio_player ) # Launch với Auth demo.launch(auth=("admin", "123456"))