4个版本 (2个重大更新)
0.2.0 | 2024年4月17日 |
---|---|
0.1.0 | 2024年4月16日 |
0.0.2 | 2024年4月8日 |
0.0.1 | 2024年4月7日 |
#123 in HTTP客户端
169 每月下载量
50KB
762 行
playht_rs
非官方的play.ht Rust API客户端crate。类似于Go模块实现。
为了使用此crate,您必须在play.ht上创建一个账户,生成API密钥并获取您的用户ID。有关更多信息,请参阅官方文档此处。
基本
使用API从文本创建音频/语音有两种方式
- 作业:音频生成是异步执行的;创建作业时,您可以通过SSE监控其进度
- 流:一旦通过API创建,立即可用的实时音频流
API还允许您使用有限大小的音频样本克隆声音。请参阅文档。
开始
[!IMPORTANT] 在尝试运行任何示例之前,您必须设置一些环境变量。这些变量在客户端创建时自动读取;您可以在自己的代码中覆盖它们。
PLAYHT_SECRET_KEY
:API密钥PLAYHT_USER_ID
:Play.HT用户ID
检查crate
cargo check
构建crate
cargo build
示例
在示例目录中有相当多的示例,请查看。它们可以给您一些关于如何使用此crate的灵感。下面我们列出了一些代码示例
克隆声音
从音频样本文件中克隆新的声音。
[!NOTE] 您必须将样本文件和MIME类型作为CLI参数传递
//! `cargo run --example clone_voices`
use playht_rs::{
api::{self, voice::CloneVoiceFileRequest, voice::DeleteClonedVoiceRequest},
prelude::*,
};
use tokio;
#[tokio::main]
async fn main() -> Result<()> {
let mut args = std::env::args().skip(1);
let sample_file = args.next().unwrap();
let mime_type = args.next().unwrap();
let req = CloneVoiceFileRequest {
sample_file,
mime_type,
voice_name: "foo-bar".to_owned(),
};
let client = api::Client::new();
let voice = client.clone_voice_from_file(req).await?;
println!("Got voice clone: {:?}", voice);
let cloned_voices = client.get_cloned_voices().await?;
println!("Got voice clones: {:?}", cloned_voices);
let req = DeleteClonedVoiceRequest { voice_id: voice.id };
let delete_resp = client.delete_cloned_voice(req).await?;
println!("Got delete response: {:?}", delete_resp);
Ok(())
}
创建异步TTS作业
创建一个异步TTS作业并获取其元数据。
[!NOTE] 可以通过PlayHT API监控异步TTS作业的进度。
//! `cargo run --example tts_jobs`
use playht_rs::{
api::{self, job::TTSJobReq, tts::Quality},
prelude::*,
};
use tokio;
#[tokio::main]
async fn main() -> Result<()> {
let client = api::Client::new();
let voices = client.get_stock_voices().await?;
if voices.is_empty() {
return Err("No voices available".into());
}
let req = TTSJobReq {
text: Some("What is life?".to_owned()),
voice: Some(voices[0].id.clone()),
quality: Some(Quality::Low),
speed: Some(1.0),
sample_rate: Some(24000),
..Default::default()
};
let tts_job = client.create_tts_job(req).await?;
println!("TTS job created: {:?}", tts_job);
let tts_job = client.get_tts_job(tts_job.id).await?;
println!("Got TTS job: {:?}", tts_job);
Ok(())
}
流式传输TTS音频
实时将TTS音频流式传输到文件中。文件通过CLI参数提供,但您也可以传递异步写入器实现,例如音频设备tokio包装器等。
[!注意] 您必须将输出文件路径作为命令行参数传递。
//! `cargo run --example tts_write_audio_stream -- "foobar.mp3"`
use playht_rs::{
api::{self, stream::TTSStreamReq, tts::Quality},
prelude::*,
};
use tokio::{fs::File, io::BufWriter};
#[tokio::main]
async fn main() -> Result<()> {
let mut args = std::env::args().skip(1);
let file_path = args.next().unwrap();
let client = api::Client::new();
let voices = client.get_stock_voices().await?;
if voices.is_empty() {
return Err("No voices available".into());
}
let req = TTSStreamReq {
text: Some("What is life?".to_owned()),
voice: Some(voices[0].id.to_owned()),
quality: Some(Quality::Low),
speed: Some(1.0),
sample_rate: Some(24000),
..Default::default()
};
let file = File::create(file_path.clone()).await?;
let mut w = BufWriter::new(file);
client.write_audio_stream(&mut w, req).await?;
println!("Done streaming into {}", file_path);
Ok(())
}
从文件播放TTS音频
//! `cargo run --example play_audio -- "/path/to/audio.mp3"`
use rodio::{Decoder, OutputStream, Sink};
use std::{fs::File, io::BufReader};
fn main() {
let mut args = std::env::args().skip(1);
let sound_file = args.next().unwrap();
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let file = BufReader::new(File::open(&sound_file).unwrap());
let source = Decoder::new(file).unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
sink.append(source);
sink.sleep_until_end();
}
播放TTS音频流数据
[!注意] 这实际上并不进行流式播放!它会将所有数据检索到缓冲区中,然后发送数据进行播放。如果您需要实时播放流,请查看下面的
tts_stream_audio
示例。
//! `cargo run --example tts_play_audio_stream`
use playht_rs::{
api::{self, stream::TTSStreamReq, tts::Quality},
prelude::*,
};
use rodio::{Decoder, OutputStream, Sink};
use std::io::Cursor;
#[tokio::main]
async fn main() -> Result<()> {
let client = api::Client::new();
let voices = client.get_stock_voices().await?;
if voices.is_empty() {
return Err("No voices available".into());
}
let req = TTSStreamReq {
text: Some("What is life?".to_owned()),
voice: Some(voices[0].id.to_owned()),
quality: Some(Quality::Low),
speed: Some(1.0),
sample_rate: Some(24000),
..Default::default()
};
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
let mut buffer = Vec::new();
client.write_audio_stream(&mut buffer, req).await?;
let source = Decoder::new(Cursor::new(buffer)).unwrap();
sink.append(source);
sink.sleep_until_end();
Ok(())
}
实时流式传输TTS音频
//! ` cargo run --example tts_stream_audio`
use bytes::BytesMut;
use playht_rs::{
api::{self, stream::TTSStreamReq, tts::Quality},
prelude::*,
};
use rodio::{Decoder, OutputStream, Sink};
use std::io::Cursor;
use tokio_stream::StreamExt;
// NOTE: this might need to be adjusted
const BUFFER_SIZE: usize = 1024 * 10;
#[tokio::main]
async fn main() -> Result<()> {
let client = api::Client::new();
let voices = client.get_stock_voices().await?;
if voices.is_empty() {
return Err("No voices available for playback".into());
}
let client = api::Client::new();
let req = TTSStreamReq {
text: Some("What is life?".to_owned()),
voice: Some(voices[0].id.to_owned()),
quality: Some(Quality::Low),
speed: Some(1.0),
sample_rate: Some(24000),
..Default::default()
};
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
let mut stream = client.stream_audio(req).await?;
let mut accumulated = BytesMut::new();
while let Some(res) = stream.next().await {
match res {
Ok(chunk) => {
accumulated.extend_from_slice(&chunk);
// Check if there's enough data to attempt decoding
if accumulated.len() > BUFFER_SIZE {
let cursor = Cursor::new(accumulated.clone().freeze().to_vec());
match Decoder::new(cursor) {
Ok(source) => {
sink.append(source);
accumulated.clear(); // Clear the buffer on successful append
}
Err(e) => {
eprintln!("Failed to decode received audio: {}", e);
}
}
}
}
Err(err) => return Err(format!("Playback error: {}", err).into()),
}
}
// Flush any remaining data at the end
if !accumulated.is_empty() {
let cursor = Cursor::new(accumulated.to_vec());
match Decoder::new(cursor) {
Ok(source) => sink.append(source),
Err(e) => println!("Remaining data could not be decoded: {}", e),
}
}
sink.sleep_until_end();
Ok(())
}
Nix
有一个Nix flake可用,它允许您在nix shell中处理Rust创建。
只需运行以下命令,您就可以开始工作了
nix develop
TODO
- gRPC流
- 清理杂乱的代码
依赖项
~6–18MB
~256K SLoC