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客户端

Download history 3/week @ 2024-04-27 4/week @ 2024-05-18 1/week @ 2024-05-25 1/week @ 2024-06-29 9/week @ 2024-07-06

169 每月下载量

Apache-2.0

50KB
762

playht_rs

Crates.io Version Build Status License: Apache-2.0

非官方的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