Upload 5 files
Browse files- cookie/19245776972.txt +1 -0
- tts-server.py +63 -12
cookie/19245776972.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
_ga=GA1.1.437012191.1745432605; d_ticket=7ef9a4bada1454fd46e0dc3fb5d3bbf7af1c9; flow_user_country=CN; _gcl_aw=GCL.1758716523.Cj0KCQjwrc7GBhCfARIsAHGcW5X3vlZenZizh4uxDAOeWTzQDdrT7JOqk-0_Toh0-nyTI98ilypZKRgaAjAREALw_wcB; _gcl_gs=2.1.k1$i1758716513$u91292815; gd_random=eyJtYXRjaCI6dHJ1ZSwicGVyY2VudCI6MC4yNTI2NzkwMDcwMDM2NzQyfQ==.RGhswbjyDU6o266pLNQGIlPw2Y8mDLztp4TGXAWMlcQ=; flow_ssr_sidebar_expand=1; s_v_web_id=verify_mind1jr9_VbD7t7qj_uikr_43bO_8n0b_zX2SCB6AdbOM; passport_csrf_token=32bfed5a75c5103dc3733669211dde21; passport_csrf_token_default=32bfed5a75c5103dc3733669211dde21; odin_tt=7f7bdbabac871052b33748b916fe0d9eeaee8805d718e032dc76eb3331e69248d07b38c4a514cd3ec554bfd56a88948f8d4623af808c742c18c899d4244d4d78; n_mh=8tLRwoSVKOVBgyNwo1ffSlHLPFA-RVJfi6Mvd4NuGM0; sid_guard=3a913278b150423698eda5269004a756%7C1764606680%7C2592000%7CWed%2C+31-Dec-2025+16%3A31%3A20+GMT; uid_tt=d6dad1722ee632bb79eabc2375f0ca93; uid_tt_ss=d6dad1722ee632bb79eabc2375f0ca93; sid_tt=3a913278b150423698eda5269004a756; sessionid=3a913278b150423698eda5269004a756; sessionid_ss=3a913278b150423698eda5269004a756; session_tlb_tag=sttt%7C16%7COpEyeLFQQjaY7aUmkASnVv________-1-1P7zRCjTPIPFv_pUUIHMZVYQsjlpXZD0w_8D__sD7Y%3D; session_tlb_tag_bk=sttt%7C16%7COpEyeLFQQjaY7aUmkASnVv________-1-1P7zRCjTPIPFv_pUUIHMZVYQsjlpXZD0w_8D__sD7Y%3D; is_staff_user=false; sid_ucp_v1=1.0.0-KGMzY2FkMTZkNzkyYjAxZDY3OTFlM2Q0MzYzY2UzNzE0OWRkMDhiZDUKIAiLzpCpts3aAxDYhbfJBhjCsR4gDDCqpdmtBjgHQPQHGgJobCIgM2E5MTMyNzhiMTUwNDIzNjk4ZWRhNTI2OTAwNGE3NTY; ssid_ucp_v1=1.0.0-KGMzY2FkMTZkNzkyYjAxZDY3OTFlM2Q0MzYzY2UzNzE0OWRkMDhiZDUKIAiLzpCpts3aAxDYhbfJBhjCsR4gDDCqpdmtBjgHQPQHGgJobCIgM2E5MTMyNzhiMTUwNDIzNjk4ZWRhNTI2OTAwNGE3NTY; user_language_code=zh; i18next=zh; ttwid=1%7CUWvZl8BhJG24HsPYG-AMSH425URWFERYswXeSG5o7JA%7C1764606681%7Cc06758733f55438250cd0bcd4655efd74e6c85bbaec7b29a4e0993cd7dac2c8f; passport_fe_beating_status=true; msToken=uMPTw6l1KqvcVnqkxi14Lxk9_77FWhrXrKQZEraq2s9cX4hnMNVW-JkviHvPlEgeh2qEDosTsKmlapgsIKH6SEC6PVrpWl1uzRR1hBBGvcSTL_bnIyoAE2fiD0SmV_QBIrHtxQhAeE8-; _ga_G8EP5CG8VZ=GS2.1.s1764606227$o73$g1$t1764606691$j51$l0$h0; tt_scid=t6ZrzYZXXDmFGF8bsZTLqjggfDFJ0m4ulDTSSZExH52G.8iucd7OjH.2oqhCdHPx0472
|
tts-server.py
CHANGED
|
@@ -8,6 +8,7 @@ import time
|
|
| 8 |
import glob
|
| 9 |
from typing import Optional, List, Dict, Any, AsyncGenerator
|
| 10 |
|
|
|
|
| 11 |
import aiohttp
|
| 12 |
import websockets
|
| 13 |
from fastapi import FastAPI, HTTPException, Header, Request, BackgroundTasks
|
|
@@ -32,6 +33,7 @@ PORT = 1547
|
|
| 32 |
HOST = "0.0.0.0"
|
| 33 |
MODELS_FILE = "models.json"
|
| 34 |
COOKIE_DIR = "cookie" # Directory to store cookie txt files
|
|
|
|
| 35 |
|
| 36 |
# Initialize FastAPI
|
| 37 |
@asynccontextmanager
|
|
@@ -110,6 +112,11 @@ class CookieManager:
|
|
| 110 |
if self.failure_count >= 2:
|
| 111 |
self._rotate()
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
def report_success(self):
|
| 114 |
"""Reset failure count on success."""
|
| 115 |
if self.failure_count > 0:
|
|
@@ -239,6 +246,13 @@ class DoubaoTTS:
|
|
| 239 |
async def stream_audio(self, text: str, voice: str, speed: float = 1.0, pitch: float = 1.0) -> AsyncGenerator[bytes, None]:
|
| 240 |
"""Connect to WebSocket and yield audio chunks with retry logic."""
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
# Map OpenAI speed (0.25 - 4.0) to Doubao rate (-100 to 100)
|
| 243 |
doubao_rate = int((speed - 1) * 100)
|
| 244 |
doubao_rate = max(-100, min(100, doubao_rate))
|
|
@@ -275,19 +289,56 @@ class DoubaoTTS:
|
|
| 275 |
|
| 276 |
first_chunk_received = False
|
| 277 |
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
self.cookie_manager.report_success() # Mark cookie as good
|
| 283 |
-
yield message
|
| 284 |
-
elif isinstance(message, str):
|
| 285 |
-
# Check for error messages in text frames
|
| 286 |
-
# Sometimes errors come as text JSON
|
| 287 |
-
pass
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
except websockets.exceptions.InvalidStatusCode as e:
|
| 293 |
logger.warning(f"WebSocket Handshake failed (Attempt {attempt+1}): {e.status_code}")
|
|
|
|
| 8 |
import glob
|
| 9 |
from typing import Optional, List, Dict, Any, AsyncGenerator
|
| 10 |
|
| 11 |
+
import aiofiles
|
| 12 |
import aiohttp
|
| 13 |
import websockets
|
| 14 |
from fastapi import FastAPI, HTTPException, Header, Request, BackgroundTasks
|
|
|
|
| 33 |
HOST = "0.0.0.0"
|
| 34 |
MODELS_FILE = "models.json"
|
| 35 |
COOKIE_DIR = "cookie" # Directory to store cookie txt files
|
| 36 |
+
AUDIO_DIR = "saved_audio"
|
| 37 |
|
| 38 |
# Initialize FastAPI
|
| 39 |
@asynccontextmanager
|
|
|
|
| 112 |
if self.failure_count >= 2:
|
| 113 |
self._rotate()
|
| 114 |
|
| 115 |
+
def force_rotate(self):
|
| 116 |
+
"""Force switch to the next cookie (e.g. when blocked)."""
|
| 117 |
+
logger.warning(f"Cookie index {self.current_index} blocked/rate-limited. Forcing rotation.")
|
| 118 |
+
self._rotate()
|
| 119 |
+
|
| 120 |
def report_success(self):
|
| 121 |
"""Reset failure count on success."""
|
| 122 |
if self.failure_count > 0:
|
|
|
|
| 246 |
async def stream_audio(self, text: str, voice: str, speed: float = 1.0, pitch: float = 1.0) -> AsyncGenerator[bytes, None]:
|
| 247 |
"""Connect to WebSocket and yield audio chunks with retry logic."""
|
| 248 |
|
| 249 |
+
# Ensure audio directory exists
|
| 250 |
+
if not os.path.exists(AUDIO_DIR):
|
| 251 |
+
try:
|
| 252 |
+
os.makedirs(AUDIO_DIR, exist_ok=True)
|
| 253 |
+
except Exception as e:
|
| 254 |
+
logger.error(f"Failed to create audio directory: {e}")
|
| 255 |
+
|
| 256 |
# Map OpenAI speed (0.25 - 4.0) to Doubao rate (-100 to 100)
|
| 257 |
doubao_rate = int((speed - 1) * 100)
|
| 258 |
doubao_rate = max(-100, min(100, doubao_rate))
|
|
|
|
| 289 |
|
| 290 |
first_chunk_received = False
|
| 291 |
|
| 292 |
+
# Prepare file for saving this attempt
|
| 293 |
+
timestamp = int(time.time() * 1000)
|
| 294 |
+
filename = f"{timestamp}_{voice}.aac"
|
| 295 |
+
filepath = os.path.join(AUDIO_DIR, filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
+
file_written = False
|
| 298 |
+
try:
|
| 299 |
+
async with aiofiles.open(filepath, "wb") as f_out:
|
| 300 |
+
async for message in ws:
|
| 301 |
+
if isinstance(message, bytes):
|
| 302 |
+
if not first_chunk_received:
|
| 303 |
+
first_chunk_received = True
|
| 304 |
+
self.cookie_manager.report_success() # Mark cookie as good
|
| 305 |
+
logger.info(f"Streaming and saving audio to: {filepath}")
|
| 306 |
+
|
| 307 |
+
yield message
|
| 308 |
+
await f_out.write(message)
|
| 309 |
+
file_written = True
|
| 310 |
+
|
| 311 |
+
elif isinstance(message, str):
|
| 312 |
+
# Check for error messages in text frames
|
| 313 |
+
try:
|
| 314 |
+
msg_json = json.loads(message)
|
| 315 |
+
code = msg_json.get("code")
|
| 316 |
+
if code and code != 0:
|
| 317 |
+
error_msg = msg_json.get("message", "Unknown Error")
|
| 318 |
+
logger.error(f"Doubao API Error (Code {code}): {error_msg}")
|
| 319 |
+
|
| 320 |
+
# If blocked or auth failed, report failure to rotate cookie
|
| 321 |
+
if "block" in str(error_msg).lower() or code == 710022002:
|
| 322 |
+
self.cookie_manager.force_rotate() # Force rotate immediately on block
|
| 323 |
+
raise Exception(f"Blocked by server (Risk Control): {error_msg}")
|
| 324 |
+
except json.JSONDecodeError:
|
| 325 |
+
pass
|
| 326 |
+
|
| 327 |
+
# If we finished the loop naturally, break retry loop
|
| 328 |
+
if file_written:
|
| 329 |
+
return
|
| 330 |
+
else:
|
| 331 |
+
# If no bytes were written, something went wrong but no exception was raised
|
| 332 |
+
logger.warning("Connection closed without receiving audio data.")
|
| 333 |
+
|
| 334 |
+
finally:
|
| 335 |
+
# Cleanup empty files if failed
|
| 336 |
+
if not file_written and os.path.exists(filepath):
|
| 337 |
+
try:
|
| 338 |
+
os.remove(filepath)
|
| 339 |
+
logger.info(f"Removed empty audio file: {filepath}")
|
| 340 |
+
except Exception as e:
|
| 341 |
+
logger.error(f"Failed to remove empty file: {e}")
|
| 342 |
|
| 343 |
except websockets.exceptions.InvalidStatusCode as e:
|
| 344 |
logger.warning(f"WebSocket Handshake failed (Attempt {attempt+1}): {e.status_code}")
|