| import os, shutil, base64 |
| from pydub import AudioSegment |
| from openai import OpenAI |
| import gradio as gr |
|
|
| |
| import gradio.processing_utils as pu |
| def _dummy_check_allowed(*a, **kw): return True |
| pu._check_allowed = _dummy_check_allowed |
| print("🔓 已解除 Gradio 上傳路徑限制") |
|
|
| |
| PASSWORD = os.getenv("APP_PASSWORD", "chou") |
| MAX_SIZE = 25 * 1024 * 1024 |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) |
| print("===== 🚀 啟動中 =====") |
|
|
| |
| def split_audio(path): |
| size = os.path.getsize(path) |
| if size <= MAX_SIZE: return [path] |
| audio = AudioSegment.from_file(path) |
| n = int(size / MAX_SIZE) + 1 |
| chunk_ms = len(audio) / n |
| files = [] |
| for i in range(n): |
| fn = f"chunk_{i+1}.wav" |
| audio[int(i*chunk_ms):int((i+1)*chunk_ms)].export(fn, format="wav") |
| files.append(fn) |
| return files |
|
|
| |
| def transcribe_core(path, model="whisper-1"): |
| if path.lower().endswith(".mp4"): |
| fixed = path[:-4] + ".m4a" |
| try: shutil.copy(path, fixed); path = fixed |
| except Exception as e: print(f"⚠️ mp4→m4a 失敗: {e}") |
|
|
| chunks = split_audio(path) |
| text_list = [] |
| for f in chunks: |
| with open(f, "rb") as af: |
| txt = client.audio.transcriptions.create(model=model, file=af, response_format="text") |
| text_list.append(txt) |
| full_txt = "\n".join(text_list) |
|
|
| trad = client.chat.completions.create( |
| model="gpt-4o-mini", |
| messages=[ |
| {"role":"system","content":"你是嚴格的繁體中文轉換器"}, |
| {"role":"user","content":f"將以下內容轉為台灣繁體,不意譯:\n{full_txt}"} |
| ], temperature=0.0).choices[0].message.content.strip() |
|
|
| summ = client.chat.completions.create( |
| model="gpt-4o-mini", |
| messages=[ |
| {"role":"system","content":"你是繁體摘要助手"}, |
| {"role":"user","content":f"用條列或一句話摘要:\n{trad}"} |
| ], temperature=0.2).choices[0].message.content.strip() |
| return trad, summ |
|
|
| |
| def transcribe(password, file): |
| if password.strip() != PASSWORD: |
| return "❌ 密碼錯誤", "", "" |
| if not file: |
| return "⚠️ 未選擇檔案", "", "" |
|
|
| |
| temp_path = "uploaded_audio.m4a" |
| try: |
| if hasattr(file, "data") and isinstance(file.data, str) and file.data.startswith("data:audio"): |
| base64_str = file.data.split(",")[1] |
| with open(temp_path, "wb") as f: |
| f.write(base64.b64decode(base64_str)) |
| file.name = temp_path |
| elif os.path.isdir(getattr(file, "name", "")): |
| print("⚠️ path 是資料夾,改用 base64") |
| base64_str = getattr(file, "data", "").split(",")[1] |
| with open(temp_path, "wb") as f: |
| f.write(base64.b64decode(base64_str)) |
| file.name = temp_path |
| except Exception as e: |
| print(f"⚠️ base64 寫入失敗: {e}") |
| return f"❌ 上傳格式錯誤: {e}", "", "" |
|
|
| text, summary = transcribe_core(file.name) |
| return "✅ 完成", text, summary |
|
|
| |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: |
| gr.Markdown("## 🎧 LINE 語音轉錄與摘要(Base64 安全版)") |
| pw = gr.Textbox(label="密碼", type="password") |
| f = gr.File(label="上傳音訊檔") |
| run = gr.Button("開始轉錄 🚀") |
| s = gr.Textbox(label="狀態", interactive=False) |
| t = gr.Textbox(label="轉錄結果", lines=10) |
| su = gr.Textbox(label="AI 摘要", lines=8) |
| run.click(transcribe, [pw, f], [s, t, su]) |
|
|
| app = demo |
| if __name__ == "__main__": |
| demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|