MichaelChou0806 commited on
Commit
2fa940d
·
verified ·
1 Parent(s): 757178b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +33 -112
app.py CHANGED
@@ -4,64 +4,18 @@ import shutil
4
  from pydub import AudioSegment
5
  from openai import OpenAI
6
  import gradio as gr
7
- from fastapi import FastAPI, File, UploadFile, Form, HTTPException
8
 
9
  # ======================================================
10
  # 🔐 設定區
11
  # ======================================================
12
- PASSWORD = os.getenv("APP_PASSWORD", "chou")
13
  MAX_SIZE = 25 * 1024 * 1024
14
  client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
15
 
16
  print("===== 🚀 啟動中 =====")
17
  print(f"APP_PASSWORD: {'✅ 已載入' if PASSWORD else '❌ 未載入'}")
18
-
19
- # ======================================================
20
- # ⚔️ 防暴力破解
21
- # ======================================================
22
- MAX_FAILED_IN_WINDOW = 10
23
- WINDOW_SECONDS = 24 * 3600
24
- LOCK_DURATION_SECONDS = 24 * 3600
25
- SHORT_BURST_LIMIT = 5
26
- SHORT_BURST_SECONDS = 60
27
-
28
- attempts = {}
29
- locked = {}
30
-
31
- def _now(): return int(time.time())
32
- def prune_old_attempts(sid):
33
- cutoff = _now() - WINDOW_SECONDS
34
- if sid in attempts:
35
- attempts[sid] = [t for t in attempts[sid] if t >= cutoff]
36
- if not attempts[sid]:
37
- del attempts[sid]
38
- def check_lock(sid):
39
- if sid in locked:
40
- if _now() < locked[sid]:
41
- remain = locked[sid] - _now()
42
- return True, f"🔒 已被鎖定,請 {remain // 60} 分鐘後再試。"
43
- else:
44
- locked.pop(sid, None)
45
- attempts.pop(sid, None)
46
- prune_old_attempts(sid)
47
- cnt = len(attempts.get(sid, []))
48
- if cnt >= MAX_FAILED_IN_WINDOW:
49
- locked[sid] = _now() + LOCK_DURATION_SECONDS
50
- return True, f"🔒 嘗試過多,已鎖定 24 小時。"
51
- return False, ""
52
- def record_failed_attempt(sid):
53
- now = _now()
54
- attempts.setdefault(sid, []).append(now)
55
- prune_old_attempts(sid)
56
- recent_cutoff = now - SHORT_BURST_SECONDS
57
- recent = [t for t in attempts[sid] if t >= recent_cutoff]
58
- if len(recent) >= SHORT_BURST_LIMIT:
59
- locked[sid] = now + 300
60
- return len(attempts[sid]), "⚠️ 多次快速嘗試,暫時鎖定5分鐘。"
61
- return len(attempts[sid]), ""
62
- def clear_attempts(sid):
63
- attempts.pop(sid, None)
64
- locked.pop(sid, None)
65
 
66
  # ======================================================
67
  # 🎧 音訊轉錄核心
@@ -82,23 +36,24 @@ def split_audio_if_needed(path):
82
  files.append(fn)
83
  return files
84
 
85
- def transcribe_core(path, model):
86
- if path.lower().endswith(".mp4"):
87
  fixed_path = path[:-4] + ".m4a"
88
  try:
89
  shutil.copy(path, fixed_path)
90
  path = fixed_path
91
  print("🔧 已自動修正 mp4 → m4a")
92
  except Exception as e:
93
- print(f"⚠️ mp4→m4a 複製失敗:{e}")
94
 
95
- # 轉錄
96
  chunks = split_audio_if_needed(path)
97
  raw_parts = []
98
  for f in chunks:
99
  with open(f, "rb") as af:
100
  res = client.audio.transcriptions.create(
101
- model=model, file=af, response_format="text"
 
 
102
  )
103
  raw_parts.append(res)
104
  full_raw = "\n".join(raw_parts)
@@ -106,12 +61,13 @@ def transcribe_core(path, model):
106
  # 簡轉繁
107
  conv_prompt = (
108
  "請將以下內容完整轉換為「繁體中文(台灣用語)」:\n"
109
- "規則:1) 僅做簡→繁字形轉換;2) 不意譯;3) 不添加文字。\n-----\n" + full_raw
 
110
  )
111
  trad_resp = client.chat.completions.create(
112
  model="gpt-4o-mini",
113
  messages=[
114
- {"role": "system", "content": "你是嚴格的繁體中文轉換器。"},
115
  {"role": "user", "content": conv_prompt}
116
  ],
117
  temperature=0.0,
@@ -120,80 +76,45 @@ def transcribe_core(path, model):
120
 
121
  # 摘要
122
  sum_prompt = (
123
- "請用台灣繁體中文撰寫摘要,若內容需要時可條列重點(不固定數量),"
124
- "重點是讓人快速理解原文,無需贅述:\n" + full_trad
125
  )
126
  sum_resp = client.chat.completions.create(
127
  model="gpt-4o-mini",
128
  messages=[
129
- {"role": "system", "content": "你是台灣繁體中文摘要助手。"},
130
  {"role": "user", "content": sum_prompt}
131
  ],
132
  temperature=0.2,
133
  )
134
  summ = sum_resp.choices[0].message.content.strip()
135
-
136
  return full_trad, summ
137
 
138
- # ======================================================
139
- # 🌐 FastAPI API(供 iPhone 捷徑使用)
140
- # ======================================================
141
- app = FastAPI(title="LINE Transcription API")
142
-
143
- @app.post("/api/transcribe")
144
- async def api_transcribe(file: UploadFile = File(...), token: str = Form(default=None)):
145
- """供捷徑上傳音訊並取得 JSON"""
146
- if token != PASSWORD:
147
- raise HTTPException(status_code=403, detail="Invalid token")
148
-
149
- temp = file.filename
150
- with open(temp, "wb") as f:
151
- f.write(await file.read())
152
-
153
- text, summary = transcribe_core(temp, "whisper-1")
154
- os.remove(temp)
155
- return {"text": text, "summary": summary}
156
-
157
- @app.get("/health")
158
- def health():
159
- return {"status": "ok", "time": int(time.time())}
160
-
161
  # ======================================================
162
  # 💬 Gradio 介面
163
  # ======================================================
164
- def transcribe_with_password(session_id, password, file_input, model_choice):
165
  if password.strip() != PASSWORD:
166
- return "❌ 密碼錯誤", "", ""
167
- path = file_input.name if file_input else None
168
- if not path:
169
- return "⚠️ 找不到檔案", "", ""
170
- full, summ = transcribe_core(path, model_choice)
171
- return "✅ 完成", full, summ
172
 
173
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
174
- gr.Markdown("## 🎧 LINE 語音轉錄與摘要工具(支援 iPhone LINE .mp4)")
175
-
176
- with gr.Row():
177
- password_input = gr.Textbox(label="輸入密碼", type="password", placeholder="請輸入密碼")
178
- model_choice = gr.Dropdown(["whisper-1", "gpt-4o-mini-transcribe"], value="whisper-1", label="選擇模型")
179
-
180
- file_input = gr.File(label="上傳音訊(.m4a / .mp4 / .aac / .wav)", file_count="single")
181
- transcribe_btn = gr.Button("開始轉錄 🚀")
182
- status_box = gr.Textbox(label="狀態")
183
- transcript_box = gr.Textbox(label="完整轉錄文字", lines=10)
184
- summary_box = gr.Textbox(label="摘要結果", lines=10)
185
-
186
- transcribe_btn.click(
187
- transcribe_with_password,
188
- [gr.State(value="local"), password_input, file_input, model_choice],
189
- [status_box, transcript_box, summary_box]
190
- )
191
 
192
  # ======================================================
193
- # 🚀 啟動(Hugging Face)
194
  # ======================================================
195
- gr.mount_gradio_app(app, demo, path="/")
196
-
197
  if __name__ == "__main__":
198
- import uvicorn
199
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
4
  from pydub import AudioSegment
5
  from openai import OpenAI
6
  import gradio as gr
7
+ from fastapi import HTTPException
8
 
9
  # ======================================================
10
  # 🔐 設定區
11
  # ======================================================
12
+ PASSWORD = os.getenv("APP_PASSWORD", "defaultpass")
13
  MAX_SIZE = 25 * 1024 * 1024
14
  client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
15
 
16
  print("===== 🚀 啟動中 =====")
17
  print(f"APP_PASSWORD: {'✅ 已載入' if PASSWORD else '❌ 未載入'}")
18
+ print(f"目前密碼內容:{PASSWORD}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  # ======================================================
21
  # 🎧 音訊轉錄核心
 
36
  files.append(fn)
37
  return files
38
 
39
+ def transcribe_core(path, model="whisper-1"):
40
+ if path and path.lower().endswith(".mp4"):
41
  fixed_path = path[:-4] + ".m4a"
42
  try:
43
  shutil.copy(path, fixed_path)
44
  path = fixed_path
45
  print("🔧 已自動修正 mp4 → m4a")
46
  except Exception as e:
47
+ print(f"⚠️ mp4→m4a 轉檔失敗:{e}")
48
 
 
49
  chunks = split_audio_if_needed(path)
50
  raw_parts = []
51
  for f in chunks:
52
  with open(f, "rb") as af:
53
  res = client.audio.transcriptions.create(
54
+ model=model,
55
+ file=af,
56
+ response_format="text"
57
  )
58
  raw_parts.append(res)
59
  full_raw = "\n".join(raw_parts)
 
61
  # 簡轉繁
62
  conv_prompt = (
63
  "請將以下內容完整轉換為「繁體中文(台灣用語)」:\n"
64
+ "規則:1) 僅做簡→繁字形轉換;2) 不要意譯或改寫;3) 不要添加任何前後綴。\n"
65
+ "-----\n" + full_raw
66
  )
67
  trad_resp = client.chat.completions.create(
68
  model="gpt-4o-mini",
69
  messages=[
70
+ {"role": "system", "content": "你是嚴格的繁體中文轉換器,只進行字形轉換。"},
71
  {"role": "user", "content": conv_prompt}
72
  ],
73
  temperature=0.0,
 
76
 
77
  # 摘要
78
  sum_prompt = (
79
+ "請用台灣繁體中文撰寫摘要。若內容資訊多,可條列出重點;若內容簡短,請用一句話概述即可。\n\n"
80
+ + full_trad
81
  )
82
  sum_resp = client.chat.completions.create(
83
  model="gpt-4o-mini",
84
  messages=[
85
+ {"role": "system", "content": "你是一位精準且嚴格使用台灣繁體中文的摘要助手。"},
86
  {"role": "user", "content": sum_prompt}
87
  ],
88
  temperature=0.2,
89
  )
90
  summ = sum_resp.choices[0].message.content.strip()
 
91
  return full_trad, summ
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  # ======================================================
94
  # 💬 Gradio 介面
95
  # ======================================================
96
+ def transcribe_with_password(password, file):
97
  if password.strip() != PASSWORD:
98
+ raise HTTPException(status_code=403, detail="密碼錯誤 ")
99
+ if not file:
100
+ return "⚠️ 未選擇檔案", "", ""
101
+ text, summary = transcribe_core(file.name)
102
+ return "✅ 完成", text, summary
 
103
 
104
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
105
+ gr.Markdown("## 🎧 LINE 語音轉錄與摘要(支援 .m4a / .mp4)")
106
+ pw = gr.Textbox(label="輸入密碼", type="password")
107
+ f = gr.File(label="上傳音訊檔")
108
+ run = gr.Button("開始轉錄 🚀")
109
+ s = gr.Textbox(label="狀態", interactive=False)
110
+ t = gr.Textbox(label="轉錄結果", lines=10)
111
+ su = gr.Textbox(label="AI 摘要", lines=8)
112
+ run.click(transcribe_with_password, [pw, f], [s, t, su])
 
 
 
 
 
 
 
 
 
113
 
114
  # ======================================================
115
+ # 🚀 啟動
116
  # ======================================================
 
 
117
  if __name__ == "__main__":
118
+ demo.launch(server_name="0.0.0.0", server_port=7860)
119
+ else:
120
+ demo.launch()