TH ▾

API ข้อความไม่เซ็นเซอร์สำหรับ Voice Pipeline

uncensoredhttps://api.xtts.cc/v1

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 นั่นคือการตั้งค่าทั้งหมด