MichaelChou0806 commited on
Commit
d4dc681
·
verified ·
1 Parent(s): e7827e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -47
app.py CHANGED
@@ -1,52 +1,161 @@
1
  import os
2
- import smtplib
3
- from email.mime.text import MIMEText
4
- import gradio as gr
5
  import time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- def check_env_and_email():
8
- results = []
9
- # 檢查環境變數
10
- app_pw = os.getenv("APP_PASSWORD")
11
- openai_key = os.getenv("OPENAI_API_KEY")
12
- alert_email = os.getenv("ALERT_EMAIL")
13
- alert_pass = os.getenv("ALERT_PASS")
14
-
15
- results.append("===== 🔍 環境變數檢查 =====")
16
- results.append(f"APP_PASSWORD = {app_pw}")
17
- results.append(f"OPENAI_API_KEY = {'✅ 已設定' if openai_key else '❌ 未設定'}")
18
- results.append(f"ALERT_EMAIL = {alert_email}")
19
- results.append(f"ALERT_PASS = {'✅ 已設定' if alert_pass else '❌ 未設定'}")
20
-
21
- # 測試寄信
22
- if not alert_email or not alert_pass:
23
- results.append("\n⚠️ 無法測試寄信,請先設定 ALERT_EMAIL 與 ALERT_PASS。")
24
- return "\n".join(results)
25
-
26
- results.append("\n===== 📧 開始測試寄信 =====")
27
- msg = MIMEText(f"這是一封來自 Hugging Face Space 的測試信。\n時間:{time.ctime()}", "plain", "utf-8")
28
- msg["Subject"] = "✅ 測試信件:Hugging Face SMTP 設定成功"
29
- msg["From"] = alert_email
30
- msg["To"] = alert_email
31
-
32
- try:
33
- with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
34
- server.login(alert_email, alert_pass)
35
- server.sendmail(alert_email, alert_email, msg.as_string())
36
- results.append(f"✅ 測試成功!郵件已寄出至 {alert_email}")
37
- except Exception as e:
38
- results.append(f"❌ 寄信失敗:{e}")
39
-
40
- return "\n".join(results)
41
-
42
-
43
- with gr.Blocks(title="環境與寄信測試工具") as demo:
44
- gr.Markdown("## 🧪 Hugging Face Space 環境變數與 Gmail 寄信測試")
45
- gr.Markdown("此頁面用來確認 Variables 是否正確設定,以及 Gmail SMTP 是否可用。")
46
-
47
- test_button = gr.Button("🚀 開始測試")
48
- output_box = gr.Textbox(label="結果輸出", lines=15)
49
-
50
- test_button.click(fn=check_env_and_email, outputs=output_box)
51
 
52
  demo.launch()
 
1
  import os
 
 
 
2
  import time
3
+ from pydub import AudioSegment
4
+ from openai import OpenAI
5
+ import gradio as gr
6
+
7
+ # ========================
8
+ # 🔐 設定區
9
+ # ========================
10
+ PASSWORD = os.getenv("APP_PASSWORD", "defaultpass")
11
+ MAX_SIZE = 25 * 1024 * 1024
12
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
13
+
14
+ # ========================
15
+ # ⚔️ 防暴力破解
16
+ # ========================
17
+ MAX_FAILED_IN_WINDOW = 10
18
+ WINDOW_SECONDS = 24 * 3600
19
+ LOCK_DURATION_SECONDS = 24 * 3600
20
+ SHORT_BURST_LIMIT = 5
21
+ SHORT_BURST_SECONDS = 60
22
+
23
+ attempts = {}
24
+ locked = {}
25
+
26
+ def _now(): return int(time.time())
27
+
28
+ def prune_old_attempts(sid):
29
+ cutoff = _now() - WINDOW_SECONDS
30
+ if sid in attempts:
31
+ attempts[sid] = [t for t in attempts[sid] if t >= cutoff]
32
+ if not attempts[sid]:
33
+ del attempts[sid]
34
+
35
+ def check_lock(sid):
36
+ if sid in locked:
37
+ if _now() < locked[sid]:
38
+ remain = locked[sid] - _now()
39
+ return True, f"🔒 已被鎖定,請 {remain // 60} 分鐘後再試。"
40
+ else:
41
+ locked.pop(sid, None)
42
+ attempts.pop(sid, None)
43
+ prune_old_attempts(sid)
44
+ cnt = len(attempts.get(sid, []))
45
+ if cnt >= MAX_FAILED_IN_WINDOW:
46
+ locked[sid] = _now() + LOCK_DURATION_SECONDS
47
+ return True, f"🔒 嘗試過多,已鎖定 24 小時。"
48
+ return False, ""
49
+
50
+ def record_failed_attempt(sid):
51
+ now = _now()
52
+ attempts.setdefault(sid, []).append(now)
53
+ prune_old_attempts(sid)
54
+ recent_cutoff = now - SHORT_BURST_SECONDS
55
+ recent = [t for t in attempts[sid] if t >= recent_cutoff]
56
+ if len(recent) >= SHORT_BURST_LIMIT:
57
+ locked[sid] = now + 300
58
+ return len(attempts[sid]), "⚠️ 多次快速嘗試,暫時鎖定5分鐘。"
59
+ return len(attempts[sid]), ""
60
+
61
+ def clear_attempts(sid):
62
+ attempts.pop(sid, None)
63
+ locked.pop(sid, None)
64
+
65
+ # ========================
66
+ # 🎧 音訊轉錄
67
+ # ========================
68
+ def split_audio_if_needed(path):
69
+ size = os.path.getsize(path)
70
+ if size <= MAX_SIZE:
71
+ return [path]
72
+ audio = AudioSegment.from_file(path)
73
+ num = int(size / MAX_SIZE) + 1
74
+ chunk_ms = len(audio) / num
75
+ files = []
76
+ for i in range(num):
77
+ start, end = int(i * chunk_ms), int((i + 1) * chunk_ms)
78
+ chunk = audio[start:end]
79
+ fn = f"chunk_{i+1}.wav"
80
+ chunk.export(fn, format="wav")
81
+ files.append(fn)
82
+ return files
83
+
84
+ def transcribe_core(path, model):
85
+ chunks = split_audio_if_needed(path)
86
+ txts = []
87
+ for f in chunks:
88
+ with open(f, "rb") as af:
89
+ res = client.audio.transcriptions.create(model=model, file=af, response_format="text")
90
+ txts.append(res)
91
+ full = "\n".join(txts)
92
+ res = client.chat.completions.create(
93
+ model="gpt-4o-mini",
94
+ messages=[{"role":"user","content":f"請用繁體中文摘要以下內容:\n{full}"}],
95
+ temperature=0.4,
96
+ )
97
+ summ = res.choices[0].message.content.strip()
98
+ return full, summ
99
+
100
+ # ========================
101
+ # 💬 主流程
102
+ # ========================
103
+ def transcribe_with_password(session_id, password, file, model_choice):
104
+ locked_flag, msg = check_lock(session_id)
105
+ if locked_flag:
106
+ return msg, "", ""
107
+ if password != PASSWORD:
108
+ cnt, msg2 = record_failed_attempt(session_id)
109
+ return msg2 or f"密碼錯誤(第 {cnt} 次)", "", ""
110
+ if not file:
111
+ return "請上傳音訊檔。", "", ""
112
+ clear_attempts(session_id)
113
+ full, summ = transcribe_core(file, model_choice)
114
+ return "✅ 轉錄完成", full, summ
115
+
116
+ def ask_about_transcript(full_text, q):
117
+ if not full_text.strip():
118
+ return "⚠️ 尚未有轉錄內容"
119
+ if not q.strip():
120
+ return "請輸入問題"
121
+ prompt = f"以下是轉錄內容:\n{full_text}\n\n問題:{q}\n請用繁體中文回答。"
122
+ res = client.chat.completions.create(
123
+ model="gpt-4o-mini", messages=[{"role":"user","content":prompt}], temperature=0.6)
124
+ return res.choices[0].message.content.strip()
125
+
126
+ # ========================
127
+ # 🌐 Gradio 介面
128
+ # ========================
129
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
130
+ gr.Markdown("## 🎧 語音轉錄與摘要工具(防暴力破解)")
131
+
132
+ session_state = gr.State(value=None)
133
+
134
+ with gr.Row():
135
+ password_input = gr.Textbox(label="輸入密碼", type="password")
136
+ model_choice = gr.Dropdown(["whisper-1", "gpt-4o-mini-transcribe"], value="whisper-1", label="選擇模型")
137
+
138
+ audio_input = gr.Audio(type="filepath", label="上傳音訊 (.m4a, .aac, .wav)")
139
+ transcribe_btn = gr.Button("開始轉錄與摘要 🚀")
140
+ status_box = gr.Textbox(label="狀態", interactive=False)
141
+ transcript_box = gr.Textbox(label="完整轉錄文字", lines=10)
142
+ summary_box = gr.Textbox(label="摘要結果", lines=10)
143
+
144
+ with gr.Accordion("💬 進一步問 AI", open=False):
145
+ user_q = gr.Textbox(label="輸入問題", lines=2)
146
+ ask_btn = gr.Button("詢問 AI 🤔")
147
+ ai_reply = gr.Textbox(label="AI 回覆", lines=6)
148
+
149
+ def init_session():
150
+ import uuid
151
+ return str(uuid.uuid4())
152
+ demo.load(init_session, None, session_state)
153
 
154
+ transcribe_btn.click(
155
+ transcribe_with_password,
156
+ [session_state, password_input, audio_input, model_choice],
157
+ [status_box, transcript_box, summary_box],
158
+ )
159
+ ask_btn.click(ask_about_transcript, [transcript_box, user_q], [ai_reply])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  demo.launch()