# 스트리밍 오디오를 ffplay로 파이핑하여 실시간 재생.
# 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
curl -N -s --request POST \
--url https://api.typecast.ai/v1/text-to-speech/stream \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api-key>' \
--data @- <<EOF | ffplay -autoexit -nodisp -loglevel error -i pipe:0
{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}
EOF# sounddevice로 실시간 재생 (pip install requests sounddevice).
# 스트리밍 WAV 형식: 32000 Hz, 16비트, 모노 — 44바이트 WAV 헤더를
# 건너뛰고 원시 PCM 샘플을 오디오 출력에 전달합니다.
import requests
import sounddevice as sd
API_HOST = "https://api.typecast.ai"
headers = {"X-API-KEY": "<api-key>", "Content-Type": "application/json"}
payload = {
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30",
}
resp = requests.post(
f"{API_HOST}/v1/text-to-speech/stream",
headers=headers, json=payload, stream=True, timeout=60,
)
resp.raise_for_status()
with sd.RawOutputStream(samplerate=32000, channels=1, dtype="int16") as player:
buf, first = bytearray(), True
for chunk in resp.iter_content(chunk_size=4096):
if not chunk:
continue
if first:
chunk = chunk[44:] # WAV 헤더 제거
first = False
buf.extend(chunk)
# int16 샘플 정렬을 위해 2바이트 단위로만 write.
n = len(buf) - (len(buf) % 2)
if n:
player.write(bytes(buf[:n]))
del buf[:n]
print("재생 완료")// 스트림을 ffplay로 파이핑하여 실시간 재생.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-KEY", "<api-key>");
var requestBody = @"{
""voice_id"": ""tc_60e5426de8b95f1d3000d7b5"",
""text"": ""문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다."",
""model"": ""ssfm-v30""
}";
var ffplay = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "ffplay",
Arguments = "-autoexit -nodisp -loglevel error -i pipe:0",
RedirectStandardInput = true,
UseShellExecute = false,
}
};
ffplay.Start();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.typecast.ai/v1/text-to-speech/stream")
{
Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
};
// ResponseHeadersRead로 실제 스트리밍을 활성화합니다 (전체 버퍼링 회피).
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
await stream.CopyToAsync(ffplay.StandardInput.BaseStream);
ffplay.StandardInput.Close();
await ffplay.WaitForExitAsync();// OkHttp 응답 스트림을 ffplay로 파이핑하여 실시간 재생.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
// Android에서는 ffplay Process 대신 AudioTrack + 원시 PCM 전달을 사용하세요.
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
val ffplay = ProcessBuilder(
"ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"
).redirectError(ProcessBuilder.Redirect.DISCARD).start()
val client = OkHttpClient()
val body = """
{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}
""".trimIndent().toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://api.typecast.ai/v1/text-to-speech/stream")
.addHeader("X-API-KEY", "<api-key>")
.post(body)
.build()
client.newCall(request).execute().use { response ->
response.body?.byteStream()?.use { input -> input.copyTo(ffplay.outputStream) }
}
ffplay.outputStream.close()
ffplay.waitFor()// 실시간 재생: libcurl write 콜백이 각 청크를 popen으로 연
// ffplay 프로세스에 전달. 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
#include <curl/curl.h>
#include <cstdio>
#include <string>
static FILE* player = nullptr;
size_t cb(void* ptr, size_t size, size_t nmemb, void*) {
return fwrite(ptr, size, nmemb, player);
}
int main() {
player = popen("ffplay -autoexit -nodisp -loglevel error -i pipe:0", "w");
CURL* curl = curl_easy_init();
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "X-API-KEY: <api-key>");
std::string body = R"({
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
})";
curl_easy_setopt(curl, CURLOPT_URL, "https://api.typecast.ai/v1/text-to-speech/stream");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb);
curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
pclose(player);
return 0;
}/* 실시간 재생: libcurl write 콜백이 각 청크를 popen으로 연
* ffplay 프로세스에 전달. 사전 설치: ffmpeg (brew/choco/apt install ffmpeg) */
#include <stdio.h>
#include <curl/curl.h>
static FILE* player = NULL;
size_t cb(void* ptr, size_t size, size_t nmemb, void* ud) {
(void)ud;
return fwrite(ptr, size, nmemb, player);
}
int main(void) {
player = popen("ffplay -autoexit -nodisp -loglevel error -i pipe:0", "w");
curl_global_init(CURL_GLOBAL_ALL);
CURL* curl = curl_easy_init();
struct curl_slist* headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "X-API-KEY: <api-key>");
const char* body =
"{"
"\"voice_id\":\"tc_60e5426de8b95f1d3000d7b5\","
"\"text\":\"문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.\","
"\"model\":\"ssfm-v30\""
"}";
curl_easy_setopt(curl, CURLOPT_URL, "https://api.typecast.ai/v1/text-to-speech/stream");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb);
curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
pclose(player);
return 0;
}// 실시간 재생(macOS): URLSession 바이트 스트림을 Process로 연
// ffplay에 파이핑. 사전 설치: ffmpeg (brew install ffmpeg).
// URLSession.bytes(for:)는 iOS 15 / macOS 12 이상 필요.
// 컴파일: swiftc -parse-as-library main.swift -o streaming_tts
// iOS에서는 Process/ffplay 대신 AVAudioEngine + 스케줄드 PCM 버퍼 사용.
import Foundation
@main
struct StreamingTTS {
static func main() async throws {
let ffplay = Process()
ffplay.executableURL = URL(fileURLWithPath: "/usr/bin/env")
ffplay.arguments = ["ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"]
let pipe = Pipe()
ffplay.standardInput = pipe
try ffplay.run()
var request = URLRequest(url: URL(string: "https://api.typecast.ai/v1/text-to-speech/stream")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("<api-key>", forHTTPHeaderField: "X-API-KEY")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30",
])
let (bytes, _) = try await URLSession.shared.bytes(for: request)
var buffer = Data()
buffer.reserveCapacity(4096)
for try await byte in bytes {
buffer.append(byte)
if buffer.count >= 4096 {
try pipe.fileHandleForWriting.write(contentsOf: buffer)
buffer.removeAll(keepingCapacity: true)
}
}
if !buffer.isEmpty {
try pipe.fileHandleForWriting.write(contentsOf: buffer)
}
try pipe.fileHandleForWriting.close()
ffplay.waitUntilExit()
}
}// 실시간 재생: reqwest 스트림을 tokio Command로 연 ffplay에 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
// Cargo.toml:
// reqwest = { version = "0.12", features = ["json", "stream"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use reqwest;
use serde_json::json;
use std::process::Stdio;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut ffplay = Command::new("ffplay")
.args(["-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"])
.stdin(Stdio::piped())
.spawn()?;
let mut stdin = ffplay.stdin.take().expect("failed to open ffplay stdin");
let client = reqwest::Client::new();
let body = json!({
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
});
let mut response = client
.post("https://api.typecast.ai/v1/text-to-speech/stream")
.header("X-API-KEY", "<api-key>")
.header("Content-Type", "application/json")
.json(&body)
.send()
.await?;
while let Some(chunk) = response.chunk().await? {
stdin.write_all(&chunk).await?;
}
drop(stdin);
ffplay.wait().await?;
Ok(())
}// Node 18+ (내장 fetch). 스트림을 ffplay로 파이핑하여 실시간 재생.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
import { spawn } from "node:child_process";
const ffplay = spawn(
"ffplay",
["-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"],
{ stdio: ["pipe", "ignore", "ignore"] },
);
const response = await fetch("https://api.typecast.ai/v1/text-to-speech/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": "<api-key>",
},
body: JSON.stringify({
voice_id: "tc_60e5426de8b95f1d3000d7b5",
text: "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
model: "ssfm-v30",
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// fetch().body는 Web ReadableStream — 청크가 도착하는 즉시 읽습니다.
const reader = response.body.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
ffplay.stdin.write(value);
}
ffplay.stdin.end();
await new Promise((resolve) => ffplay.on("close", resolve));<?php
// libcurl write 콜백을 ffplay stdin으로 직접 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
$ffplay = popen("ffplay -autoexit -nodisp -loglevel error -i pipe:0", "w");
$payload = json_encode([
"voice_id" => "tc_60e5426de8b95f1d3000d7b5",
"text" => "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model" => "ssfm-v30",
]);
$ch = curl_init("https://api.typecast.ai/v1/text-to-speech/stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-KEY: <api-key>",
],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_WRITEFUNCTION => function ($ch, $data) use ($ffplay) {
fwrite($ffplay, $data);
return strlen($data);
},
]);
curl_exec($ch);
pclose($ffplay);// 스트리밍 응답 본문을 ffplay stdin으로 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
package main
import (
"bytes"
"io"
"net/http"
"os/exec"
)
func main() {
ffplay := exec.Command("ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0")
stdin, _ := ffplay.StdinPipe()
if err := ffplay.Start(); err != nil {
panic(err)
}
body := []byte(`{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}`)
req, _ := http.NewRequest("POST", "https://api.typecast.ai/v1/text-to-speech/stream", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-KEY", "<api-key>")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
io.Copy(stdin, resp.Body)
stdin.Close()
ffplay.Wait()
}// Java 11+ HttpClient + InputStream body handler.
// 스트리밍 응답을 ffplay stdin으로 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.io.InputStream;
import java.io.OutputStream;
public class StreamingTTS {
public static void main(String[] args) throws Exception {
Process ffplay = new ProcessBuilder(
"ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0")
.redirectError(ProcessBuilder.Redirect.DISCARD)
.start();
String body = """
{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.typecast.ai/v1/text-to-speech/stream"))
.header("Content-Type", "application/json")
.header("X-API-KEY", "<api-key>")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<InputStream> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofInputStream());
try (InputStream in = response.body();
OutputStream out = ffplay.getOutputStream()) {
in.transferTo(out);
}
ffplay.waitFor();
}
}# IO.popen으로 ffplay를 띄우고 스트리밍 응답을 파이핑.
# 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
require "net/http"
require "uri"
require "json"
ffplay = IO.popen(
["ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"],
"wb",
)
uri = URI("https://api.typecast.ai/v1/text-to-speech/stream")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["X-API-KEY"] = "<api-key>"
req.body = {
voice_id: "tc_60e5426de8b95f1d3000d7b5",
text: "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
model: "ssfm-v30",
}.to_json
http.request(req) do |response|
response.read_body { |chunk| ffplay.write(chunk) }
end
ffplay.close"[Binary audio stream - WAV chunks]"{
"detail": "Invalid voice_id"
}{
"detail": "Invalid API key"
}{
"detail": "Insufficient credit"
}{
"detail": "Voice not found"
}{
"detail": "Invalid request format"
}{
"detail": "Too many requests"
}{
"detail": "An unexpected error occurred"
}스트리밍 텍스트 음성 변환(Streaming TTS)
실시간 스트리밍을 사용하여 텍스트에서 음성을 생성합니다. 전체 합성이 완료되기 전에 오디오 재생을 시작할 수 있습니다.
이 엔드포인트는 오디오 데이터를 청크 단위로 스트리밍하여 즉각적인 피드백이 필요한 애플리케이션에서 낮은 지연 시간의 오디오 재생을 가능하게 합니다.
스트리밍 형식:
- WAV 형식: 첫 번째 청크에는 WAV 헤더(스트리밍용 size=0xFFFFFFFF)와 원시 PCM 데이터가 포함됩니다. 이후 청크에는 PCM 데이터만 포함됩니다.
- MP3 형식: 각 청크에는 독립적으로 디코딩할 수 있는 후처리된 MP3 데이터가 포함됩니다.
사용 사례:
- 대화형 AI, 챗봇, 실시간 음성 비서 등
- 즉각적인 오디오 피드백이 필요한 인터랙티브 애플리케이션
- 전체 합성을 기다리는 것이 비실용적인 장문 콘텐츠
요청 파라미터:
표준 TTS 엔드포인트와 동일한 TTSRequest 스키마를 사용합니다. output.audio_format을 “wav” 또는 “mp3”로 설정하여 스트리밍 형식을 제어합니다.
# 스트리밍 오디오를 ffplay로 파이핑하여 실시간 재생.
# 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
curl -N -s --request POST \
--url https://api.typecast.ai/v1/text-to-speech/stream \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api-key>' \
--data @- <<EOF | ffplay -autoexit -nodisp -loglevel error -i pipe:0
{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}
EOF# sounddevice로 실시간 재생 (pip install requests sounddevice).
# 스트리밍 WAV 형식: 32000 Hz, 16비트, 모노 — 44바이트 WAV 헤더를
# 건너뛰고 원시 PCM 샘플을 오디오 출력에 전달합니다.
import requests
import sounddevice as sd
API_HOST = "https://api.typecast.ai"
headers = {"X-API-KEY": "<api-key>", "Content-Type": "application/json"}
payload = {
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30",
}
resp = requests.post(
f"{API_HOST}/v1/text-to-speech/stream",
headers=headers, json=payload, stream=True, timeout=60,
)
resp.raise_for_status()
with sd.RawOutputStream(samplerate=32000, channels=1, dtype="int16") as player:
buf, first = bytearray(), True
for chunk in resp.iter_content(chunk_size=4096):
if not chunk:
continue
if first:
chunk = chunk[44:] # WAV 헤더 제거
first = False
buf.extend(chunk)
# int16 샘플 정렬을 위해 2바이트 단위로만 write.
n = len(buf) - (len(buf) % 2)
if n:
player.write(bytes(buf[:n]))
del buf[:n]
print("재생 완료")// 스트림을 ffplay로 파이핑하여 실시간 재생.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-KEY", "<api-key>");
var requestBody = @"{
""voice_id"": ""tc_60e5426de8b95f1d3000d7b5"",
""text"": ""문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다."",
""model"": ""ssfm-v30""
}";
var ffplay = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "ffplay",
Arguments = "-autoexit -nodisp -loglevel error -i pipe:0",
RedirectStandardInput = true,
UseShellExecute = false,
}
};
ffplay.Start();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.typecast.ai/v1/text-to-speech/stream")
{
Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
};
// ResponseHeadersRead로 실제 스트리밍을 활성화합니다 (전체 버퍼링 회피).
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
await stream.CopyToAsync(ffplay.StandardInput.BaseStream);
ffplay.StandardInput.Close();
await ffplay.WaitForExitAsync();// OkHttp 응답 스트림을 ffplay로 파이핑하여 실시간 재생.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
// Android에서는 ffplay Process 대신 AudioTrack + 원시 PCM 전달을 사용하세요.
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
val ffplay = ProcessBuilder(
"ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"
).redirectError(ProcessBuilder.Redirect.DISCARD).start()
val client = OkHttpClient()
val body = """
{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}
""".trimIndent().toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://api.typecast.ai/v1/text-to-speech/stream")
.addHeader("X-API-KEY", "<api-key>")
.post(body)
.build()
client.newCall(request).execute().use { response ->
response.body?.byteStream()?.use { input -> input.copyTo(ffplay.outputStream) }
}
ffplay.outputStream.close()
ffplay.waitFor()// 실시간 재생: libcurl write 콜백이 각 청크를 popen으로 연
// ffplay 프로세스에 전달. 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
#include <curl/curl.h>
#include <cstdio>
#include <string>
static FILE* player = nullptr;
size_t cb(void* ptr, size_t size, size_t nmemb, void*) {
return fwrite(ptr, size, nmemb, player);
}
int main() {
player = popen("ffplay -autoexit -nodisp -loglevel error -i pipe:0", "w");
CURL* curl = curl_easy_init();
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "X-API-KEY: <api-key>");
std::string body = R"({
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
})";
curl_easy_setopt(curl, CURLOPT_URL, "https://api.typecast.ai/v1/text-to-speech/stream");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb);
curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
pclose(player);
return 0;
}/* 실시간 재생: libcurl write 콜백이 각 청크를 popen으로 연
* ffplay 프로세스에 전달. 사전 설치: ffmpeg (brew/choco/apt install ffmpeg) */
#include <stdio.h>
#include <curl/curl.h>
static FILE* player = NULL;
size_t cb(void* ptr, size_t size, size_t nmemb, void* ud) {
(void)ud;
return fwrite(ptr, size, nmemb, player);
}
int main(void) {
player = popen("ffplay -autoexit -nodisp -loglevel error -i pipe:0", "w");
curl_global_init(CURL_GLOBAL_ALL);
CURL* curl = curl_easy_init();
struct curl_slist* headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "X-API-KEY: <api-key>");
const char* body =
"{"
"\"voice_id\":\"tc_60e5426de8b95f1d3000d7b5\","
"\"text\":\"문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.\","
"\"model\":\"ssfm-v30\""
"}";
curl_easy_setopt(curl, CURLOPT_URL, "https://api.typecast.ai/v1/text-to-speech/stream");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb);
curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
pclose(player);
return 0;
}// 실시간 재생(macOS): URLSession 바이트 스트림을 Process로 연
// ffplay에 파이핑. 사전 설치: ffmpeg (brew install ffmpeg).
// URLSession.bytes(for:)는 iOS 15 / macOS 12 이상 필요.
// 컴파일: swiftc -parse-as-library main.swift -o streaming_tts
// iOS에서는 Process/ffplay 대신 AVAudioEngine + 스케줄드 PCM 버퍼 사용.
import Foundation
@main
struct StreamingTTS {
static func main() async throws {
let ffplay = Process()
ffplay.executableURL = URL(fileURLWithPath: "/usr/bin/env")
ffplay.arguments = ["ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"]
let pipe = Pipe()
ffplay.standardInput = pipe
try ffplay.run()
var request = URLRequest(url: URL(string: "https://api.typecast.ai/v1/text-to-speech/stream")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("<api-key>", forHTTPHeaderField: "X-API-KEY")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30",
])
let (bytes, _) = try await URLSession.shared.bytes(for: request)
var buffer = Data()
buffer.reserveCapacity(4096)
for try await byte in bytes {
buffer.append(byte)
if buffer.count >= 4096 {
try pipe.fileHandleForWriting.write(contentsOf: buffer)
buffer.removeAll(keepingCapacity: true)
}
}
if !buffer.isEmpty {
try pipe.fileHandleForWriting.write(contentsOf: buffer)
}
try pipe.fileHandleForWriting.close()
ffplay.waitUntilExit()
}
}// 실시간 재생: reqwest 스트림을 tokio Command로 연 ffplay에 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
// Cargo.toml:
// reqwest = { version = "0.12", features = ["json", "stream"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use reqwest;
use serde_json::json;
use std::process::Stdio;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut ffplay = Command::new("ffplay")
.args(["-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"])
.stdin(Stdio::piped())
.spawn()?;
let mut stdin = ffplay.stdin.take().expect("failed to open ffplay stdin");
let client = reqwest::Client::new();
let body = json!({
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
});
let mut response = client
.post("https://api.typecast.ai/v1/text-to-speech/stream")
.header("X-API-KEY", "<api-key>")
.header("Content-Type", "application/json")
.json(&body)
.send()
.await?;
while let Some(chunk) = response.chunk().await? {
stdin.write_all(&chunk).await?;
}
drop(stdin);
ffplay.wait().await?;
Ok(())
}// Node 18+ (내장 fetch). 스트림을 ffplay로 파이핑하여 실시간 재생.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
import { spawn } from "node:child_process";
const ffplay = spawn(
"ffplay",
["-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"],
{ stdio: ["pipe", "ignore", "ignore"] },
);
const response = await fetch("https://api.typecast.ai/v1/text-to-speech/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": "<api-key>",
},
body: JSON.stringify({
voice_id: "tc_60e5426de8b95f1d3000d7b5",
text: "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
model: "ssfm-v30",
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// fetch().body는 Web ReadableStream — 청크가 도착하는 즉시 읽습니다.
const reader = response.body.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
ffplay.stdin.write(value);
}
ffplay.stdin.end();
await new Promise((resolve) => ffplay.on("close", resolve));<?php
// libcurl write 콜백을 ffplay stdin으로 직접 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
$ffplay = popen("ffplay -autoexit -nodisp -loglevel error -i pipe:0", "w");
$payload = json_encode([
"voice_id" => "tc_60e5426de8b95f1d3000d7b5",
"text" => "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model" => "ssfm-v30",
]);
$ch = curl_init("https://api.typecast.ai/v1/text-to-speech/stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-KEY: <api-key>",
],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_WRITEFUNCTION => function ($ch, $data) use ($ffplay) {
fwrite($ffplay, $data);
return strlen($data);
},
]);
curl_exec($ch);
pclose($ffplay);// 스트리밍 응답 본문을 ffplay stdin으로 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
package main
import (
"bytes"
"io"
"net/http"
"os/exec"
)
func main() {
ffplay := exec.Command("ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0")
stdin, _ := ffplay.StdinPipe()
if err := ffplay.Start(); err != nil {
panic(err)
}
body := []byte(`{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}`)
req, _ := http.NewRequest("POST", "https://api.typecast.ai/v1/text-to-speech/stream", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-KEY", "<api-key>")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
io.Copy(stdin, resp.Body)
stdin.Close()
ffplay.Wait()
}// Java 11+ HttpClient + InputStream body handler.
// 스트리밍 응답을 ffplay stdin으로 파이핑.
// 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.io.InputStream;
import java.io.OutputStream;
public class StreamingTTS {
public static void main(String[] args) throws Exception {
Process ffplay = new ProcessBuilder(
"ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0")
.redirectError(ProcessBuilder.Redirect.DISCARD)
.start();
String body = """
{
"voice_id": "tc_60e5426de8b95f1d3000d7b5",
"text": "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
"model": "ssfm-v30"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.typecast.ai/v1/text-to-speech/stream"))
.header("Content-Type", "application/json")
.header("X-API-KEY", "<api-key>")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<InputStream> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofInputStream());
try (InputStream in = response.body();
OutputStream out = ffplay.getOutputStream()) {
in.transferTo(out);
}
ffplay.waitFor();
}
}# IO.popen으로 ffplay를 띄우고 스트리밍 응답을 파이핑.
# 사전 설치: ffmpeg (brew/choco/apt install ffmpeg)
require "net/http"
require "uri"
require "json"
ffplay = IO.popen(
["ffplay", "-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"],
"wb",
)
uri = URI("https://api.typecast.ai/v1/text-to-speech/stream")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["X-API-KEY"] = "<api-key>"
req.body = {
voice_id: "tc_60e5426de8b95f1d3000d7b5",
text: "문의해 주셔서 감사합니다. 금요일 오후 7시로 예약이 확정되었습니다.",
model: "ssfm-v30",
}.to_json
http.request(req) do |response|
response.read_body { |chunk| ffplay.write(chunk) }
end
ffplay.close"[Binary audio stream - WAV chunks]"{
"detail": "Invalid voice_id"
}{
"detail": "Invalid API key"
}{
"detail": "Insufficient credit"
}{
"detail": "Voice not found"
}{
"detail": "Invalid request format"
}{
"detail": "Too many requests"
}{
"detail": "An unexpected error occurred"
}인증
인증을 위한 API 키. 타입캐스트 API 콘솔에서 API 키를 생성할 수 있습니다.
본문
스트리밍 텍스트 음성 변환 요청 파라미터
음성으로 변환할 텍스트. 최소 1자, 최대 2000자. 텍스트 길이에 따라 크레딧이 소비됩니다. 영어, 한국어, 일본어, 중국어를 포함한 여러 언어를 지원합니다. 특수 문자와 구두점은 자동으로 처리됩니다.
1 - 2000"모든 것이 너무나 완벽해서 마치 꿈을 꾸는 것 같습니다."
음성 합성에 사용할 보이스 모델.
- ssfm-v30: 향상된 플로우와 추가 감정 프리셋이 있는 최신 모델(권장)
- ssfm-v21: 빠르고 안정적인 모델로 신뢰할 수 있는 품질 제공
ssfm-v30, ssfm-v21 "ssfm-v30"
ISO 639-3 표준을 따르는 언어 코드. 대소문자 구분 안 함("KOR"과 "kor" 모두 허용). 제공하지 않으면 텍스트 내용을 기반으로 자동 감지됩니다.
ssfm-v30 지원 언어 (37개)
| 코드 | 언어 | 코드 | 언어 | 코드 | 언어 |
|---|---|---|---|---|---|
| ARA | 아랍어 | IND | 인도네시아어 | POR | 포르투갈어 |
| BEN | 벵골어 | ITA | 이탈리아어 | RON | 루마니아어 |
| BUL | 불가리아어 | JPN | 일본어 | RUS | 러시아어 |
| CES | 체코어 | KOR | 한국어 | SLK | 슬로바키아어 |
| DAN | 덴마크어 | MSA | 말레이어 | SPA | 스페인어 |
| DEU | 독일어 | NAN | 민남어 | SWE | 스웨덴어 |
| ELL | 그리스어 | NLD | 네덜란드어 | TAM | 타밀어 |
| ENG | 영어 | NOR | 노르웨이어 | TGL | 타갈로그어 |
| FIN | 핀란드어 | PAN | 펀자브어 | THA | 태국어 |
| FRA | 프랑스어 | POL | 폴란드어 | TUR | 터키어 |
| HIN | 힌디어 | UKR | 우크라이나어 | VIE | 베트남어 |
| HRV | 크로아티아어 | YUE | 광둥어 | ZHO | 중국어 |
| HUN | 헝가리어 |
ssfm-v21 지원 언어 (27개)
| 코드 | 언어 | 코드 | 언어 | 코드 | 언어 |
|---|---|---|---|---|---|
| ARA | 아랍어 | IND | 인도네시아어 | RON | 루마니아어 |
| BUL | 불가리아어 | ITA | 이탈리아어 | RUS | 러시아어 |
| CES | 체코어 | JPN | 일본어 | SLK | 슬로바키아어 |
| DAN | 덴마크어 | KOR | 한국어 | SPA | 스페인어 |
| DEU | 독일어 | MSA | 말레이어 | SWE | 스웨덴어 |
| ELL | 그리스어 | NLD | 네덜란드어 | TAM | 타밀어 |
| ENG | 영어 | POL | 폴란드어 | TGL | 타갈로그어 |
| FIN | 핀란드어 | POR | 포르투갈어 | UKR | 우크라이나어 |
| FRA | 프랑스어 | HRV | 크로아티아어 | ZHO | 중국어 |
"kor"
생성된 음성의 감정 및 스타일 설정.
- 스마트 프롬프트 (ssfm-v30)
- 프리셋 프롬프트 (ssfm-v30)
- 프롬프트 (ssfm-v21)
Show child attributes
Show child attributes
{
"emotion_type": "smart",
"previous_text": "I feel like I'm walking on air and I just want to scream with joy!",
"next_text": "I am literally bursting with happiness and I never want this feeling to end!"
}
피치(-12 ~ +12 반음), 속도(0.5x ~ 2.0x), 형식(wav/mp3), target_lufs(-70 ~ 0 LUFS) 등 스트리밍 오디오 출력 설정. 참고: 스트리밍 모드에서는 volume을 사용할 수 없습니다.
Show child attributes
Show child attributes
재현 가능한 음성 생성을 위한 부호 없는 정수 시드. 동일한 시드와 동일한 입력 파라미터로 항상 같은 오디오 결과를 생성합니다.
- 0 이상의 정수만 허용됩니다. 음수 값은 사용할 수 없습니다.
- 생략하면 서버가 매번 랜덤 시드를 생성하여 약간의 변이가 발생합니다.
0 <= x <= 429496729542
응답
Success - Returns streaming audio data in chunks
청크 단위 WAV 오디오 스트림(16비트, 모노, 32000 Hz). 첫 번째 청크에는 size 0xFFFFFFFF(스트리밍 표시)의 WAV 헤더와 원시 PCM 데이터가 포함됩니다. 이후 청크에는 PCM 데이터만 포함됩니다.