| 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 |
|
|
| |
| STORAGE_DIR = "projects" |
| if not os.path.exists(STORAGE_DIR): |
| os.makedirs(STORAGE_DIR) |
|
|
| |
|
|
| |
| import socket |
|
|
| def scan_video(url): |
| |
| ydl_opts = { |
| 'quiet': True, |
| 'no_warnings': True, |
| 'noplaylist': True, |
| 'nocheckcertificate': True, |
| 'check_formats': False, |
| 'source_address': '0.0.0.0', |
| |
| |
| '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', |
| |
| |
| 'socket_timeout': 30, |
| 'retries': 5, |
| 'dynamic_mpd': False, |
| } |
|
|
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: |
| try: |
| |
| if not url or "http" not in url: |
| return "⚠️ Vui lòng nhập link URL hợp lệ!", None, [] |
| |
| |
| info = ydl.extract_info(url, download=False) |
| |
| |
| subs_found = [] |
| |
| |
| if 'subtitles' in info and info['subtitles']: |
| for lang in info['subtitles']: |
| subs_found.append(f"Sub: {lang}") |
| |
| |
| 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: |
| |
| 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, [] |
| |
| 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]}") |
| return response.text |
|
|
| |
| async def create_dubbing(sub_json_str): |
| sub_data = json.loads(sub_json_str) |
| |
| communicate = edge_tts.Communicate(sub_data[0]['text'], "vi-VN-HoaiMiNeural") |
| await communicate.save("preview.mp3") |
| return "preview.mp3" |
|
|
| |
| 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: |
| |
| 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=[]) |
| |
| |
| 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") |
|
|
| |
| 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...)*") |
|
|
| |
| 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") |
|
|
| |
| 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ớ") |
|
|
| |
| 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 |
| ) |
|
|
| |
| demo.launch(auth=("admin", "123456")) |