動機
下記の記事を最近の windows クレート仕様に書き換えてみたかったのと、音声ファイルとして保存する方法を調べてみました。
環境
rustc 1.79.0
Windows 10 22H2
実装
短いので全部載せます。
Cargo.toml
[package]
name = "speech"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
[dependencies.windows]
version = "0.57"
features = [
"Media_Core",
"Media_Playback",
"Media_SpeechSynthesis",
"Storage_Streams",
"Win32_System_WinRT",
]
main.rs
use anyhow::Result;
use std::fs;
use std::path::Path;
use std::slice;
use std::sync::mpsc;
use windows::{
core::{Interface, HSTRING},
Foundation::TypedEventHandler,
Media::{
Core::MediaSource,
Playback::MediaPlayer,
SpeechSynthesis::{SpeechSynthesisStream, SpeechSynthesizer},
},
Storage::Streams::DataReader,
Win32::System::WinRT::IBufferByteAccess,
};
fn speech_synthesis_stream(source: &str) -> Result<SpeechSynthesisStream> {
let utf16 = source.encode_utf16().collect::<Vec<_>>();
let source = HSTRING::from_wide(&utf16)?;
let synth = SpeechSynthesizer::new()?;
let stream = synth.SynthesizeTextToStreamAsync(&source)?.get()?;
Ok(stream)
}
fn speech(source: &str) -> Result<()> {
let stream = speech_synthesis_stream(source)?;
let player = MediaPlayer::new()?;
let source = MediaSource::CreateFromStream(&stream, &stream.ContentType()?)?;
player.SetSource(&source)?;
let (tx, rx) = mpsc::channel();
let tx_clone = tx.clone();
// イベントハンドラの登録
// 再生終了時に呼ばれる
let token_media_ended = player.MediaEnded(&TypedEventHandler::new(move |_, _| {
tx_clone.send(()).ok();
Ok(())
}))?;
// 再生失敗時に呼ばれる?
let token_media_failed = player.MediaFailed(&TypedEventHandler::new(move |_, _| {
tx.send(()).ok();
Ok(())
}))?;
player.Play()?;
// 再生終了を待つ
rx.recv()?;
// イベントハンドラの削除
player.RemoveMediaEnded(token_media_ended)?;
player.RemoveMediaFailed(token_media_failed)?;
Ok(())
}
fn save_to_wav<P: AsRef<Path>>(path: P, source: &str) -> Result<()> {
let stream = speech_synthesis_stream(source)?;
// ストリームがらデータを読む
let reader = DataReader::CreateDataReader(&stream)?;
let size = stream.Size()? as u32;
reader.LoadAsync(size)?.get()?;
// 生ポインタを取得するために IBuffer を IBufferByteAccess にキャスト
let buffer: IBufferByteAccess = reader.ReadBuffer(size)?.cast()?;
let ptr = unsafe { buffer.Buffer()? };
// Rust の世界のスライスに変換
let slice = unsafe { slice::from_raw_parts(ptr, size as usize) };
// 保存
fs::write(path, slice)?;
Ok(())
}
fn main() -> Result<()> {
// 「こんにちは」って喋る
speech("こんにちは")?;
// 「こんばんは」という WAV ファイルを指定したパスに保存
save_to_wav("test.wav", "こんばんは")?;
Ok(())
}
以上です。