import os import time import shutil from pydub import AudioSegment from openai import OpenAI import gradio as gr from fastapi import FastAPI, UploadFile, File, Form from threading import Thread import uvicorn # ====================================================== # 🔐 設定區 # ====================================================== PASSWORD = os.getenv("APP_PASSWORD", "chou") MAX_SIZE = 25 * 1024 * 1024 client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) print("===== 🚀 啟動中 =====") print(f"APP_PASSWORD: {'✅ 已載入' if PASSWORD else '❌ 未載入'}") print(f"目前密碼內容:{PASSWORD}") # ====================================================== # 🎧 音訊轉錄核心 # ====================================================== def split_audio_if_needed(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 parts = [] 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") parts.append(fn) return parts 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_if_needed(path) txts = [] for f in chunks: with open(f, "rb") as af: res = client.audio.transcriptions.create(model=model, file=af, response_format="text") txts.append(res) full_raw = "\n".join(txts) conv_prompt = ( "請將以下內容完整轉換為「繁體中文(台灣用語)」:\n" "規則:1) 僅做簡→繁字形轉換;2) 不要意譯或改寫;3) 不要添加任何前後綴。\n-----\n" + full_raw ) trad = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是嚴格的繁體中文轉換器。"}, {"role": "user", "content": conv_prompt} ], temperature=0.0, ).choices[0].message.content.strip() sum_prompt = ( "請用台灣繁體中文撰寫摘要。若內容資訊多,可條列出重點;" "若內容簡短,請用一句話概述即可。\n\n" + trad ) summ = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一位精準且嚴格使用台灣繁體中文的摘要助手。"}, {"role": "user", "content": sum_prompt} ], temperature=0.2, ).choices[0].message.content.strip() return trad, summ # ====================================================== # 🌐 FastAPI for 捷徑 # ====================================================== api_app = FastAPI() @api_app.post("/api/transcribe") async def api_transcribe(file: UploadFile = File(...), token: str = Form(...)): if token != PASSWORD: return {"error": "Invalid token"} temp = file.filename with open(temp, "wb") as f: f.write(await file.read()) text, summary = transcribe_core(temp) os.remove(temp) return {"text": text, "summary": summary} # ====================================================== # 💬 Gradio 介面 # ====================================================== def transcribe_with_password(password, file): if password.strip() != PASSWORD: return "❌ 密碼錯誤", "", "" if not file: return "⚠️ 未選擇檔案", "", "" text, summary = transcribe_core(file.name) return "✅ 完成", text, summary with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("## 🎧 LINE 語音轉錄與摘要(支援 .m4a / .mp4)") 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_with_password, [pw, f], [s, t, su]) # ====================================================== # 🚀 啟動 # ====================================================== def run_api(): uvicorn.run(api_app, host="0.0.0.0", port=7861) Thread(target=run_api, daemon=True).start() app = demo # ✅ Hugging Face 主入口使用 Gradio if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)