TTS API Python: เตรียมข้อความสำหรับเสียงด้วย SDK
API นี้ไม่ส่งคืนเสียง เป็นโมเดลข้อความที่คุณเรียกใช้จาก Python ด้วย OpenAI SDK และทำงานอยู่หน้าเครื่องสังเคราะห์เสียง: API เขียนสคริปต์ ทำความสะอาด และแบ่งเป็นประโยคที่ขั้นตอนเสียงของคุณสามารถอ่านออกเสียงได้
อัปเดต

สิ่งที่ API นี้ทำได้และไม่ได้ทำ
หากคุณค้นหาบทเรียน tts api python จุดหนึ่งที่ต้องชัดเจนก่อนเริ่มเขียนโค้ดคือ บริการนี้ไม่สร้างเสียงพูด มันเปิดเผย POST /v1/chat/completions และ GET /v1/models สำหรับโมเดลข้อความแบบเปิดน้ำหนักหนึ่งโมเดล โดยมีโมเดล ID คือ uncensored ไม่มีเอนด์พอยต์สำหรับเสียง ไม่มีรายการเสียง และไม่มีไฟล์เสียงในคำตอบใดๆ ทั้งสิ้น นอกจากนี้ยังไม่มีเอาต์พุตภาพ วิดีโอ หรือการฝังตัว
แล้วมันอยู่ตรงไหนในโปรเจกต์เสียง? อยู่ในส่วนของข้อความในสายงาน ก่อนที่เครื่องสังเคราะห์เสียงจะอ่านสคริปต์ มีคนต้องเขียนสคริปต์ ขยายตัวเลขและคำย่อให้เป็นคำที่ออกเสียงได้ ลบมาร์กอัปออก และตัดเป็นท่อนที่สั้นพอที่จะสังเคราะห์ได้โดยไม่มีการหยุดพักนาน โมเดลภาษาทำงานส่วนนี้ได้ดี และคุณสามารถเรียกใช้จาก Python ด้วย SDK เดียวกับที่คุณอาจใช้สำหรับบริการที่เข้ากันได้กับ OpenAI อื่นๆ
โมเดลถูกปรับให้ตอบโดยไม่ปฏิเสธเนื้อหาสำหรับการใช้งานผู้ใหญ่ที่ถูกต้องตามกฎหมาย ซึ่งสำคัญสำหรับนิยาย บทพูดตัวละคร และการบรรยายที่โมเดลเข้มงวดกว่าอาจหยุดกลางฉาก กฎข้อหนึ่งคือ: ไม่มีเนื้อหาทางเพศที่เกี่ยวข้องกับผู้เยาว์
ส่วนที่เหลือของบทความนี้จะสร้างโมดูล Python เล็กๆ ในสี่ขั้นตอน: การเรียกครั้งแรก ตัวแบ่งประโยคแบบสตรีมมิง ตัวปรับมาตรฐานข้อความพร้อมเอาต์พุต JSON และตัวห่อการเรียกซ้ำที่เคารพขีดจำกัดอัตรา
ติดตั้ง SDK และทำการเรียกครั้งแรก
คุณต้อง Python 3.8 ขึ้นไปและแพ็กเกจ openai ทางการ สร้างคีย์ API ที่หน้า API key ก่อน บัญชีใหม่สามารถขอเครดิตทดลองใช้ $0.50 ได้ด้วยคลิกเดียว และไม่ต้องใช้บัตรสำหรับสิ่งนี้
pip install openai
เก็บคีย์ไว้จากซอร์สโค้ดของคุณ ส่งออกเป็นตัวแปรสภาพแวดล้อมและอ่านค่าในโค้ด:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.xtts.cc/v1",
api_key=os.environ["XTTS_API_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You write short narration scripts. Plain sentences only."},
{"role": "user", "content": "Write a 60-word intro for a podcast about night trains."},
],
temperature=0.8,
)
script = resp.choices[0].message.content
print(script)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
นั่นคือการผสานรวมทั้งหมด SDK จะส่ง Authorization: Bearer <key> ให้คุณเอง ทุกการตอบกลับยังมี X-Balance-USD header เพื่อให้คุณสามารถอ่านเครดิตคงเหลือจากการตอบกลับดิบได้หากต้องการบันทึก หน้าเอกสาร ระบุ header และพารามิเตอร์อื่นๆ
พารามิเตอร์เช่น temperature, top_p, stop, seed และค่าปรับจะถูกส่งผ่าน สำหรับการเล่าเรื่อง ค่า temperature ระหว่าง 0.6 ถึง 0.9 เป็นช่วงเริ่มต้นที่เหมาะสม และ seed คงที่ช่วยเมื่อคุณต้องการสร้างการบันทึกซ้ำขณะปรับพรอมต์
สตรีมสคริปต์และตัดเป็นประโยค
เครื่องสังเคราะห์เสียงมักทำงานทีละประโยค หากคุณรอให้สคริปต์ 900 คำเสร็จ ผู้ฟังก็รอไปด้วย ด้วย stream: true API จะส่งโทเคนผ่าน server-sent events และคุณสามารถส่งประโยคที่เสร็จแล้วไปยังขั้นตอนเสียงขณะที่ส่วนอื่นยังกำลังเขียนอยู่
import os
import re
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
SENTENCE_END = re.compile(r"(?<=[.?])\s+")
def stream_sentences(topic):
stream = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "Write spoken narration. One idea per sentence."},
{"role": "user", "content": topic},
],
stream=True,
)
buffer = ""
for chunk in stream:
if not chunk.choices:
continue # the final chunk can carry usage only
buffer += chunk.choices[0].delta.content or ""
parts = SENTENCE_END.split(buffer)
for sentence in parts[:-1]:
yield sentence.strip()
buffer = parts[-1]
if buffer.strip():
yield buffer.strip()
for line in stream_sentences("Explain how a tide table is read."):
print(line) # replace with: your_voice_engine.say(line)
รายละเอียดสองจุดที่มักมองข้าม ประการแรก ตัวเลขการใช้งานจะมาที่ท้ายสตรีม ในชิ้นข้อมูลที่มีรายการ choices ว่าง ซึ่งทำให้ลูปข้ามชิ้นข้อมูลที่ไม่มี choices ประการที่สอง คำขอแบบสตรีมจะครอบครองช่องความพร้อมใช้งานพร้อมกันจนกว่าสตรีมจะสิ้นสุด หากเครื่องมือเสียงของคุณช้าและคุณอ่านสตรีมแบบล่าช้า ช่องนั้นจะยังคงไม่ว่าง ให้ดึงข้อความออกอย่างรวดเร็ว วางประโยคในคิว และให้ฝั่งเสียงบริโภคคิวตามจังหวะของมัน
นิพจน์ปกติด้านบนแบ่งตามจุดหรือเครื่องหมายคำถามตามด้วยช่องว่าง มันจะแบ่งผิดหลังคำย่อเช่น "Dr." หรือ "No." ซึ่งเป็นเหตุผลเพิ่มเติมในการปรับมาตรฐานข้อความก่อน ตามที่ส่วนถัดไปแสดง
ปรับมาตรฐานข้อความสำหรับหู
ข้อความเขียนและข้อความพูดแตกต่างกัน "Dr. Lee ซื้อ 3 เล่มเมื่อวันที่ 3/4 สำหรับฉบับที่ 2" อ่านง่ายแต่พูดยาก เครื่องมือสังเคราะห์เสียงหลายตัวจัดการส่วนนี้ได้บ้าง แต่ผลลัพธ์แตกต่างกันไปตามเครื่องมือ การทำความสะอาดสคริปต์ก่อนถึงขั้นตอนเสียงจะขจัดปัญหาประเภทหนึ่ง ถามโมเดลให้ทำด้วยคำแนะนำที่ชัดเจน:
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
NORMALIZE = (
"Rewrite the user's text so it can be read aloud. "
"Spell out numbers, currency, dates and abbreviations as words. "
"Remove markdown, URLs and emoji. Keep the meaning and the order. "
"Return only the rewritten text."
)
def normalize(text):
r = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": NORMALIZE},
{"role": "user", "content": text},
],
temperature=0.2,
)
return r.choices[0].message.content.strip()
print(normalize("Dr. Lee bought 3 copies on 3/4 for the 2nd edition."))
ใช้ค่า temperature ต่ำสำหรับขั้นตอนนี้ คุณต้องการให้ input เดียวกันให้ output เดียวกัน และความสร้างสรรค์มีแต่จะขัดขวาง ตรวจสอบตัวอย่างผลลัพธ์ก่อนรันหนังสือทั้งเล่ม และตัดสินใจว่าจะจัดการรายการที่คลุมเครืออย่างไร วันที่ "3/4" เป็นตัวอย่างมาตรฐาน: อ่านเป็นวันที่ 4 มีนาคม หรือ 3 เมษายน? ใส่ตัวเลือกของคุณใน system prompt เพราะโมเดลไม่สามารถเดาพื้นที่ของคุณได้
ต้นทุนของขั้นตอนนี้
การปรับมาตรฐานมีราคาถูก พารากราฟ 600 โทเคนเข้าและประมาณ 650 โทเคนออก มีต้นทุน 600 x $0.15 / 1,000,000 = $0.00009 สำหรับอินพุต บวก 650 x $0.50 / 1,000,000 = $0.000325 สำหรับเอาต์พุต รวมเป็น $0.000415 นั่นคือประมาณ $0.42 สำหรับพารากราฟหนึ่งพันรายการ หน้าราคา มีตารางเต็มและตัวเลือกการเติมเงิน
ส่งคืนส่วนที่มีโครงสร้างด้วยโหมด JSON
เครื่องมือสังเคราะห์เสียงบางตัวยอมรับการตั้งค่าต่อส่วน: ความยาวการหยุดพัก อัตราการพูด เสียงต่างสำหรับตัวละครต่าง คุณสามารถให้โมเดลผลิตคำแนะนำเหล่านั้นเป็น JSON และอ่านใน Python ตั้ง response_format เป็น {"type": "json_object"} และอธิบายรูปร่างในพรอมต์ด้วย เนื่องจากโหมด JSON รับประกัน JSON ที่ถูกต้อง ไม่ใช่สคีมาของคุณ
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
prompt = (
"Split this scene into segments. Return JSON like "
'{"segments": [{"speaker": "narrator", "text": "...", "pause_ms": 300}]}. '
"Speakers are narrator, mara, or tom."
)
r = client.chat.completions.create(
model="uncensored",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": "Mara opens the door. Tom says he is late again. She sighs and lets him in."},
],
temperature=0.3,
)
data = json.loads(r.choices[0].message.content)
for seg in data["segments"]:
print(seg["speaker"], seg["pause_ms"], seg["text"])
ตรวจสอบสิ่งที่ส่งกลับมา ตรวจสอบว่า segments มีอยู่ ทุกรายการมีผู้พูดที่รู้จัก และ pause_ms เป็นตัวเลขในช่วงที่ยอมรับได้ หากการตรวจสอบล้มเหลว ให้เรียกซ้ำหนึ่งครั้งด้วยค่า temperature ที่ต่ำกว่าก่อนยอมจำนน ถือเอาต์พุตโมเดลเป็นอินพุตที่ไม่ไว้วางใจ เหมือนกับที่คุณทำกับข้อความผู้ใช้ใดๆ
กับดักที่เสียเวลา
ข้อผิดพลาดไม่กี่อย่างปรากฏซ้ำแล้วซ้ำอีกเมื่อผู้คนต่อโมเดลภาษาไว้หน้าเครื่องมือสังเคราะห์เสียง
- การปล่อยให้โมเดลเพิ่มคำอธิบาย โมเดลชอบพูดว่า "แน่นอน นี่คือสคริปต์ของคุณ" ประโยคนี้จะถูกอ่านออกเสียง จบ system prompt ของคุณด้วย "ส่งเฉพาะข้อความสคริปต์" และตัดบรรทัดนำหน้าออกหากลงท้ายด้วยเครื่องหมายทวิภาค
- การไว้วางใจความยาวประโยค เครื่องมือสร้างเสียงบางตัวมีปัญหากับประโยคที่ยาวมาก เพิ่มกฎเช่น "ไม่มีประโยคยาวเกิน 25 คำ" ใน prompt และบังคับใช้ใน Python โดยแบ่งตามเครื่องหมายจุลภาคเมื่อประโยคยังยาวเกินไป
- ลืมหน้าต่างบริบท พรอมต์และการตอบกลับใช้ร่วมกัน 100,000 โทเคน สำหรับบทหนังสือที่ยาว ให้ส่งเป็นช่วงๆ พร้อมสรุปย่อของช่วงก่อนหน้า แทนที่จะส่งข้อความเต็มทุกครั้ง นอกจากนี้ยังช่วยควบคุมบิล input ให้ลดลง
- การใช้ไคลเอนต์เดียวในหลาย thread โดยไม่มีขีดจำกัด ไคลเอนต์ใช้งานร่วมกันได้ แต่บริการไม่มีจำกัด จำกัดพูลให้ตรงกับ concurrency ของคีย์
- การฝังคีย์ไว้ในโค้ด ใช้ตัวแปรสภาพแวดล้อมและหมุนคีย์หากมันตกลงไปในที่เก็บโค้ด
เมื่อมีบางอย่างดูผิดปกติ ให้บันทึกส่วนหัว X-Request-Id พร้อมพรอมต์ที่ทำให้เกิดสิ่งนั้น หากคุณต้องการขอความช่วยเหลือ ID นั้นเป็นวิธีที่เร็วที่สุดในการชี้ไปที่คำขอเฉพาะเจาะจง และ docs อธิบายวิธีค้นหา
จัดการขีดจำกัดอัตราและการเรียกซ้ำ
คีย์แบบเต็มอนุญาตให้ทำคำขอพร้อมกัน 8 คำขอ และ 300 คำขอต่อนาที คีย์ทดลองอนุญาตให้ทำคำขอพร้อมกัน 2 คำขอ และ 60 คำขอต่อนาที หากเลยขีดจำกัดเหล่านี้ คุณจะได้รับ HTTP 429 พร้อม error code rate_limited สำหรับคำขอต่อนาที หรือ concurrency สำหรับคำขอพร้อมกันที่มากเกินไป เมื่อระบบมีภาระชั่วคราว API จะตอบกลับด้วย 429 และส่ง Retry-After: 5
SDK จะทำการเรียกซ้ำโดยอัตโนมัติ แต่ wrapper ที่กำหนดเองช่วยให้คุณควบคุมพฤติกรรมและทำให้เห็นการดำเนินการในบันทึกการเรียก (logs) ผสมผสานกับ thread pool ที่มีขนาดตามคีย์ของคุณ คุณจะแทบไม่เห็น 429 เลย:
import os
import time
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.xtts.cc/v1",
api_key=os.environ["XTTS_API_KEY"],
max_retries=0,
)
def ask(prompt, tries=5):
for attempt in range(tries):
try:
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": prompt}],
)
return r.choices[0].message.content
except RateLimitError as err:
wait = int(err.response.headers.get("Retry-After", 5))
time.sleep(wait * (attempt + 1))
raise RuntimeError("still rate limited after retries")
prompts = ["Narrate a sunrise in two sentences."] * 20
with ThreadPoolExecutor(max_workers=8) as pool: # use 2 on a trial key
results = list(pool.map(ask, prompts))
print(len(results))
คำขอที่ล้มเหลวหรือถูกปฏิเสธจะไม่ถูกเรียกเก็บเงิน ดังนั้นการเรียกซ้ำหลังจากเกิดข้อผิดพลาดจึงไม่เสียค่าใช้จ่ายสำหรับความพยายามที่ล้มเหลว คำขอที่เสร็จสิ้นจะถูกเรียกเก็บเงินตามปกติ ดังนั้นควรหลีกเลี่ยงการเรียกซ้ำแบบสุ่มสำหรับคำขอที่ช้าแต่สำเร็จ
ส่งข้อความไปยังเครื่องมือสังเคราะห์เสียง
ขั้นตอนสุดท้ายอยู่นอก API นี้ Python มีหลายวิธีในการผลิตเสียงจากข้อความ: speech models แบบโอเพนซอร์สที่คุณสามารถรันเอง บริการเสียงบนเดสก์ท็อปที่รวมอยู่ในระบบปฏิบัติการ และ speech API ที่โฮสต์โดยบริษัทอื่น แต่ละอันมีใบอนุญาต คุณภาพ และราคาเป็นของตัวเอง ดังนั้นตรวจสอบเอกสารและข้อกำหนดปัจจุบันก่อนเลือก เว็บไซต์นี้ไม่ได้ให้ส่วนนั้น
ขอบเขตที่สะอาดทำให้โค้ดของคุณเปลี่ยนง่าย เขียนฟังก์ชันเดียวที่มี signature speak(text: str) -> None และเก็บรายละเอียดเฉพาะ engine ไว้ภายใน pipeline ข้อความด้านบนเรียกฟังก์ชันนั้นสำหรับแต่ละประโยคหรือส่วน เมื่อคุณเปลี่ยน voice engine สิ่งอื่นไม่เปลี่ยนแปลง
หากคุณยังตัดสินใจระหว่าง trial และการตั้งค่าแบบเสียเงิน ตัวเลขนั้นเรียบง่าย: คุณจ่าย $0.15 ต่อล้าน input token และ $0.50 ต่อล้าน output token จาก prepaid credit ที่ไม่หมดอายุ โดยไม่มี subscription การเติมเงินเริ่มตั้งแต่ $20 ถึง $500 และสามารถจ่ายด้วยcrypto (USDT บน TRC20, USDC บน Base)
คู่มือเพิ่มเติม
ถาม-ตอบ
API นี้ส่งไฟล์เสียงกลับมาหรือไม่?
ไม่ ส่งเฉพาะข้อความ ใช้เพื่อเขียน ทำความสะอาด และจัดโครงสร้างสคริปต์ จากนั้นส่งข้อความไปยังเครื่องมือสังเคราะห์เสียงแยกต่างหาก
ควรใช้แพ็กเกจ Python ตัวใด?
แพ็กเกจทางการของ openai ตั้งค่า base_url เป็น https://api.xtts.cc/v1 ส่งคีย์ของคุณ และใช้โมเดล ID uncensored
ฉันสามารถสตรีมคำตอบใน Python ได้หรือไม่?
ใช่ ส่ง stream=True และวนซ้ำผ่านชิ้นส่วน การใช้งานจะรายงานที่ท้ายสตรีม และคำขอจะถือช่องความพร้อมใช้งานพร้อมกันไว้จนกว่าสตรีมจะเสร็จ
เกิดอะไรขึ้นเมื่อถึงขีดจำกัดอัตรา?
คุณจะได้รับ HTTP 429 พร้อม code rate_limited หรือ concurrency รอค่า Retry-After หากมีอยู่ แล้วทำการเรียกซ้ำ และรักษาจำนวน worker ให้ไม่เกิน 8 สำหรับคีย์เต็ม หรือ 2 สำหรับคีย์ทดลอง
คีย์ของคุณอยู่ห่างแค่แบบฟอร์มเดียว
สร้างบัญชี คัดลอกคีย์ และเปลี่ยน base URL นั่นคือการตั้งค่าทั้งหมด