rvsttd: model fetch mode, build from source, ~/.rvsttd convention
This commit is contained in:
+106
-1
@@ -1,10 +1,12 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::{SampleFormat, SampleRate};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::CStr;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
@@ -353,11 +355,112 @@ fn start_cpal(
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
// ─── model fetch ──────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Manifest {
|
||||
groups: Vec<ManifestGroup>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ManifestGroup {
|
||||
#[allow(dead_code)]
|
||||
base_url: String,
|
||||
files: Vec<ManifestFile>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ManifestFile {
|
||||
name: String,
|
||||
url: String,
|
||||
size: Option<u64>,
|
||||
}
|
||||
|
||||
fn fetch_model(model_dir: &str) -> Result<()> {
|
||||
let dest = PathBuf::from(model_dir);
|
||||
log(&format!("Fetching medium-streaming-en model to {}", dest.display()));
|
||||
|
||||
let lang = std::ffi::CString::new("en").unwrap();
|
||||
let opt_name = std::ffi::CString::new("model_arch").unwrap();
|
||||
let opt_value = std::ffi::CString::new("5").unwrap();
|
||||
let mut options = [moonshine_option_t {
|
||||
name: opt_name.as_ptr(),
|
||||
value: opt_value.as_ptr(),
|
||||
}];
|
||||
|
||||
let mut json_ptr: *mut i8 = std::ptr::null_mut();
|
||||
let rc = unsafe {
|
||||
moonshine_get_stt_dependencies(
|
||||
lang.as_ptr(),
|
||||
options.as_mut_ptr(),
|
||||
options.len() as u64,
|
||||
&mut json_ptr,
|
||||
)
|
||||
};
|
||||
|
||||
if rc != 0 || json_ptr.is_null() {
|
||||
return Err(anyhow!("moonshine_get_stt_dependencies failed: {}", err_str(rc)));
|
||||
}
|
||||
|
||||
let json_str = unsafe { CStr::from_ptr(json_ptr) }
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
unsafe { moonshine_free_buffer(json_ptr as *mut std::ffi::c_void) };
|
||||
|
||||
let manifest: Manifest = serde_json::from_str(&json_str)?;
|
||||
|
||||
std::fs::create_dir_all(&dest)?;
|
||||
|
||||
let mut total_files = 0;
|
||||
let mut total_bytes: u64 = 0;
|
||||
|
||||
for group in &manifest.groups {
|
||||
for file in &group.files {
|
||||
let dest_path = dest.join(&file.name);
|
||||
if dest_path.exists() {
|
||||
log(&format!(" SKIP {} (already exists)", file.name));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
log(&format!(" GET {}", file.url));
|
||||
let response = ureq::get(&file.url).call()?;
|
||||
|
||||
if let Some(expected) = file.size {
|
||||
log(&format!(" {} bytes", expected));
|
||||
total_bytes += expected;
|
||||
}
|
||||
|
||||
let mut file_handle = std::fs::File::create(&dest_path)?;
|
||||
std::io::copy(&mut response.into_reader(), &mut file_handle)?;
|
||||
total_files += 1;
|
||||
}
|
||||
}
|
||||
|
||||
log(&format!("Done: {} files, ~{} MB", total_files, total_bytes / (1024 * 1024)));
|
||||
log(&format!("Model directory: {}", dest.display()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ─── main ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let mut model_dir = String::from("../bench/medium-streaming-en");
|
||||
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
|
||||
let default_model = format!("{}/.rvsttd/model", home);
|
||||
|
||||
let first = args.next();
|
||||
if first.as_deref() == Some("fetch") {
|
||||
let model_dir = args.next().unwrap_or_else(|| default_model.clone());
|
||||
return fetch_model(&model_dir);
|
||||
}
|
||||
|
||||
let mut args = first.into_iter().chain(args);
|
||||
let mut model_dir = default_model;
|
||||
|
||||
while let Some(a) = args.next() {
|
||||
match a.as_str() {
|
||||
@@ -366,8 +469,10 @@ fn main() -> Result<()> {
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
println!("Usage: rvsttd [--model-dir DIR]");
|
||||
println!(" rvsttd fetch [DIR]");
|
||||
println!("Listens on TCP {}", BIND_ADDR);
|
||||
println!("Model: medium-streaming (Moonshine)");
|
||||
println!("'rvsttd fetch' downloads the English medium-streaming model");
|
||||
return Ok(());
|
||||
}
|
||||
_ => return Err(anyhow!("unknown arg: {}", a)),
|
||||
|
||||
Reference in New Issue
Block a user