Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -18,6 +18,8 @@ import json
|
|
| 18 |
from urllib.parse import urlparse
|
| 19 |
import tempfile
|
| 20 |
import shutil
|
|
|
|
|
|
|
| 21 |
|
| 22 |
app = FastAPI(
|
| 23 |
title="Edge TTS API",
|
|
@@ -38,6 +40,11 @@ app.add_middleware(
|
|
| 38 |
OUTPUT_DIR = tempfile.mkdtemp(prefix="edge_tts_")
|
| 39 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
# 掛載靜態文件(如果存在)
|
| 42 |
if os.path.exists("static"):
|
| 43 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
@@ -61,6 +68,71 @@ def _is_origin_allowed(request: Request) -> bool:
|
|
| 61 |
# 在 Hugging Face Spaces 環境中,允許所有來源
|
| 62 |
return True
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
@app.get("/")
|
| 65 |
async def root():
|
| 66 |
"""根路徑,返回 API 信息"""
|
|
@@ -71,9 +143,13 @@ async def root():
|
|
| 71 |
"GET /voices": "獲取所有可用語音",
|
| 72 |
"POST /tts": "文字轉語音",
|
| 73 |
"GET /tts": "文字轉語音 (GET 方法)",
|
| 74 |
-
"GET /health": "健康檢查"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
},
|
| 76 |
-
"note": "此服務部署在 Hugging Face Spaces 上"
|
| 77 |
}
|
| 78 |
|
| 79 |
@app.get("/health")
|
|
@@ -235,6 +311,61 @@ async def delete_audio_file(file_id: str):
|
|
| 235 |
except Exception as e:
|
| 236 |
raise HTTPException(status_code=500, detail=f"文件刪除失敗: {str(e)}")
|
| 237 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
# 清理函數(可選)
|
| 239 |
@app.on_event("shutdown")
|
| 240 |
async def cleanup():
|
|
|
|
| 18 |
from urllib.parse import urlparse
|
| 19 |
import tempfile
|
| 20 |
import shutil
|
| 21 |
+
import time
|
| 22 |
+
from datetime import datetime, timedelta
|
| 23 |
|
| 24 |
app = FastAPI(
|
| 25 |
title="Edge TTS API",
|
|
|
|
| 40 |
OUTPUT_DIR = tempfile.mkdtemp(prefix="edge_tts_")
|
| 41 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 42 |
|
| 43 |
+
# 檔案清理設定
|
| 44 |
+
FILE_CLEANUP_INTERVAL = 300 # 5分鐘清理一次
|
| 45 |
+
FILE_MAX_AGE = 1800 # 檔案最大存活時間:30分鐘
|
| 46 |
+
MAX_FILES = 100 # 最大檔案數量
|
| 47 |
+
|
| 48 |
# 掛載靜態文件(如果存在)
|
| 49 |
if os.path.exists("static"):
|
| 50 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
| 68 |
# 在 Hugging Face Spaces 環境中,允許所有來源
|
| 69 |
return True
|
| 70 |
|
| 71 |
+
# 檔案清理函數
|
| 72 |
+
def cleanup_old_files():
|
| 73 |
+
"""清理過期的音頻檔案"""
|
| 74 |
+
try:
|
| 75 |
+
if not os.path.exists(OUTPUT_DIR):
|
| 76 |
+
return
|
| 77 |
+
|
| 78 |
+
current_time = time.time()
|
| 79 |
+
files = []
|
| 80 |
+
|
| 81 |
+
# 獲取所有檔案及其修改時間
|
| 82 |
+
for filename in os.listdir(OUTPUT_DIR):
|
| 83 |
+
if filename.endswith('.mp3'):
|
| 84 |
+
file_path = os.path.join(OUTPUT_DIR, filename)
|
| 85 |
+
file_mtime = os.path.getmtime(file_path)
|
| 86 |
+
files.append((file_path, file_mtime, filename))
|
| 87 |
+
|
| 88 |
+
# 按修改時間排序(最新的在前)
|
| 89 |
+
files.sort(key=lambda x: x[1], reverse=True)
|
| 90 |
+
|
| 91 |
+
deleted_count = 0
|
| 92 |
+
|
| 93 |
+
# 刪除過期檔案
|
| 94 |
+
for file_path, file_mtime, filename in files:
|
| 95 |
+
file_age = current_time - file_mtime
|
| 96 |
+
|
| 97 |
+
# 如果檔案超過最大存活時間,刪除它
|
| 98 |
+
if file_age > FILE_MAX_AGE:
|
| 99 |
+
try:
|
| 100 |
+
os.remove(file_path)
|
| 101 |
+
deleted_count += 1
|
| 102 |
+
print(f"刪除過期檔案: {filename}")
|
| 103 |
+
except Exception as e:
|
| 104 |
+
print(f"刪除檔案失敗 {filename}: {e}")
|
| 105 |
+
|
| 106 |
+
# 如果檔案數量超過限制,刪除最舊的檔案
|
| 107 |
+
remaining_files = len(files) - deleted_count
|
| 108 |
+
if remaining_files > MAX_FILES:
|
| 109 |
+
files_to_delete = files[MAX_FILES:]
|
| 110 |
+
for file_path, _, filename in files_to_delete:
|
| 111 |
+
try:
|
| 112 |
+
if os.path.exists(file_path):
|
| 113 |
+
os.remove(file_path)
|
| 114 |
+
deleted_count += 1
|
| 115 |
+
print(f"刪除超量檔案: {filename}")
|
| 116 |
+
except Exception as e:
|
| 117 |
+
print(f"刪除檔案失敗 {filename}: {e}")
|
| 118 |
+
|
| 119 |
+
if deleted_count > 0:
|
| 120 |
+
print(f"清理完成,共刪除 {deleted_count} 個檔案")
|
| 121 |
+
|
| 122 |
+
except Exception as e:
|
| 123 |
+
print(f"檔案清理過程中發生錯誤: {e}")
|
| 124 |
+
|
| 125 |
+
# 背景清理任務
|
| 126 |
+
async def background_cleanup():
|
| 127 |
+
"""背景檔案清理任務"""
|
| 128 |
+
while True:
|
| 129 |
+
try:
|
| 130 |
+
cleanup_old_files()
|
| 131 |
+
await asyncio.sleep(FILE_CLEANUP_INTERVAL)
|
| 132 |
+
except Exception as e:
|
| 133 |
+
print(f"背景清理任務錯誤: {e}")
|
| 134 |
+
await asyncio.sleep(60) # 錯誤時等待1分鐘再重試
|
| 135 |
+
|
| 136 |
@app.get("/")
|
| 137 |
async def root():
|
| 138 |
"""根路徑,返回 API 信息"""
|
|
|
|
| 143 |
"GET /voices": "獲取所有可用語音",
|
| 144 |
"POST /tts": "文字轉語音",
|
| 145 |
"GET /tts": "文字轉語音 (GET 方法)",
|
| 146 |
+
"GET /health": "健康檢查",
|
| 147 |
+
"GET /audio/{file_id}.mp3": "獲取音頻文件",
|
| 148 |
+
"DELETE /audio/{file_id}.mp3": "刪除音頻文件",
|
| 149 |
+
"POST /cleanup": "手動觸發檔案清理",
|
| 150 |
+
"GET /storage-info": "獲取儲存空間資訊"
|
| 151 |
},
|
| 152 |
+
"note": "此服務部署在 Hugging Face Spaces 上,具有自動檔案清理功能"
|
| 153 |
}
|
| 154 |
|
| 155 |
@app.get("/health")
|
|
|
|
| 311 |
except Exception as e:
|
| 312 |
raise HTTPException(status_code=500, detail=f"文件刪除失敗: {str(e)}")
|
| 313 |
|
| 314 |
+
@app.post("/cleanup")
|
| 315 |
+
async def manual_cleanup():
|
| 316 |
+
"""手動觸發檔案清理"""
|
| 317 |
+
try:
|
| 318 |
+
cleanup_old_files()
|
| 319 |
+
return {"success": True, "message": "手動清理完成"}
|
| 320 |
+
except Exception as e:
|
| 321 |
+
raise HTTPException(status_code=500, detail=f"清理失敗: {str(e)}")
|
| 322 |
+
|
| 323 |
+
@app.get("/storage-info")
|
| 324 |
+
async def get_storage_info():
|
| 325 |
+
"""獲取儲存空間資訊"""
|
| 326 |
+
try:
|
| 327 |
+
if not os.path.exists(OUTPUT_DIR):
|
| 328 |
+
return {"files": 0, "total_size": 0, "max_files": MAX_FILES, "max_age": FILE_MAX_AGE}
|
| 329 |
+
|
| 330 |
+
files = []
|
| 331 |
+
total_size = 0
|
| 332 |
+
|
| 333 |
+
for filename in os.listdir(OUTPUT_DIR):
|
| 334 |
+
if filename.endswith('.mp3'):
|
| 335 |
+
file_path = os.path.join(OUTPUT_DIR, filename)
|
| 336 |
+
file_size = os.path.getsize(file_path)
|
| 337 |
+
file_mtime = os.path.getmtime(file_path)
|
| 338 |
+
file_age = time.time() - file_mtime
|
| 339 |
+
|
| 340 |
+
files.append({
|
| 341 |
+
"filename": filename,
|
| 342 |
+
"size": file_size,
|
| 343 |
+
"age_seconds": int(file_age),
|
| 344 |
+
"age_minutes": int(file_age / 60)
|
| 345 |
+
})
|
| 346 |
+
total_size += file_size
|
| 347 |
+
|
| 348 |
+
return {
|
| 349 |
+
"files": len(files),
|
| 350 |
+
"total_size": total_size,
|
| 351 |
+
"total_size_mb": round(total_size / (1024 * 1024), 2),
|
| 352 |
+
"max_files": MAX_FILES,
|
| 353 |
+
"max_age_seconds": FILE_MAX_AGE,
|
| 354 |
+
"max_age_minutes": int(FILE_MAX_AGE / 60),
|
| 355 |
+
"cleanup_interval_seconds": FILE_CLEANUP_INTERVAL,
|
| 356 |
+
"cleanup_interval_minutes": int(FILE_CLEANUP_INTERVAL / 60),
|
| 357 |
+
"file_list": files[:10] # 只顯示前10個檔案
|
| 358 |
+
}
|
| 359 |
+
except Exception as e:
|
| 360 |
+
raise HTTPException(status_code=500, detail=f"獲取儲存資訊失敗: {str(e)}")
|
| 361 |
+
|
| 362 |
+
# 應用啟動事件
|
| 363 |
+
@app.on_event("startup")
|
| 364 |
+
async def startup_event():
|
| 365 |
+
"""應用啟動時啟動背景清理任務"""
|
| 366 |
+
asyncio.create_task(background_cleanup())
|
| 367 |
+
print("背景檔案清理任務已啟動")
|
| 368 |
+
|
| 369 |
# 清理函數(可選)
|
| 370 |
@app.on_event("shutdown")
|
| 371 |
async def cleanup():
|