hoangtaiii commited on
Commit
413bca8
·
verified ·
1 Parent(s): 51a0db6

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +383 -0
  2. packages.txt +3 -0
  3. requirements.txt +12 -0
app.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ app.py — Gradio Cloud Studio for Hugging Face Spaces
3
+ ────────────────────────────────────────────────────
4
+ Features:
5
+ - Instant Live Video Preview with Bounding Box Overlay (100% reliable across all Gradio versions).
6
+ - 1-Click Fast Presets (Bottom, Bottom-Low, Middle) + Step Nudge Buttons for mobile touch.
7
+ - True Unicode Vietnamese Fonts (DejaVu Sans) — No more square [][][][][] boxes.
8
+ - 90% Solid Masking Box to 100% cover old Chinese subtitles.
9
+ - 25 AI Models Matrix (Groq, Gemini, OpenRouter, NVIDIA NIM, xKiro).
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import time
15
+ from pathlib import Path
16
+
17
+ # Add current directory to sys.path
18
+ BASE_DIR = Path(__file__).resolve().parent
19
+ if str(BASE_DIR) not in sys.path:
20
+ sys.path.insert(0, str(BASE_DIR))
21
+
22
+ import gradio as gr
23
+
24
+ try:
25
+ import spaces
26
+ except ImportError:
27
+ class MockSpaces:
28
+ @staticmethod
29
+ def GPU(*args, **kwargs):
30
+ def decorator(f):
31
+ return f
32
+ return decorator
33
+ spaces = MockSpaces()
34
+
35
+ from app.core.cloud_pipeline import CloudPipeline
36
+
37
+ pipeline = CloudPipeline(base_dir=BASE_DIR)
38
+
39
+
40
+ @spaces.GPU(duration=10)
41
+ def _zerogpu_keepalive():
42
+ return True
43
+
44
+
45
+ def get_actual_path(video_file, video_url=""):
46
+ """Robust extraction of video filepath across all Gradio versions."""
47
+ if video_file is not None:
48
+ if isinstance(video_file, str) and Path(video_file).exists():
49
+ return video_file
50
+ if hasattr(video_file, "path") and video_file.path and Path(video_file.path).exists():
51
+ return video_file.path
52
+ if hasattr(video_file, "name") and video_file.name and Path(video_file.name).exists():
53
+ return video_file.name
54
+ if isinstance(video_file, dict):
55
+ p = video_file.get("path") or video_file.get("name")
56
+ if p and Path(p).exists():
57
+ return p
58
+
59
+ if video_url and video_url.strip():
60
+ saved_path = BASE_DIR / "temp" / f"url_video_{int(time.time())}.mp4"
61
+ saved_path.parent.mkdir(parents=True, exist_ok=True)
62
+ try:
63
+ import subprocess
64
+ cmd = ["yt-dlp", "-f", "best[ext=mp4]/best", "-o", str(saved_path), video_url.strip()]
65
+ res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=60)
66
+ if res.returncode == 0 and saved_path.exists():
67
+ return str(saved_path)
68
+ except Exception:
69
+ pass
70
+
71
+ return None
72
+
73
+
74
+ def update_preview_box(video_file, url_input, custom_x, custom_y, custom_w, custom_h, preview_sec):
75
+ vpath = get_actual_path(video_file, url_input)
76
+ if not vpath:
77
+ return None
78
+
79
+ try:
80
+ frame_rgb = pipeline.generate_preview_frame(
81
+ video_path=str(vpath),
82
+ region_preset="custom",
83
+ custom_y_pct=float(custom_y),
84
+ custom_h_pct=float(custom_h),
85
+ custom_x_pct=float(custom_x),
86
+ custom_w_pct=float(custom_w),
87
+ sec=float(preview_sec)
88
+ )
89
+ return frame_rgb
90
+ except Exception:
91
+ return None
92
+
93
+
94
+ # Helper functions for 1-click preset buttons
95
+ def preset_bottom():
96
+ return 0, 72, 100, 22 # x, y, w, h
97
+
98
+ def preset_bottom_low():
99
+ return 0, 82, 100, 16 # x, y, w, h
100
+
101
+ def preset_middle():
102
+ return 0, 38, 100, 24 # x, y, w, h
103
+
104
+ def nudge_up(y):
105
+ return max(0, y - 5)
106
+
107
+ def nudge_down(y):
108
+ return min(95, y + 5)
109
+
110
+ def nudge_expand(h):
111
+ return min(60, h + 4)
112
+
113
+ def nudge_shrink(h):
114
+ return max(4, h - 4)
115
+
116
+
117
+ def process_video(
118
+ video_file,
119
+ video_url,
120
+ mode,
121
+ sub_mask_mode,
122
+ sub_style,
123
+ custom_x,
124
+ custom_y,
125
+ custom_w,
126
+ custom_h,
127
+ voice,
128
+ source_lang,
129
+ speed,
130
+ volume,
131
+ progress=gr.Progress()
132
+ ):
133
+ actual_video_path = get_actual_path(video_file, video_url)
134
+ if not actual_video_path or not Path(actual_video_path).exists():
135
+ return None, "❌ Vui lòng chọn 1 video từ điện thoại/máy tính hoặc nhập đường link!"
136
+
137
+ logs = []
138
+ def log_cb(msg: str):
139
+ logs.append(f"[{time.strftime('%H:%M:%S')}] {msg}")
140
+
141
+ def progress_cb(pct: int, stage: str):
142
+ progress(pct / 100.0, desc=f"{stage} ({pct}%)")
143
+
144
+ exec_pipeline = CloudPipeline(
145
+ base_dir=BASE_DIR,
146
+ log_callback=log_cb,
147
+ progress_callback=progress_cb
148
+ )
149
+
150
+ out_video = exec_pipeline.run_video(
151
+ video_path=str(actual_video_path),
152
+ mode=mode,
153
+ source_lang=source_lang,
154
+ voice=voice,
155
+ speed=speed,
156
+ pitch=0,
157
+ volume=int(volume),
158
+ region_preset="custom",
159
+ sub_mask_mode=sub_mask_mode,
160
+ sub_style=sub_style,
161
+ custom_y_pct=float(custom_y),
162
+ custom_h_pct=float(custom_h),
163
+ custom_x_pct=float(custom_x),
164
+ custom_w_pct=float(custom_w)
165
+ )
166
+
167
+ log_output = "\n".join(logs)
168
+ if out_video and Path(out_video).exists():
169
+ return str(out_video), f"🎉 DỊCH & LỒNG TIẾNG THÀNH CÔNG!\n\n{log_output}"
170
+ else:
171
+ return None, f"❌ Xử lý thất bại.\n\n{log_output}"
172
+
173
+
174
+ with gr.Blocks(title="Trung Sáng Việt Cloud Studio") as demo:
175
+ gr.Markdown(
176
+ """
177
+ # 🎬 TRUNG SÁNG VIỆT CLOUD STUDIO
178
+ ### Dịch • Lồng Tiếng • Che Sub Cũ & Đè Sub Mới Chuẩn Studio • 100% Cloud-Native
179
+ """
180
+ )
181
+
182
+ with gr.Row():
183
+ # Cột 1: Video & Live Preview Khung Vùng Che
184
+ with gr.Column(scale=1):
185
+ gr.Markdown("### 📥 1. Chọn Video & Xem Trước Trực Quan (Live Preview)")
186
+ video_input = gr.File(
187
+ label="📁 Bấm vào đây để chọn video từ Thư viện ảnh / Tệp tin điện thoại",
188
+ file_types=["video", ".mp4", ".mov", ".mkv", ".avi", ".webm", ".m4v"]
189
+ )
190
+ url_input = gr.Textbox(
191
+ label="Hoặc dán Link video (Douyin / TikTok / MP4)",
192
+ placeholder="https://v.douyin.com/... hoặc https://www.tiktok.com/..."
193
+ )
194
+
195
+ # Live preview frame with box
196
+ preview_image = gr.Image(
197
+ label="👁️ Khung Hình Xem Trước Vùng Che (Hộp Vàng-Đỏ di chuyển theo nút bấm)",
198
+ interactive=False
199
+ )
200
+ preview_sec = gr.Slider(
201
+ minimum=0.0, maximum=30.0, value=3.0, step=0.5,
202
+ label="⏱️ Tua giây video (để chọn khung hình có phụ đề rõ nhất)"
203
+ )
204
+
205
+ # Fast 1-Click Alignment Buttons
206
+ gr.Markdown("##### 🎯 Căn Chỉnh Vị Trí Nhanh (1-Chạm Trên Điện Thoại):")
207
+ with gr.Row():
208
+ btn_preset_bottom = gr.Button("📍 Đáy Màn Hình (Chuẩn TikTok)", size="sm")
209
+ btn_preset_bottom_low = gr.Button("📍 Đáy Sát Mép", size="sm")
210
+ btn_preset_middle = gr.Button("📍 Giữa Video", size="sm")
211
+
212
+ with gr.Row():
213
+ btn_up = gr.Button("🔼 Dịch Lên (-5%)", size="sm")
214
+ btn_down = gr.Button("🔽 Dịch Xuống (+5%)", size="sm")
215
+ btn_expand = gr.Button("➕ Cao Thêm (+4%)", size="sm")
216
+ btn_shrink = gr.Button("➖ Thu Gọn (-4%)", size="sm")
217
+
218
+ with gr.Row():
219
+ custom_y = gr.Slider(minimum=0, maximum=95, value=72, step=1, label="↕️ Vị trí Y (%)")
220
+ custom_h = gr.Slider(minimum=4, maximum=60, value=22, step=1, label="↕️ Chiều Cao H (%)")
221
+
222
+ with gr.Row(visible=False):
223
+ custom_x = gr.Number(value=0)
224
+ custom_w = gr.Number(value=100)
225
+
226
+ mode_input = gr.Radio(
227
+ choices=[
228
+ ("🎙️ Cloud ASR (Groq Whisper - Siêu tốc 1.5s/video)", "asr"),
229
+ ("👁️ Cloud OCR (25 AI Vision Models - Quét chữ màn hình)", "ocr")
230
+ ],
231
+ value="asr",
232
+ label="Phương thức bóc tách phụ đề"
233
+ )
234
+
235
+ # Cột 2: Cấu hình Giọng Đọc & Sub Mới
236
+ with gr.Column(scale=1):
237
+ gr.Markdown("### 🎙️ 2. Cấu Hình Che Sub Cũ & Giọng Đọc Tiếng Việt")
238
+
239
+ sub_mask_mode = gr.Dropdown(
240
+ choices=[
241
+ ("⬛ Hộp Đen Mờ Che Kín (Phủ 90% - Che sạch 100% chữ Hán)", "box"),
242
+ ("🌫️ Làm Mờ Sub Cũ (Delogo Blur) - Tự nhiên", "delogo"),
243
+ ("🚫 Không Che (Chỉ Đè Sub Mới Viền Đậm)", "none")
244
+ ],
245
+ value="box",
246
+ label="Kiểu che / xóa phụ đề cũ"
247
+ )
248
+
249
+ voice_input = gr.Dropdown(
250
+ choices=[
251
+ ("🎙️ Nam Minh (Nam - Trầm ấm, chuyên nghiệp, phim tài liệu)", "vi-VN-NamMinhNeural"),
252
+ ("🎙️ Hoài My (Nữ - Truyền cảm, ngọt ngào, review thời trang)", "vi-VN-HoaiMyNeural")
253
+ ],
254
+ value="vi-VN-NamMinhNeural",
255
+ label="Giọng đọc tiếng Việt (Microsoft Edge-TTS)"
256
+ )
257
+
258
+ source_lang_input = gr.Dropdown(
259
+ choices=[("🇨🇳 Tiếng Trung (Chinese - zh)", "zh"), ("🇺🇸 Tiếng Anh (English - en)", "en")],
260
+ value="zh",
261
+ label="Ngôn ngữ gốc của video"
262
+ )
263
+
264
+ sub_style = gr.Dropdown(
265
+ choices=[
266
+ ("✨ Chữ Trắng Viền Đen Nổi Bật (Arial Bold 24)", "white_bold"),
267
+ ("🟡 Chữ Vàng Viền Đen (Review/Phim)", "yellow_bold"),
268
+ ("🟢 Chữ Xanh Neon Cyberpunk", "neon_cyan")
269
+ ],
270
+ value="white_bold",
271
+ label="Kiểu chữ phụ đề tiếng Việt mới (Hardsub Style)"
272
+ )
273
+
274
+ with gr.Row():
275
+ speed_input = gr.Slider(minimum=0.8, maximum=1.5, value=1.0, step=0.05, label="Tốc độ đọc (Speed)")
276
+ vol_input = gr.Slider(minimum=50, maximum=150, value=100, step=5, label="Âm lượng (%)")
277
+
278
+ start_btn = gr.Button("🚀 BẮT ĐẦU DỊCH & LỒNG TIẾNG CLOUD", variant="primary")
279
+
280
+ # ── Live Preview Dynamic Event Listeners ─────────────────────────────────
281
+ preview_inputs = [
282
+ video_input,
283
+ url_input,
284
+ custom_x,
285
+ custom_y,
286
+ custom_w,
287
+ custom_h,
288
+ preview_sec
289
+ ]
290
+
291
+ video_input.change(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
292
+ url_input.change(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
293
+ custom_y.change(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
294
+ custom_h.change(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
295
+ preview_sec.change(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
296
+
297
+ # Fast Preset Button Events
298
+ btn_preset_bottom.click(
299
+ fn=preset_bottom,
300
+ outputs=[custom_x, custom_y, custom_w, custom_h]
301
+ ).then(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
302
+
303
+ btn_preset_bottom_low.click(
304
+ fn=preset_bottom_low,
305
+ outputs=[custom_x, custom_y, custom_w, custom_h]
306
+ ).then(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
307
+
308
+ btn_preset_middle.click(
309
+ fn=preset_middle,
310
+ outputs=[custom_x, custom_y, custom_w, custom_h]
311
+ ).then(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
312
+
313
+ # Nudge Button Events
314
+ btn_up.click(fn=nudge_up, inputs=[custom_y], outputs=[custom_y]).then(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
315
+ btn_down.click(fn=nudge_down, inputs=[custom_y], outputs=[custom_y]).then(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
316
+ btn_expand.click(fn=nudge_expand, inputs=[custom_h], outputs=[custom_h]).then(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
317
+ btn_shrink.click(fn=nudge_shrink, inputs=[custom_h], outputs=[custom_h]).then(fn=update_preview_box, inputs=preview_inputs, outputs=preview_image)
318
+
319
+ with gr.Row():
320
+ with gr.Column():
321
+ gr.Markdown("### 🎉 3. Video Thành Phẩm & Nhật Ký Xử Lý")
322
+ video_output = gr.Video(label="Video đã lồng tiếng hoàn chỉnh", interactive=False)
323
+ log_output = gr.Textbox(label="Nhật ký xử lý (Live Logs)", lines=8)
324
+
325
+ start_btn.click(
326
+ fn=process_video,
327
+ inputs=[
328
+ video_input,
329
+ url_input,
330
+ mode_input,
331
+ sub_mask_mode,
332
+ sub_style,
333
+ custom_x,
334
+ custom_y,
335
+ custom_w,
336
+ custom_h,
337
+ voice_input,
338
+ source_lang_input,
339
+ speed_input,
340
+ vol_input
341
+ ],
342
+ outputs=[video_output, log_output]
343
+ )
344
+
345
+ # ── Multi-User Authentication System ─────────────────────────────────────────
346
+ ACCOUNTS = {
347
+ "hoangtai": "Drippy2026@",
348
+ "admin": "AdminStudio2026@",
349
+ "user1": "Pass123456",
350
+ "user2": "Pass123456",
351
+ "khach": "DrippyVip2026@"
352
+ }
353
+
354
+ # Hỗ trợ thêm tài khoản linh hoạt từ biến môi trường Hugging Face (nếu có)
355
+ env_auth = os.environ.get("AUTH_USERS", "")
356
+ if env_auth:
357
+ for item in env_auth.split(","):
358
+ if ":" in item:
359
+ u, p = item.strip().split(":", 1)
360
+ ACCOUNTS[u.strip()] = p.strip()
361
+
362
+
363
+ def verify_login(username: str, password: str) -> bool:
364
+ """
365
+ Xác thực đăng nhập thông minh chống lỗi bàn phím điện thoại:
366
+ - Bỏ qua chữ hoa/chữ thường ở username (chống lỗi bàn phím tự viết hoa chữ cái đầu).
367
+ - Tự động cắt khoảng trắng thừa ở đầu/cuối do gợi ý bàn phím điện thoại chèn vào.
368
+ """
369
+ if not username or not password:
370
+ return False
371
+ clean_user = username.strip().lower()
372
+ clean_pass = password.strip()
373
+ for acc_user, acc_pass in ACCOUNTS.items():
374
+ if acc_user.lower() == clean_user and acc_pass == clean_pass:
375
+ return True
376
+ return False
377
+
378
+
379
+ demo.queue(max_size=20).launch(
380
+ auth=verify_login,
381
+ auth_message="🎬 TRUNG SÁNG VIỆT STUDIO • Vui lòng đăng nhập để sử dụng\n📱 Lưu ý: Hãy mở qua link https://hoangtaiii-drippy4.hf.space để đăng nhập mượt nhất trên điện thoại"
382
+ )
383
+
packages.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ffmpeg
2
+ fonts-dejavu
3
+ fonts-liberation
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.20.0
2
+ fastapi>=0.109.0
3
+ uvicorn[standard]>=0.27.0
4
+ python-multipart>=0.0.9
5
+ requests>=2.31.0
6
+ edge-tts>=6.1.10
7
+ pydub>=0.25.1
8
+ opencv-python-headless>=4.9.0.80
9
+ python-dotenv>=1.0.1
10
+ yt-dlp>=2024.03.10
11
+ websockets>=12.0
12
+ deep-translator>=1.11.4