djinn17 commited on
Commit
55779b5
·
verified ·
1 Parent(s): 45cad74

Deploy tool gỡ băng video/audio

Browse files
Files changed (3) hide show
  1. README.md +21 -7
  2. app.py +188 -0
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,13 +1,27 @@
1
  ---
2
- title: Go Bang Video
3
- emoji:
4
- colorFrom: pink
5
- colorTo: red
6
  sdk: gradio
7
- sdk_version: 6.15.2
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Go Bang Video Audio AI
3
+ emoji: 🎙️
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 4.44.0
 
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # 🎙️ Gỡ băng Video / Audio bằng AI
13
+
14
+ Tool nội bộ cho phòng truyền thông: upload video/audio → AI (Gemini) chuyển thành
15
+ văn bản tiếng Việt. Mỗi người nhập API key riêng.
16
+
17
+ ## Cách dùng
18
+ 1. Lấy API key tại https://aistudio.google.com/apikey
19
+ 2. Dán key vào ô **API key**
20
+ 3. Kéo thả video/audio (nhiều file được)
21
+ 4. Bấm **Bắt đầu gỡ băng** → đợi → copy kết quả hoặc tải file `.txt`
22
+
23
+ ## Lưu ý
24
+ - Mỗi file < 2GB. File lớn nên tách lấy audio trước cho nhanh.
25
+ - File câm/không tiếng → trả `(không có nội dung thoại)`.
26
+ - Báo *hết quota* → đợi hôm sau hoặc bật billing cho key.
27
+ - ⚠️ Key = tiền công ty, không chia sẻ ra ngoài.
app.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Tool transcribe video/audio -> text bằng Gemini, giao diện Gradio.
3
+ Dành cho người không rành kỹ thuật: nhập API key, kéo thả file, bấm nút.
4
+
5
+ KHÔNG cần ffmpeg: upload file thẳng lên Gemini Files API (Gemini tự xử lý
6
+ audio/video). Có chống AI "bịa" (lặp dạ vâng) trên file câm/nhiễu.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ import time
12
+ import tempfile
13
+ from collections import Counter
14
+ from pathlib import Path
15
+
16
+ import gradio as gr
17
+ from google import genai
18
+ from google.genai import types
19
+
20
+ # ---- Cấu hình ----
21
+ MODELS = ["gemini-2.5-flash", "gemini-3.1-flash-lite", "gemini-2.5-pro"]
22
+ DEFAULT_MODEL = "gemini-2.5-flash"
23
+ NO_SPEECH = "(không có nội dung thoại)"
24
+ MAX_MB = 1900 # Gemini Files API giới hạn ~2GB/file
25
+
26
+ TRANSCRIBE_PROMPT = """Bạn là công cụ gỡ băng (transcribe) audio. Hãy chuyển toàn bộ
27
+ lời nói trong file thành văn bản tiếng Việt VERBATIM (đúng từng từ).
28
+
29
+ YÊU CẦU BẮT BUỘC:
30
+ 1. Ngôn ngữ chính là tiếng Việt; nếu người nói xen tiếng Anh thì GIỮ NGUYÊN, KHÔNG dịch.
31
+ 2. KHÔNG tóm tắt, KHÔNG bình luận, KHÔNG thêm timestamp.
32
+ 3. TUYỆT ĐỐI KHÔNG thêm mô tả âm thanh dạng [im lặng], [tiếng nhạc], [tiếng cười]...
33
+ 4. Mỗi câu hoàn chỉnh hoặc mỗi lượt nói nằm trên một dòng riêng.
34
+ 5. Nếu HOÀN TOÀN không có lời nói, chỉ trả về đúng một dòng:
35
+ (không có nội dung thoại)
36
+ 6. Chỉ trả về phần text transcript thuần, không tiêu đề/ghi chú/markdown.
37
+ """.strip()
38
+
39
+
40
+ def looks_like_loop(text: str) -> bool:
41
+ """Phát hiện AI bịa (lặp 'dạ vâng' vô tận) trên file câm/nhiễu."""
42
+ lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
43
+ if len(lines) < 40:
44
+ return False
45
+ fillers = {"dạ", "vâng", "ạ", "ừm", "à", "ờ", "alo", "đúng rồi", "dạ vâng",
46
+ "vâng ạ", "dạ.", "vâng."}
47
+ def norm(s: str) -> str:
48
+ s = re.sub(r"[.,!?;:…\"'\-_/()]+", " ", s.lower())
49
+ return re.sub(r"\s+", " ", s).strip()
50
+ normed = [norm(x) for x in lines]
51
+ filler_ratio = sum(1 for n in normed if n in fillers
52
+ or all(w in fillers for w in n.split())) / len(normed)
53
+ counter = Counter(normed)
54
+ top3 = sum(c for _, c in counter.most_common(3)) / len(normed)
55
+ distinct = len(counter) / len(normed)
56
+ return filler_ratio >= 0.55 or top3 >= 0.6 or distinct <= 0.2
57
+
58
+
59
+ def wait_active(client, file_obj, timeout_s: int = 300):
60
+ """Đợi Gemini xử lý xong file vừa upload."""
61
+ deadline = time.time() + timeout_s
62
+ while file_obj.state.name == "PROCESSING":
63
+ if time.time() > deadline:
64
+ raise TimeoutError("File xử lý quá lâu trên server.")
65
+ time.sleep(2)
66
+ file_obj = client.files.get(name=file_obj.name)
67
+ if file_obj.state.name != "ACTIVE":
68
+ raise RuntimeError(f"File lỗi trạng thái: {file_obj.state.name}")
69
+ return file_obj
70
+
71
+
72
+ def transcribe_one(client, path: str, model: str) -> str:
73
+ """Upload 1 file -> transcribe -> trả text. Tự xóa file trên server."""
74
+ uploaded = client.files.upload(file=path)
75
+ try:
76
+ uploaded = wait_active(client, uploaded)
77
+ cfg = types.GenerateContentConfig(
78
+ system_instruction=TRANSCRIBE_PROMPT,
79
+ temperature=0.0,
80
+ thinking_config=types.ThinkingConfig(thinking_budget=0), # rẻ + nhanh
81
+ )
82
+ # Retry 503 (server quá tải tạm thời)
83
+ for wait in (0, 5, 10, 20):
84
+ if wait:
85
+ time.sleep(wait)
86
+ try:
87
+ resp = client.models.generate_content(
88
+ model=model,
89
+ contents=[uploaded, "Gỡ băng file trên theo đúng quy tắc."],
90
+ config=cfg,
91
+ )
92
+ text = (resp.text or "").strip()
93
+ return NO_SPEECH if looks_like_loop(text) else text
94
+ except Exception as e:
95
+ if "503" in str(e) or "UNAVAILABLE" in str(e):
96
+ continue
97
+ raise
98
+ return NO_SPEECH
99
+ finally:
100
+ try:
101
+ client.files.delete(name=uploaded.name)
102
+ except Exception:
103
+ pass
104
+
105
+
106
+ def run(api_key, files, model, progress=gr.Progress()):
107
+ if not api_key or not api_key.strip():
108
+ raise gr.Error("Chưa nhập API key. Lấy key tại aistudio.google.com/apikey")
109
+ if not files:
110
+ raise gr.Error("Chưa chọn file nào.")
111
+
112
+ try:
113
+ client = genai.Client(api_key=api_key.strip())
114
+ except Exception as e:
115
+ raise gr.Error(f"API key không hợp lệ: {e}")
116
+
117
+ sections, errors = [], []
118
+ total = len(files)
119
+ for i, f in enumerate(files):
120
+ path = f.name if hasattr(f, "name") else f
121
+ name = Path(path).name
122
+ size_mb = Path(path).stat().st_size / 1_000_000
123
+ progress((i) / total, desc=f"[{i+1}/{total}] {name}")
124
+ if size_mb > MAX_MB:
125
+ errors.append(f"{name}: quá lớn ({size_mb:.0f}MB > {MAX_MB}MB), bỏ qua.")
126
+ continue
127
+ try:
128
+ text = transcribe_one(client, path, model)
129
+ sections.append(f"## {name}\n\n{text}\n")
130
+ except Exception as e:
131
+ msg = str(e)
132
+ if "429" in msg or "RESOURCE_EXHAUSTED" in msg or "quota" in msg.lower():
133
+ errors.append(f"{name}: hết quota/giới hạn API key. Dừng.")
134
+ break
135
+ errors.append(f"{name}: lỗi - {msg[:120]}")
136
+
137
+ progress(1.0, desc="Xong")
138
+ combined = "\n---\n\n".join(sections) if sections else "(không có kết quả)"
139
+ if errors:
140
+ combined += "\n\n---\n\n### ⚠️ Ghi chú lỗi\n" + "\n".join(f"- {e}" for e in errors)
141
+
142
+ # Ghi file để tải xuống
143
+ out = Path(tempfile.gettempdir()) / "transcripts.txt"
144
+ out.write_text(combined, encoding="utf-8")
145
+ status = f"✅ Xong {len(sections)}/{total} file." + (
146
+ f" ({len(errors)} lỗi)" if errors else "")
147
+ return combined, str(out), status
148
+
149
+
150
+ # ---- Giao diện ----
151
+ with gr.Blocks(title="Gỡ băng Video/Audio bằng AI", theme=gr.themes.Soft()) as demo:
152
+ gr.Markdown(
153
+ "# 🎙️ Gỡ băng Video / Audio → Text (AI)\n"
154
+ "Upload video hoặc audio → AI chuyển thành văn bản tiếng Việt. "
155
+ "Hỗ trợ .mp4 .mov .m4a .mp3 .wav... (mỗi file < 2GB)."
156
+ )
157
+ with gr.Row():
158
+ with gr.Column(scale=1):
159
+ api_key = gr.Textbox(
160
+ label="🔑 API key (Gemini)", type="password",
161
+ placeholder="Dán key AIza... — lấy tại aistudio.google.com/apikey",
162
+ info="Key = tiền công ty. KHÔNG chia sẻ key cho người ngoài.",
163
+ )
164
+ model = gr.Dropdown(MODELS, value=DEFAULT_MODEL, label="Model")
165
+ files = gr.File(
166
+ label="📁 Chọn / kéo thả video, audio (nhiều file được)",
167
+ file_count="multiple",
168
+ file_types=["video", "audio"],
169
+ )
170
+ btn = gr.Button("▶️ Bắt đầu gỡ băng", variant="primary")
171
+ status = gr.Textbox(label="Trạng thái", interactive=False)
172
+ with gr.Column(scale=2):
173
+ out_text = gr.Textbox(label="📝 Kết quả transcript", lines=25,
174
+ show_copy_button=True)
175
+ out_file = gr.File(label="⬇️ Tải file .txt")
176
+
177
+ btn.click(run, inputs=[api_key, files, model],
178
+ outputs=[out_text, out_file, status])
179
+
180
+ gr.Markdown(
181
+ "---\n"
182
+ "**Mẹo:** file câm/không tiếng sẽ trả `(không có nội dung thoại)`. "
183
+ "File quá lớn nên tách lấy audio trước cho nhanh. "
184
+ "Nếu báo *hết quota* → đợi hôm sau hoặc bật billing cho key."
185
+ )
186
+
187
+ if __name__ == "__main__":
188
+ demo.queue(max_size=20).launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=4.44
2
+ google-genai>=1.0.0