VI ▾

API văn bản không kiểm duyệt cho pipeline giọng nói

uncensoredhttps://api.xtts.cc/v1

API TTS Python: Chuẩn bị văn bản để tạo giọng nói với SDK

API này không trả về âm thanh. Đây là một mô hình văn bản mà bạn gọi từ Python bằng OpenAI SDK, và nó phù hợp để đứng trước một công cụ tạo giọng nói: nó viết kịch bản, làm sạch và chia nhỏ thành các câu mà công cụ tạo giọng nói của bạn có thể đọc to.

Cập nhật

Minh họa đẳng hướng các khối văn bản chảy từ terminal vào một cổng sóng âm

API này làm được và không làm được gì

Nếu bạn đã tìm kiếm hướng dẫn tts api python, có một điểm cần rõ ràng trước bất kỳ mã nào: dịch vụ này không tổng hợp giọng nói. Nó cung cấp POST /v1/chat/completions và GET /v1/models cho một mô hình văn bản trọng số mở, với model ID uncensored. Không có endpoint âm thanh, không có danh sách giọng và không có tệp âm thanh trong bất kỳ phản hồi nào. Nó cũng không có đầu ra hình ảnh, video hay embedding.

Vậy nó phù hợp ở đâu trong một dự án giọng nói? Ở phần xử lý văn bản của chuỗi xử lý. Trước khi một công cụ giọng nói có thể đọc một kịch bản, ai đó phải viết nó, mở rộng các số và viết tắt thành các từ phát âm, loại bỏ markup và cắt nó thành các đoạn đủ ngắn để tổng hợp mà không có khoảng dừng dài. Một mô hình ngôn ngữ thực hiện công việc đó rất tốt, và bạn có thể gọi nó từ Python bằng cùng SDK mà bạn có thể đã dùng cho các dịch vụ tương thích OpenAI khác.

Mô hình được tinh chỉnh để trả lời mà không từ chối nội dung cho mục đích người trưởng thành hợp pháp, điều này quan trọng đối với truyện, hội thoại nhân vật và tường thuật nơi một mô hình nghiêm ngặt hơn có thể dừng giữa cảnh. Một quy tắc cứng duy nhất áp dụng: không có nội dung tình dục liên quan đến người chưa thành niên.

Phần còn lại của bài viết này xây dựng một mô-đun Python nhỏ gồm bốn bước: một lệnh gọi đầu tiên, bộ chia câu streaming, bộ chuẩn hóa văn bản với đầu ra JSON và bộ bao bọc thử lại tôn trọng các giới hạn tốc độ.

Cài đặt SDK và thực hiện lệnh gọi đầu tiên

Bạn cần Python 3.8 trở lên và gói openai chính thức. Tạo một khóa API trên trang API key trước tiên. Tài khoản mới có thể nhận tín dụng dùng thử $0.50 ở đó chỉ với một cú nhấp chuột và không cần thẻ để làm điều đó.

pip install openai

Giữ khóa bên ngoài mã nguồn của bạn. Xuất nó dưới dạng biến môi trường và đọc nó trong mã:

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)

Đó là toàn bộ quá trình tích hợp. SDK gửi Authorization: Bearer <key> cho bạn. Mọi phản hồi cũng mang theo tiêu đề X-Balance-USD, vì vậy bạn có thể đọc tín dụng còn lại của mình từ phản hồi thô nếu bạn muốn ghi lại nó. Trang docs liệt kê các tiêu đề và tham số khác.

Các tham số như temperature, top_p, stop, seed và các hệ số phạt được chuyển tiếp. Đối với tường thuật, nhiệt độ từ 0.6 đến 0.9 là một phạm vi bắt đầu hợp lý, và một seed cố định giúp khi bạn muốn tái tạo một lần đọc trong khi tinh chỉnh prompt.

Nhận stream kịch bản và chia nó thành các câu

Các công cụ tạo giọng nói thường hoạt động từng câu một. Nếu bạn đợi một kịch bản 900 từ kết thúc, người nghe cũng phải đợi. Với stream: true, API gửi các token qua các sự kiện do máy chủ gửi, và bạn có thể chuyển mỗi câu hoàn thành cho giai đoạn giọng nói trong khi phần còn lại vẫn đang được viết.

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)

Hai chi tiết dễ bị bỏ qua. Đầu tiên, các số liệu sử dụng xuất hiện ở cuối luồng, trong một phần dữ liệu có thể có danh sách choices trống, đó là lý do vòng lặp bỏ qua các phần dữ liệu không có lựa chọn. Thứ hai, một yêu cầu truyền phát giữ khe đồng thời của nó cho đến khi luồng kết thúc. Nếu công cụ giọng nói của bạn chậm và bạn đọc luồng một cách lười biếng, khe sẽ vẫn bận. Hãy lấy văn bản ra nhanh, đặt các câu vào hàng đợi và để phần âm thanh tiêu thụ hàng đợi theo tốc độ của riêng nó.

Biểu thức chính quy ở trên chia trên một dấu chấm hoặc dấu hỏi theo sau là khoảng trắng. Nó sẽ chia sai sau các từ viết tắt như "Dr." hoặc "No.", đó là một lý do nữa để chuẩn hóa văn bản trước, như phần tiếp theo sẽ hiển thị.

Chuẩn hóa văn bản cho tai nghe

Văn bản viết và văn bản nói khác nhau. "Tiến sĩ Lee đã mua 3 bản vào ngày 3/4 cho ấn bản thứ 2" dễ đọc và khó nói. Nhiều công cụ tạo giọng nói xử lý một phần điều này, nhưng kết quả khác nhau tùy thuộc vào công cụ, vì vậy việc làm sạch kịch bản trước khi nó đến giai đoạn giọng nói loại bỏ một lớp các tình huống bất ngờ. Hãy yêu cầu mô hình thực hiện điều đó với một hướng dẫn rõ ràng:

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."))

Hãy sử dụng nhiệt độ thấp cho bước này. Bạn muốn cùng một đầu vào cho cùng một đầu ra, và sự sáng tạo chỉ cản trở. Hãy xem xét một mẫu kết quả trước khi bạn chạy toàn bộ cuốn sách qua nó, và quyết định cách bạn muốn xử lý các mục mơ hồ. Ngày "3/4" là ví dụ tiêu chuẩn: đọc nó là ngày 4 tháng 3 hay ngày 3 tháng 4? Hãy đặt lựa chọn của bạn vào prompt hệ thống, vì mô hình không thể đoán khu vực địa lý của bạn.

Chi phí cho bước này

Chuẩn hóa rất rẻ. Một đoạn văn bản 600 token vào và khoảng 650 token ra tốn 600 x $0.15 / 1,000,000 = $0.00009 cho đầu vào, cộng 650 x $0.50 / 1,000,000 = $0.000325 cho đầu ra, tổng cộng $0.000415. Đó là khoảng $0.42 cho một nghìn đoạn văn. Trang pricing có bảng đầy đủ và các tùy chọn top up.

Trả về các đoạn có cấu trúc với chế độ JSON

Một số công cụ giọng nói chấp nhận cài đặt từng đoạn: độ dài khoảng dừng, tốc độ nói, một giọng khác cho một nhân vật khác. Bạn có thể yêu cầu mô hình tạo ra các hướng dẫn đó dưới dạng JSON và đọc chúng trong Python. Đặt response_format thành {"type": "json_object"} và cũng mô tả hình dạng trong prompt, vì chế độ JSON đảm bảo JSON hợp lệ, không phải lược đồ của bạn.

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"])

Hãy xác thực những gì trả về. Hãy kiểm tra xem segments có tồn tại, rằng mọi mục đều có một người nói đã biết và rằng pause_ms là một số trong một phạm vi bạn chấp nhận. Nếu xác thực thất bại, hãy thử lại một lần với nhiệt độ thấp hơn trước khi bỏ cuộc. Hãy coi đầu ra của mô hình là đầu vào không đáng tin cậy, giống như bất kỳ văn bản người dùng nào khác.

Những lỗi tốn thời gian

Một vài lỗi xuất hiện lại và lại khi mọi người kết nối một mô hình ngôn ngữ phía trước một công cụ tạo giọng nói.

  • Cho phép mô hình thêm phần bình luận. Các mô hình thích nói "Chắc chắn, đây là kịch bản của bạn." Câu đó sẽ được đọc to. Kết thúc prompt hệ thống của bạn bằng "Chỉ trả về văn bản kịch bản" và loại bỏ một dòng dẫn đầu nếu nó kết thúc bằng dấu hai chấm.
  • Tin tưởng vào độ dài câu. Một số công cụ gặp khó khăn với các câu rất dài. Thêm một quy tắc như "không có câu nào dài hơn 25 từ" vào prompt, và cũng thực thi nó trong Python bằng cách chia trên dấu phẩy khi một câu vẫn còn quá dài.
  • Quên cửa sổ ngữ cảnh. Prompt và completion chia sẻ 100,000 token. Đối với một chương sách dài, hãy gửi nó theo các phần với một bản tóm tắt ngắn của phần trước thay vì toàn bộ văn bản mỗi lần. Điều đó cũng giữ hóa đơn đầu vào thấp.
  • Tái sử dụng một trình khách toàn cục duy nhất qua các luồng mà không có giới hạn. Trình khách an toàn để chia sẻ, nhưng dịch vụ không phải là vô hạn. Hãy giới hạn nhóm của bạn ở mức độ đồng thời của khóa của bạn.
  • Mã hóa cứng khóa. Hãy sử dụng một biến môi trường và xoay khóa nếu nó cuối cùng nằm trong một kho mã nguồn.

Khi một thứ gì đó trông sai, hãy ghi lại tiêu đề X-Request-Id với prompt gây ra nó. Nếu bạn cần yêu cầu trợ giúp, ID đó là cách nhanh nhất để chỉ vào một lệnh gọi cụ thể, và docs giải thích nơi tìm nó.

Xử lý giới hạn tốc độ và thử lại

Một khóa đầy đủ cho phép 8 yêu cầu đồng thời và 300 yêu cầu mỗi phút. Một khóa dùng thử cho phép 2 yêu cầu đồng thời và 60 yêu cầu mỗi phút. Vượt quá giới hạn nào, bạn sẽ nhận được HTTP 429, với mã lỗi rate_limited cho yêu cầu mỗi phút hoặc concurrency cho quá nhiều cùng lúc. Khi quá tải tạm thời, API cũng trả về 429 và gửi Retry-After: 5.

SDK tự động thử lại, nhưng một bộ bao bọc rõ ràng cho bạn quyền kiểm soát và làm cho hành vi hiển thị trong nhật ký. Kết hợp nó với một thread pool có kích thước phù hợp với khóa của bạn, và bạn sẽ hiếm khi thấy một 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))

Các yêu cầu bị lỗi hoặc bị từ chối không được tính phí, vì vậy thử lại sau một lỗi không tốn chi phí cho bạn cho lần thử bị lỗi. Một yêu cầu hoàn thành được tính phí bình thường, vì vậy hãy tránh thử lại mù quáng trên các lệnh gọi chậm nhưng thành công.

Chuyển văn bản cho công cụ tạo giọng nói của bạn

Bước cuối cùng nằm ngoài API này. Python có nhiều cách để tạo âm thanh từ văn bản: các mô hình giọng nói mã nguồn mở mà bạn có thể tự chạy, các dịch vụ giọng nói máy tính để bàn được tích hợp trong hệ điều hành và các API giọng nói được lưu trữ từ các công ty khác. Mỗi cái có giấy phép, chất lượng và giá riêng, vì vậy hãy kiểm tra tài liệu và điều khoản hiện tại của chúng trước khi bạn chọn. Trang web này không cung cấp phần đó.

Một ranh giới sạch sẽ giữ mã của bạn dễ thay đổi. Hãy viết một hàm với chữ ký speak(text: str) -> None và giữ mọi chi tiết cụ thể của công cụ bên trong nó. Pipeline văn bản ở trên gọi hàm đó cho mỗi câu hoặc đoạn. Khi bạn thay đổi công cụ, không có gì khác thay đổi.

Nếu bạn vẫn đang phân vân giữa một khóa dùng thử và một thiết lập trả phí, các con số rất đơn giản: bạn trả $0.15 cho mỗi triệu token đầu vào và $0.50 cho mỗi triệu token đầu ra, từ tín dụng trả trước không bao giờ hết hạn, không có đăng ký. Các khoản top up chạy từ $20 đến $500 và có thể được thanh toán bằng crypto (USDT trên TRC20, USDC trên Base).

Hỏi đáp

API này có trả về tệp âm thanh không?

Không. Nó chỉ trả về văn bản. Hãy dùng nó để viết, làm sạch và cấu trúc kịch bản, sau đó chuyển văn bản sang một công cụ tạo giọng nói tách biệt.

Tôi nên sử dụng gói Python nào?

Gói chính thức của OpenAI. Đặt base_url thành https://api.xtts.cc/v1, truyền khóa của bạn và dùng model ID uncensored.

Bạn có thể truyền phát phản hồi trong Python không?

Có. Hãy truyền stream=True và lặp qua các chunk. Việc sử dụng được báo cáo ở cuối luồng truyền phát, và yêu cầu giữ khe đồng thời của nó cho đến khi luồng truyền phát kết thúc.

Điều gì xảy ra khi tôi đạt giới hạn tốc độ?

Bạn sẽ nhận HTTP 429 với mã rate_limited hoặc concurrency. Hãy đợi giá trị Retry-After nếu có và thử lại, đồng thời giữ số lượng worker của bạn ở mức bằng hoặc thấp hơn 8 đối với khóa đầy đủ hoặc 2 đối với khóa dùng thử.

Khóa của bạn chỉ cách một biểu mẫu

Tạo tài khoản, sao chép khóa, thay đổi URL cơ sở. Đó là toàn bộ thiết lập.