tbdavid2019 commited on
Commit
feb8411
·
1 Parent(s): 9473dce

新增 api

Browse files
Files changed (5) hide show
  1. .env.example +2 -0
  2. README.md +85 -5
  3. api.py +283 -0
  4. app.py +7 -0
  5. requirements.txt +4 -1
.env.example ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # OpenAI API Key for TTS
2
+ OPENAI_API_KEY=your_openai_api_key_here
README.md CHANGED
@@ -21,6 +21,9 @@ short_description: 原tbdavid2019/PDF2podcast拆出的語音生成(2)
21
  - 🎚️ **模型選擇**:支援標準(tts-1)和高清(tts-1-hd)音頻模型
22
  - 🌐 **友好界面**:基於Gradio的簡潔網頁界面,易於使用
23
  - 💾 **自動文件管理**:自動保存生成的音頻並清理過期文件
 
 
 
24
 
25
  ## 安裝說明
26
 
@@ -28,6 +31,7 @@ short_description: 原tbdavid2019/PDF2podcast拆出的語音生成(2)
28
 
29
  - Python 3.7+
30
  - OpenAI API金鑰(需要啟用TTS功能)
 
31
 
32
  ### 安裝步驟
33
 
@@ -40,7 +44,22 @@ pip install -r requirements.txt
40
 
41
  ## 使用方法
42
 
43
- 1. 運行應用程式:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  ```bash
46
  python app.py
@@ -48,10 +67,62 @@ python app.py
48
 
49
  2. 在瀏覽器中打開顯示的URL(通常是 http://127.0.0.1:7860)
50
  3. 在文本框中輸入您的腳本
51
- 4. 輸入您的OpenAI API金鑰
52
  5. 選擇所需的音頻模型和說話者聲音
53
- 6. 點擊「生成音頻」按鈕
54
- 7. 等待處理完成後,您可以播放或下載生成的音頻
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  ## 腳本格式
57
 
@@ -73,6 +144,7 @@ speaker-1: 繼續對話...
73
  | 音頻模型 | 選擇TTS模型:標準(tts-1)或高清(tts-1-hd) |
74
  | 說話者1聲音 | 第一位說話者使用的聲音 |
75
  | 說話者2聲音 | 第二位說話者使用的聲音 |
 
76
  | OpenAI API Key | 您的OpenAI API金鑰 |
77
 
78
  ## 聲音選項
@@ -89,14 +161,20 @@ speaker-1: 繼續對話...
89
  ## 技術細節
90
 
91
  - 使用OpenAI的TTS API進行語音合成
92
- - 使用Gradio建立網頁界面
 
93
  - 自動管理臨時音頻文件(24小時後自動清理)
94
  - 支援流式處理大型音頻文件
 
95
 
96
  ## 依賴項
97
 
98
  - gradio: 網頁界面
99
  - openai: OpenAI API客戶端
 
 
 
 
100
  - pathlib: 文件路徑處理
101
  - io: 二進制數據處理
102
 
@@ -105,6 +183,8 @@ speaker-1: 繼續對話...
105
  - 使用此應用程式需要有效的OpenAI API金鑰
106
  - API使用會產生費用,請參考OpenAI的[價格頁面](https://openai.com/pricing)
107
  - 生成的臨時音頻文件會在24小時後自動刪除
 
 
108
 
109
  ## 授權信息
110
 
 
21
  - 🎚️ **模型選擇**:支援標準(tts-1)和高清(tts-1-hd)音頻模型
22
  - 🌐 **友好界面**:基於Gradio的簡潔網頁界面,易於使用
23
  - 💾 **自動文件管理**:自動保存生成的音頻並清理過期文件
24
+ - 🔊 **音量調整**:內建音量增益功能,可調整輸出音頻音量
25
+ - 🌍 **API支援**:提供獨立的API服務,支援外部應用程式呼叫
26
+ - 🔑 **環境變量**:支援通過.env文件配置API金鑰
27
 
28
  ## 安裝說明
29
 
 
31
 
32
  - Python 3.7+
33
  - OpenAI API金鑰(需要啟用TTS功能)
34
+ - 可選:創建`.env`文件存儲API金鑰(從`.env.example`複製並修改)
35
 
36
  ### 安裝步驟
37
 
 
44
 
45
  ## 使用方法
46
 
47
+ ### 環境設置
48
+
49
+ ```bash
50
+ # 複製環境變量範本
51
+ cp .env.example .env
52
+
53
+ # 編輯 .env 文件,添加您的 OpenAI API Key
54
+ nano .env # 或使用其他編輯器
55
+
56
+ # 安裝依賴項
57
+ pip install -r requirements.txt
58
+ ```
59
+
60
+ ### 通過網頁界面使用 (app.py)
61
+
62
+ 1. 運行Gradio應用程式:
63
 
64
  ```bash
65
  python app.py
 
67
 
68
  2. 在瀏覽器中打開顯示的URL(通常是 http://127.0.0.1:7860)
69
  3. 在文本框中輸入您的腳本
70
+ 4. 輸入您的OpenAI API金鑰(或預先在`.env`文件中配置)
71
  5. 選擇所需的音頻模型和說話者聲音
72
+ 6. 調整音量增益(建議值:6-10 dB)
73
+ 7. 點擊「生成音頻」按鈕
74
+ 8. 等待處理完成後,您可以播放或下載生成的音頻
75
+
76
+ ### 通過API使用 (api.py)
77
+
78
+ 如果您需要從外部應用程式呼叫TTS功能,可以使用API:
79
+
80
+ 1. 運行API服務:
81
+
82
+ ```bash
83
+ python api.py
84
+ ```
85
+
86
+ 2. API服務將在 http://localhost:8000 啟動
87
+
88
+ #### API端點
89
+
90
+ ##### 生成音頻
91
+
92
+ ```
93
+ POST /generate-audio
94
+ ```
95
+
96
+ 請求體示例:
97
+ ```json
98
+ {
99
+ "script": "speaker-1: 你好,歡迎來到播客!\nspeaker-2: 謝謝邀請,很高興來到這裡。",
100
+ "api_key": "your_openai_api_key",
101
+ "model": "tts-1",
102
+ "speaker1_voice": "onyx",
103
+ "speaker2_voice": "nova",
104
+ "volume_boost": 6.0,
105
+ "return_url": false
106
+ }
107
+ ```
108
+
109
+ - 如果`return_url`為`false`,將直接返回音頻文件
110
+ - 如果`return_url`為`true`,將返回音頻文件的URL
111
+
112
+ ##### 獲取可用選項
113
+
114
+ ```
115
+ GET /options
116
+ ```
117
+
118
+ 返回可用的音頻模型和聲音選項。
119
+
120
+ ##### API文檔
121
+
122
+ 啟動API服務後,可以訪問自動生成的API文檔:
123
+
124
+ - Swagger UI: http://localhost:8000/docs
125
+ - ReDoc: http://localhost:8000/redoc
126
 
127
  ## 腳本格式
128
 
 
144
  | 音頻模型 | 選擇TTS模型:標準(tts-1)或高清(tts-1-hd) |
145
  | 說話者1聲音 | 第一位說話者使用的聲音 |
146
  | 說話者2聲音 | 第二位說話者使用的聲音 |
147
+ | 音量增益 | 增加音頻音量的分貝值(dB),建議值:6-10 dB |
148
  | OpenAI API Key | 您的OpenAI API金鑰 |
149
 
150
  ## 聲音選項
 
161
  ## 技術細節
162
 
163
  - 使用OpenAI的TTS API進行語音合成
164
+ - 使用Gradio建立網頁界面(app.py)
165
+ - 使用FastAPI提供RESTful API(api.py)
166
  - 自動管理臨時音頻文件(24小時後自動清理)
167
  - 支援流式處理大型音頻文件
168
+ - 使用pydub處理音頻音量調整
169
 
170
  ## 依賴項
171
 
172
  - gradio: 網頁界面
173
  - openai: OpenAI API客戶端
174
+ - fastapi: API框架
175
+ - uvicorn: ASGI服務器
176
+ - pydub: 音頻處理
177
+ - python-dotenv: 環境變量管理
178
  - pathlib: 文件路徑處理
179
  - io: 二進制數據處理
180
 
 
183
  - 使用此應用程式需要有效的OpenAI API金鑰
184
  - API使用會產生費用,請參考OpenAI的[價格頁面](https://openai.com/pricing)
185
  - 生成的臨時音頻文件會在24小時後自動刪除
186
+ - 在Hugging Face Space上運行時,app.py會自動啟動,提供Gradio界面
187
+ - 如需API功能,需要單獨運行api.py
188
 
189
  ## 授權信息
190
 
api.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ from pathlib import Path
4
+ from tempfile import NamedTemporaryFile
5
+ import time
6
+ from typing import Optional
7
+ import uvicorn
8
+ from fastapi import FastAPI, HTTPException, Body
9
+ from fastapi.middleware.cors import CORSMiddleware
10
+ from fastapi.responses import FileResponse, JSONResponse
11
+ from pydantic import BaseModel
12
+ from dotenv import load_dotenv
13
+ from openai import OpenAI
14
+ from pydub import AudioSegment
15
+
16
+ # 加載環境變量
17
+ load_dotenv()
18
+
19
+ # 獲取 OpenAI API Key
20
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
21
+
22
+ # 標準音頻模型和聲音選項
23
+ STANDARD_AUDIO_MODELS = [
24
+ "tts-1",
25
+ "tts-1-hd",
26
+ ]
27
+ STANDARD_VOICES = [
28
+ "alloy",
29
+ "echo",
30
+ "fable",
31
+ "onyx",
32
+ "nova",
33
+ "shimmer",
34
+ ]
35
+
36
+ # 創建 FastAPI 應用
37
+ app = FastAPI(
38
+ title="TTS API",
39
+ description="API for generating audio from text using OpenAI TTS",
40
+ version="1.0.0"
41
+ )
42
+
43
+ # 添加 CORS 中間件
44
+ app.add_middleware(
45
+ CORSMiddleware,
46
+ allow_origins=["*"], # 允許所有來源,可以根據需要限制
47
+ allow_credentials=True,
48
+ allow_methods=["*"], # 允許所有方法
49
+ allow_headers=["*"], # 允許所有頭部
50
+ )
51
+
52
+ # 優化腳本處理 - 合併相同說話者連續文本
53
+ def optimize_script(script):
54
+ lines = [line.strip() for line in script.splitlines() if line.strip()]
55
+ optimized = []
56
+ current_speaker = None
57
+ current_text = ""
58
+
59
+ for line in lines:
60
+ if line.lower().startswith("speaker-1:"):
61
+ speaker = "speaker-1"
62
+ text = line.split(":", 1)[1].strip()
63
+ elif line.lower().startswith("speaker-2:"):
64
+ speaker = "speaker-2"
65
+ text = line.split(":", 1)[1].strip()
66
+ else:
67
+ speaker = "speaker-1" # 默認使用說話者1
68
+ text = line
69
+
70
+ # 如果說話者變了,保存之前的文本並開始新的
71
+ if speaker != current_speaker and current_text:
72
+ optimized.append((current_speaker, current_text))
73
+ current_text = text
74
+ current_speaker = speaker
75
+ else:
76
+ # 相同說話者,合併文本(加空格)
77
+ if current_text:
78
+ current_text += " " + text
79
+ else:
80
+ current_text = text
81
+ current_speaker = speaker
82
+
83
+ # 添加最後一個說話者的文本
84
+ if current_text:
85
+ optimized.append((current_speaker, current_text))
86
+
87
+ return optimized
88
+
89
+ def get_mp3(text: str, voice: str, audio_model: str, api_key: str) -> bytes:
90
+ """使用 OpenAI TTS API 生成音頻"""
91
+ client = OpenAI(api_key=api_key)
92
+ try:
93
+ with client.audio.speech.with_streaming_response.create(
94
+ model=audio_model,
95
+ voice=voice,
96
+ input=text,
97
+ ) as response:
98
+ with io.BytesIO() as file:
99
+ for chunk in response.iter_bytes():
100
+ file.write(chunk)
101
+ return file.getvalue()
102
+ except Exception as e:
103
+ print(f"Error generating audio: {e}")
104
+ raise
105
+
106
+ def generate_audio_from_script(
107
+ script: str,
108
+ audio_api_key: str,
109
+ audio_model: str = "tts-1",
110
+ speaker1_voice: str = "onyx",
111
+ speaker2_voice: str = "nova",
112
+ volume_boost: float = 0,
113
+ ) -> tuple[bytes, list]:
114
+ """從腳本生成音頻,支持兩個說話者,並優化 API 調用"""
115
+ combined_audio = b""
116
+ status_log = []
117
+
118
+ # 優化腳本處理
119
+ optimized_script = optimize_script(script)
120
+
121
+ # 處理每一段
122
+ for speaker, text in optimized_script:
123
+ voice_to_use = speaker1_voice if speaker == "speaker-1" else speaker2_voice
124
+ status_log.append(f"[{speaker}] {text}")
125
+
126
+ try:
127
+ # 生成這一段的音頻
128
+ audio_chunk = get_mp3(
129
+ text,
130
+ voice_to_use,
131
+ audio_model,
132
+ audio_api_key
133
+ )
134
+ combined_audio += audio_chunk
135
+ except Exception as e:
136
+ status_log.append(f"[錯誤] 無法生成音頻: {str(e)}")
137
+ raise HTTPException(status_code=500, detail=f"無法生成音頻: {str(e)}")
138
+
139
+ # 如果需要調整音量
140
+ if volume_boost > 0:
141
+ try:
142
+ # 將二進制數據轉換為 AudioSegment
143
+ with NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
144
+ temp_file.write(combined_audio)
145
+ temp_file_path = temp_file.name
146
+
147
+ # 讀取音頻並調整音量
148
+ audio = AudioSegment.from_mp3(temp_file_path)
149
+ audio = audio + volume_boost # 增加音量 (dB)
150
+
151
+ # 將調整後的音頻轉換回二進制數據
152
+ output = io.BytesIO()
153
+ audio.export(output, format="mp3")
154
+ combined_audio = output.getvalue()
155
+
156
+ # 刪除臨時文件
157
+ os.unlink(temp_file_path)
158
+
159
+ status_log.append(f"[音量] 已增加 {volume_boost} dB")
160
+ except Exception as e:
161
+ status_log.append(f"[警告] 音量調整失敗: {str(e)}")
162
+
163
+ return combined_audio, status_log
164
+
165
+ def save_audio_file(audio_data: bytes) -> str:
166
+ """將音頻數據保存為臨時文件"""
167
+ temp_dir = Path("./temp_audio")
168
+ temp_dir.mkdir(exist_ok=True)
169
+ # 清理舊文件
170
+ for old_file in temp_dir.glob("*.mp3"):
171
+ if old_file.stat().st_mtime < (time.time() - 24*60*60): # 24小時前的文件
172
+ old_file.unlink()
173
+ # 創建新的臨時文件
174
+ temp_file = NamedTemporaryFile(
175
+ dir=temp_dir,
176
+ delete=False,
177
+ suffix=".mp3"
178
+ )
179
+ temp_file.write(audio_data)
180
+ temp_file.close()
181
+ return temp_file.name
182
+
183
+ # 定義請求模型
184
+ class TTSRequest(BaseModel):
185
+ script: str
186
+ api_key: Optional[str] = None
187
+ model: Optional[str] = "tts-1"
188
+ speaker1_voice: Optional[str] = "onyx"
189
+ speaker2_voice: Optional[str] = "nova"
190
+ volume_boost: Optional[float] = 6.0
191
+ return_url: Optional[bool] = False
192
+
193
+ # API 端點
194
+ @app.post("/generate-audio")
195
+ async def generate_audio(request: TTSRequest):
196
+ """
197
+ 生成音頻 API 端點
198
+
199
+ - **script**: 腳本內容,格式為 "speaker-1: 文本" 或 "speaker-2: 文本"
200
+ - **api_key**: OpenAI API Key (可選,如果未提供則使用環境變量)
201
+ - **model**: 音頻模型 (可選,默認為 "tts-1")
202
+ - **speaker1_voice**: 說話者1的聲音 (可選,默認為 "onyx")
203
+ - **speaker2_voice**: 說話者2的聲音 (可選,默認為 "nova")
204
+ - **volume_boost**: 音量增益 dB (可選,默認為 6.0)
205
+ - **return_url**: 是否返回音頻文件的 URL (可選,默認為 False)
206
+ """
207
+ # 使用提供的 API Key 或環境變量中的 API Key
208
+ api_key = request.api_key or OPENAI_API_KEY
209
+
210
+ if not api_key:
211
+ raise HTTPException(status_code=400, detail="未提供 OpenAI API Key")
212
+
213
+ try:
214
+ # 生成音頻
215
+ audio_data, status_log = generate_audio_from_script(
216
+ request.script,
217
+ api_key,
218
+ request.model,
219
+ request.speaker1_voice,
220
+ request.speaker2_voice,
221
+ request.volume_boost
222
+ )
223
+
224
+ # 保存音頻文件
225
+ audio_path = save_audio_file(audio_data)
226
+
227
+ # 根據請求返回不同的響應
228
+ if request.return_url:
229
+ # 構建文件 URL (相對路徑)
230
+ file_name = os.path.basename(audio_path)
231
+ file_url = f"/audio/{file_name}"
232
+
233
+ return {
234
+ "status": "success",
235
+ "message": "音頻生成成功",
236
+ "audio_url": file_url,
237
+ "logs": status_log
238
+ }
239
+ else:
240
+ # 直接返回文件
241
+ return FileResponse(
242
+ audio_path,
243
+ media_type="audio/mpeg",
244
+ filename="generated_audio.mp3"
245
+ )
246
+
247
+ except Exception as e:
248
+ raise HTTPException(status_code=500, detail=f"生成音頻時發生錯誤: {str(e)}")
249
+
250
+ # 獲取音頻文件的端點
251
+ @app.get("/audio/{file_name}")
252
+ async def get_audio(file_name: str):
253
+ """獲取生成的音頻文件"""
254
+ file_path = Path(f"./temp_audio/{file_name}")
255
+
256
+ if not file_path.exists():
257
+ raise HTTPException(status_code=404, detail="音頻文件不存在")
258
+
259
+ return FileResponse(
260
+ file_path,
261
+ media_type="audio/mpeg",
262
+ filename="generated_audio.mp3"
263
+ )
264
+
265
+ # 獲取可用的音頻模型和聲音選項
266
+ @app.get("/options")
267
+ async def get_options():
268
+ """獲取可用的音頻模型和聲音選項"""
269
+ return {
270
+ "models": STANDARD_AUDIO_MODELS,
271
+ "voices": STANDARD_VOICES
272
+ }
273
+
274
+ # 健康檢查端點
275
+ @app.get("/health")
276
+ async def health_check():
277
+ """API 健康檢查"""
278
+ return {"status": "healthy", "api_version": "1.0.0"}
279
+
280
+ # 主程序
281
+ if __name__ == "__main__":
282
+ # 啟動 API 服務器
283
+ uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)
app.py CHANGED
@@ -6,6 +6,13 @@ import time
6
  import gradio as gr
7
  from openai import OpenAI
8
  from pydub import AudioSegment
 
 
 
 
 
 
 
9
 
10
  # 标准音频模型和声音选项
11
  STANDARD_AUDIO_MODELS = [
 
6
  import gradio as gr
7
  from openai import OpenAI
8
  from pydub import AudioSegment
9
+ from dotenv import load_dotenv
10
+
11
+ # 加載環境變量
12
+ load_dotenv()
13
+
14
+ # 獲取 OpenAI API Key (如果在環境變量中設置了)
15
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
16
 
17
  # 标准音频模型和声音选项
18
  STANDARD_AUDIO_MODELS = [
requirements.txt CHANGED
@@ -5,4 +5,7 @@ loguru
5
  promptic
6
  tenacity
7
  bs4
8
- pydub
 
 
 
 
5
  promptic
6
  tenacity
7
  bs4
8
+ pydub
9
+ fastapi
10
+ uvicorn
11
+ python-dotenv