video / app.py
izfx's picture
Update app.py
f520d3c verified
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"))