11
This commit is contained in:
170
src-tauri/src/asr_db.rs
Normal file
170
src-tauri/src/asr_db.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
//! 语音识别历史 SQLite
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use chrono::Utc;
|
||||
use rusqlite::{params, Connection};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
const MAX_RECORDS: i64 = 50;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AsrDbError {
|
||||
#[error("{0}")]
|
||||
Db(#[from] rusqlite::Error),
|
||||
#[error("{0}")]
|
||||
Message(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AsrHistoryRecord {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub source_path: String,
|
||||
pub result_text: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AsrDb {
|
||||
conn: Mutex<Connection>,
|
||||
}
|
||||
|
||||
impl AsrDb {
|
||||
pub fn open(path: PathBuf) -> Result<Self, AsrDbError> {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| {
|
||||
AsrDbError::Message(format!("创建数据库目录失败: {e}"))
|
||||
})?;
|
||||
}
|
||||
let conn = Connection::open(path)?;
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
PRAGMA journal_mode = WAL;
|
||||
CREATE TABLE IF NOT EXISTS asr_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
source_path TEXT NOT NULL,
|
||||
result_text TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
"#,
|
||||
)?;
|
||||
Ok(Self {
|
||||
conn: Mutex::new(conn),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list(&self) -> Result<Vec<AsrHistoryRecord>, AsrDbError> {
|
||||
let conn = self.conn.lock().map_err(|_| {
|
||||
AsrDbError::Message("ASR 数据库锁异常".into())
|
||||
})?;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, name, source_path, result_text, created_at
|
||||
FROM asr_history ORDER BY id DESC LIMIT ?1",
|
||||
)?;
|
||||
let rows = stmt.query_map(params![MAX_RECORDS], |row| {
|
||||
Ok(AsrHistoryRecord {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
source_path: row.get(2)?,
|
||||
result_text: row.get(3)?,
|
||||
created_at: row.get(4)?,
|
||||
})
|
||||
})?;
|
||||
let mut items = Vec::new();
|
||||
for row in rows {
|
||||
items.push(row?);
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&self,
|
||||
name: &str,
|
||||
source_path: &str,
|
||||
result_text: &str,
|
||||
) -> Result<AsrHistoryRecord, AsrDbError> {
|
||||
let source_path = source_path.trim();
|
||||
if source_path.is_empty() {
|
||||
return Err(AsrDbError::Message("源文件路径不能为空".into()));
|
||||
}
|
||||
let name = name.trim();
|
||||
let name = if name.is_empty() {
|
||||
source_path
|
||||
.replace('\\', "/")
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.unwrap_or("语音识别")
|
||||
.to_string()
|
||||
} else {
|
||||
name.to_string()
|
||||
};
|
||||
let created_at = Utc::now().to_rfc3339();
|
||||
let conn = self.conn.lock().map_err(|_| {
|
||||
AsrDbError::Message("ASR 数据库锁异常".into())
|
||||
})?;
|
||||
conn.execute(
|
||||
"INSERT INTO asr_history (name, source_path, result_text, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
params![name, source_path, result_text.trim(), created_at],
|
||||
)?;
|
||||
let id = conn.last_insert_rowid();
|
||||
self.prune_excess(&conn)?;
|
||||
Ok(AsrHistoryRecord {
|
||||
id,
|
||||
name,
|
||||
source_path: source_path.to_string(),
|
||||
result_text: result_text.trim().to_string(),
|
||||
created_at,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_name(&self, id: i64, name: &str) -> Result<(), AsrDbError> {
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return Err(AsrDbError::Message("名称不能为空".into()));
|
||||
}
|
||||
let conn = self.conn.lock().map_err(|_| {
|
||||
AsrDbError::Message("ASR 数据库锁异常".into())
|
||||
})?;
|
||||
let n = conn.execute(
|
||||
"UPDATE asr_history SET name = ?1 WHERE id = ?2",
|
||||
params![name, id],
|
||||
)?;
|
||||
if n == 0 {
|
||||
return Err(AsrDbError::Message("记录不存在".into()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prune_excess(&self, conn: &Connection) -> Result<(), AsrDbError> {
|
||||
let count: i64 = conn.query_row(
|
||||
"SELECT COUNT(1) FROM asr_history",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
if count <= MAX_RECORDS {
|
||||
return Ok(());
|
||||
}
|
||||
let excess = count - MAX_RECORDS;
|
||||
conn.execute(
|
||||
"DELETE FROM asr_history WHERE id IN (
|
||||
SELECT id FROM asr_history ORDER BY id ASC LIMIT ?1
|
||||
)",
|
||||
params![excess],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(&self, id: i64) -> Result<bool, AsrDbError> {
|
||||
let conn = self.conn.lock().map_err(|_| {
|
||||
AsrDbError::Message("ASR 数据库锁异常".into())
|
||||
})?;
|
||||
let n = conn.execute("DELETE FROM asr_history WHERE id = ?1", params![id])?;
|
||||
Ok(n > 0)
|
||||
}
|
||||
}
|
||||
83
src-tauri/src/asr_store.rs
Normal file
83
src-tauri/src/asr_store.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//! 语音识别历史:SQLite 访问与 Tauri 状态
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::asr_db::{AsrDb, AsrHistoryRecord};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AsrStore {
|
||||
inner: Arc<RwLock<Option<Arc<AsrDb>>>>,
|
||||
}
|
||||
|
||||
impl AsrStore {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn init(&self, db_path: PathBuf) -> Result<(), String> {
|
||||
let db = tokio::task::spawn_blocking(move || {
|
||||
AsrDb::open(db_path).map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())??;
|
||||
|
||||
let count = tokio::task::spawn_blocking({
|
||||
let db = Arc::new(db);
|
||||
let db2 = db.clone();
|
||||
move || db2.list().map(|l| (db, l.len())).map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())??;
|
||||
|
||||
let (db, n) = count;
|
||||
*self.inner.write().await = Some(db);
|
||||
log::info!(target: "asr", "ASR 历史数据库已初始化,共 {n} 条记录");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn with_db<F, T>(&self, f: F) -> Result<T, String>
|
||||
where
|
||||
F: FnOnce(Arc<AsrDb>) -> Result<T, String> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let db = self
|
||||
.inner
|
||||
.read()
|
||||
.await
|
||||
.clone()
|
||||
.ok_or_else(|| "ASR 数据库未初始化".to_string())?;
|
||||
tokio::task::spawn_blocking(move || f(db))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
pub async fn list(&self) -> Result<Vec<AsrHistoryRecord>, String> {
|
||||
self.with_db(|db| db.list().map_err(|e| e.to_string())).await
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
&self,
|
||||
name: String,
|
||||
source_path: String,
|
||||
result_text: String,
|
||||
) -> Result<AsrHistoryRecord, String> {
|
||||
self.with_db(move |db| {
|
||||
db.insert(&name, &source_path, &result_text)
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_name(&self, id: i64, name: String) -> Result<(), String> {
|
||||
self.with_db(move |db| db.update_name(id, &name).map_err(|e| e.to_string()))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(&self, id: i64) -> Result<bool, String> {
|
||||
self.with_db(move |db| db.delete(id).map_err(|e| e.to_string()))
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ pub struct GeneratedAudioRecord {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub file_path: String,
|
||||
pub source_text: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
@@ -47,10 +48,15 @@ impl AudioDb {
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
file_path TEXT NOT NULL,
|
||||
source_text TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
"#,
|
||||
)?;
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE generated_audios ADD COLUMN source_text TEXT NOT NULL DEFAULT ''",
|
||||
[],
|
||||
);
|
||||
Ok(Self {
|
||||
conn: Mutex::new(conn),
|
||||
})
|
||||
@@ -72,14 +78,15 @@ impl AudioDb {
|
||||
AudioDbError::Message("语音数据库锁异常".into())
|
||||
})?;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, name, file_path, created_at FROM generated_audios ORDER BY id DESC LIMIT ?1",
|
||||
"SELECT id, name, file_path, source_text, created_at FROM generated_audios ORDER BY id DESC LIMIT ?1",
|
||||
)?;
|
||||
let rows = stmt.query_map(params![MAX_RECORDS], |row| {
|
||||
Ok(GeneratedAudioRecord {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
file_path: row.get(2)?,
|
||||
created_at: row.get(3)?,
|
||||
source_text: row.get(3)?,
|
||||
created_at: row.get(4)?,
|
||||
})
|
||||
})?;
|
||||
let mut items = Vec::new();
|
||||
@@ -94,14 +101,15 @@ impl AudioDb {
|
||||
AudioDbError::Message("语音数据库锁异常".into())
|
||||
})?;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, name, file_path, created_at FROM generated_audios WHERE id = ?1",
|
||||
"SELECT id, name, file_path, source_text, created_at FROM generated_audios WHERE id = ?1",
|
||||
)?;
|
||||
let mut rows = stmt.query_map(params![id], |row| {
|
||||
Ok(GeneratedAudioRecord {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
file_path: row.get(2)?,
|
||||
created_at: row.get(3)?,
|
||||
source_text: row.get(3)?,
|
||||
created_at: row.get(4)?,
|
||||
})
|
||||
})?;
|
||||
match rows.next() {
|
||||
@@ -110,7 +118,30 @@ impl AudioDb {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&self, name: &str, file_path: &str) -> Result<GeneratedAudioRecord, AudioDbError> {
|
||||
pub fn update_name(&self, id: i64, name: &str) -> Result<(), AudioDbError> {
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return Err(AudioDbError::Message("名称不能为空".into()));
|
||||
}
|
||||
let conn = self.conn.lock().map_err(|_| {
|
||||
AudioDbError::Message("语音数据库锁异常".into())
|
||||
})?;
|
||||
let n = conn.execute(
|
||||
"UPDATE generated_audios SET name = ?1 WHERE id = ?2",
|
||||
params![name, id],
|
||||
)?;
|
||||
if n == 0 {
|
||||
return Err(AudioDbError::Message("记录不存在".into()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&self,
|
||||
name: &str,
|
||||
file_path: &str,
|
||||
source_text: &str,
|
||||
) -> Result<GeneratedAudioRecord, AudioDbError> {
|
||||
let file_path = file_path.trim();
|
||||
if file_path.is_empty() {
|
||||
return Err(AudioDbError::Message("文件路径不能为空".into()));
|
||||
@@ -125,9 +156,10 @@ impl AudioDb {
|
||||
let conn = self.conn.lock().map_err(|_| {
|
||||
AudioDbError::Message("语音数据库锁异常".into())
|
||||
})?;
|
||||
let source_text = source_text.trim().to_string();
|
||||
conn.execute(
|
||||
"INSERT INTO generated_audios (name, file_path, created_at) VALUES (?1, ?2, ?3)",
|
||||
params![name, file_path, created_at],
|
||||
"INSERT INTO generated_audios (name, file_path, source_text, created_at) VALUES (?1, ?2, ?3, ?4)",
|
||||
params![name, file_path, source_text, created_at],
|
||||
)?;
|
||||
let id = conn.last_insert_rowid();
|
||||
self.prune_excess(&conn)?;
|
||||
@@ -135,6 +167,7 @@ impl AudioDb {
|
||||
id,
|
||||
name,
|
||||
file_path: file_path.to_string(),
|
||||
source_text,
|
||||
created_at,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -58,8 +58,21 @@ impl AudioStore {
|
||||
self.with_db(|db| db.list().map_err(|e| e.to_string())).await
|
||||
}
|
||||
|
||||
pub async fn insert(&self, name: String, file_path: String) -> Result<GeneratedAudioRecord, String> {
|
||||
self.with_db(move |db| db.insert(&name, &file_path).map_err(|e| e.to_string()))
|
||||
pub async fn insert(
|
||||
&self,
|
||||
name: String,
|
||||
file_path: String,
|
||||
source_text: String,
|
||||
) -> Result<GeneratedAudioRecord, String> {
|
||||
self.with_db(move |db| {
|
||||
db.insert(&name, &file_path, &source_text)
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_name(&self, id: i64, name: String) -> Result<(), String> {
|
||||
self.with_db(move |db| db.update_name(id, &name).map_err(|e| e.to_string()))
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,20 @@ pub async fn insert_generated_audio(
|
||||
store: State<'_, AudioStore>,
|
||||
name: String,
|
||||
file_path: String,
|
||||
#[allow(unused)] source_text: Option<String>,
|
||||
) -> Result<GeneratedAudioRecord, String> {
|
||||
store.insert(name, file_path).await
|
||||
store
|
||||
.insert(name, file_path, source_text.unwrap_or_default())
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_generated_audio_name(
|
||||
store: State<'_, AudioStore>,
|
||||
id: i64,
|
||||
name: String,
|
||||
) -> Result<(), String> {
|
||||
store.update_name(id, name).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod auth;
|
||||
pub mod audio;
|
||||
pub mod avatar;
|
||||
pub mod material;
|
||||
pub mod sound_asr;
|
||||
pub mod video;
|
||||
pub mod fs_util;
|
||||
pub mod nodejs;
|
||||
|
||||
162
src-tauri/src/commands/sound_asr.rs
Normal file
162
src-tauri/src/commands/sound_asr.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
//! 本地媒体文件语音识别(DashScope + OSS)
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use tauri::State;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::app_config::AppConfig;
|
||||
use crate::asr;
|
||||
use crate::asr_store::AsrStore;
|
||||
use crate::ffmpeg;
|
||||
use crate::oss::{self, OssConfig};
|
||||
|
||||
const VIDEO_EXTS: &[&str] = &["mp4", "avi", "mov", "mkv", "webm", "m4v", "flv"];
|
||||
|
||||
fn normalize_path(path: &str) -> PathBuf {
|
||||
Path::new(path)
|
||||
.components()
|
||||
.filter(|c| !matches!(c, Component::ParentDir))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn config_get(map: &HashMap<String, String>, keys: &[&str]) -> Option<String> {
|
||||
for key in keys {
|
||||
if let Some(v) = map.get(*key) {
|
||||
let t = v.trim();
|
||||
if !t.is_empty() {
|
||||
return Some(t.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn build_oss_config(map: &HashMap<String, String>) -> Result<OssConfig, String> {
|
||||
let access_key_id = config_get(
|
||||
map,
|
||||
&[
|
||||
"ALIYUN_OSS_ACCESS_KEY_ID",
|
||||
"OSS_ACCESS_KEY_ID",
|
||||
"ALIYUN_ACCESS_KEY_ID",
|
||||
],
|
||||
)
|
||||
.ok_or_else(|| "缺少 OSS AccessKeyId,请在本地配置中设置".to_string())?;
|
||||
let access_key_secret = config_get(
|
||||
map,
|
||||
&[
|
||||
"ALIYUN_OSS_ACCESS_KEY_SECRET",
|
||||
"OSS_ACCESS_KEY_SECRET",
|
||||
"ALIYUN_ACCESS_KEY_SECRET",
|
||||
],
|
||||
)
|
||||
.ok_or_else(|| "缺少 OSS AccessKeySecret".to_string())?;
|
||||
let bucket = config_get(map, &["ALIYUN_OSS_BUCKET", "OSS_BUCKET"])
|
||||
.ok_or_else(|| "缺少 OSS Bucket".to_string())?;
|
||||
let region = config_get(map, &["ALIYUN_OSS_REGION", "OSS_REGION"])
|
||||
.ok_or_else(|| "缺少 OSS Region".to_string())?;
|
||||
let endpoint = config_get(map, &["ALIYUN_OSS_ENDPOINT", "OSS_ENDPOINT"]);
|
||||
Ok(OssConfig {
|
||||
access_key_id,
|
||||
access_key_secret,
|
||||
bucket,
|
||||
region,
|
||||
endpoint,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_video_path(path: &Path) -> bool {
|
||||
path.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| VIDEO_EXTS.contains(&e.to_lowercase().as_str()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_asr_history(store: State<'_, AsrStore>) -> Result<Vec<crate::asr_db::AsrHistoryRecord>, String> {
|
||||
store.list().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_asr_history(store: State<'_, AsrStore>, id: i64) -> Result<(), String> {
|
||||
let ok = store.delete(id).await?;
|
||||
if !ok {
|
||||
return Err("记录不存在".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_asr_history_name(
|
||||
store: State<'_, AsrStore>,
|
||||
id: i64,
|
||||
name: String,
|
||||
) -> Result<(), String> {
|
||||
store.update_name(id, name).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn transcribe_media_file(
|
||||
app_config: State<'_, AppConfig>,
|
||||
asr_store: State<'_, AsrStore>,
|
||||
file_path: String,
|
||||
model: Option<String>,
|
||||
) -> Result<crate::asr_db::AsrHistoryRecord, String> {
|
||||
let src = normalize_path(file_path.trim());
|
||||
if !src.is_file() {
|
||||
return Err(format!("文件不存在: {}", src.display()));
|
||||
}
|
||||
|
||||
let map = app_config.entries().await;
|
||||
let api_key = config_get(
|
||||
&map,
|
||||
&["BAILIAN_API_KEY", "DASHSCOPE_API_KEY", "ALIYUN_BAILIAN_API_KEY"],
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
"缺少 BAILIAN_API_KEY(或 DASHSCOPE_API_KEY),请在本地配置中设置".to_string()
|
||||
})?;
|
||||
let oss_cfg = build_oss_config(&map)?;
|
||||
let model = model
|
||||
.filter(|m| !m.trim().is_empty())
|
||||
.unwrap_or_else(|| "qwen3-asr-flash-filetrans".to_string());
|
||||
|
||||
let temp_dir = std::env::temp_dir().join("aiclient-asr");
|
||||
tokio::fs::create_dir_all(&temp_dir)
|
||||
.await
|
||||
.map_err(|e| format!("创建临时目录失败: {e}"))?;
|
||||
|
||||
let upload_path = if is_video_path(&src) {
|
||||
let wav = temp_dir.join(format!("{}.wav", Uuid::new_v4()));
|
||||
ffmpeg::extract_audio(&src, &wav)
|
||||
.await
|
||||
.map_err(|e| format!("提取音频失败: {e}"))?;
|
||||
wav
|
||||
} else {
|
||||
src.clone()
|
||||
};
|
||||
|
||||
let key = format!("audio/{}.wav", Uuid::new_v4());
|
||||
let audio_url = oss::put_object(&oss_cfg, &upload_path, &key, "audio/wav")
|
||||
.await
|
||||
.map_err(|e| format!("上传音频到 OSS 失败: {e}"))?;
|
||||
|
||||
let result = asr::transcribe(&audio_url, &api_key, &model)
|
||||
.await
|
||||
.map_err(|e| format!("语音识别失败: {e}"))?;
|
||||
|
||||
if !result.success {
|
||||
return Err(result
|
||||
.error
|
||||
.unwrap_or_else(|| "语音识别失败".into()));
|
||||
}
|
||||
|
||||
let text = result
|
||||
.text
|
||||
.filter(|t| !t.trim().is_empty())
|
||||
.ok_or_else(|| "未识别到有效文本".to_string())?;
|
||||
|
||||
asr_store
|
||||
.insert(String::new(), src.to_string_lossy().to_string(), text)
|
||||
.await
|
||||
}
|
||||
@@ -7,6 +7,8 @@ pub mod avatar_db;
|
||||
pub mod avatar_store;
|
||||
pub mod material_db;
|
||||
pub mod material_store;
|
||||
pub mod asr_db;
|
||||
pub mod asr_store;
|
||||
pub mod video_db;
|
||||
pub mod video_store;
|
||||
pub mod local_config;
|
||||
@@ -38,6 +40,7 @@ pub fn run() {
|
||||
.manage(app_config::AppConfig::new())
|
||||
.manage(avatar_store::AvatarStore::new())
|
||||
.manage(material_store::MaterialStore::new())
|
||||
.manage(asr_store::AsrStore::new())
|
||||
.manage(audio_store::AudioStore::new())
|
||||
.manage(video_store::VideoStore::new())
|
||||
.manage(publish_store::PublishStore::new())
|
||||
@@ -54,6 +57,7 @@ pub fn run() {
|
||||
let app_config = handle.state::<app_config::AppConfig>();
|
||||
let avatar_store = handle.state::<avatar_store::AvatarStore>();
|
||||
let material_store = handle.state::<material_store::MaterialStore>();
|
||||
let asr_store = handle.state::<asr_store::AsrStore>();
|
||||
let audio_store = handle.state::<audio_store::AudioStore>();
|
||||
let video_store = handle.state::<video_store::VideoStore>();
|
||||
let publish_store = handle.state::<publish_store::PublishStore>();
|
||||
@@ -65,6 +69,7 @@ pub fn run() {
|
||||
app_config.init_local_store(db_path).await?;
|
||||
avatar_store.init(dir.join("avatars.db")).await?;
|
||||
material_store.init(dir.join("video_materials.db")).await?;
|
||||
asr_store.init(dir.join("asr_history.db")).await?;
|
||||
audio_store.init(dir.join("generated_audios.db")).await?;
|
||||
video_store.init(dir.join("generated_videos.db")).await?;
|
||||
publish_store.init(dir.join("publish_accounts.db")).await?;
|
||||
@@ -113,6 +118,11 @@ pub fn run() {
|
||||
commands::material::update_material_name,
|
||||
commands::material::delete_material,
|
||||
commands::material::import_material_files,
|
||||
commands::audio::update_generated_audio_name,
|
||||
commands::sound_asr::list_asr_history,
|
||||
commands::sound_asr::delete_asr_history,
|
||||
commands::sound_asr::update_asr_history_name,
|
||||
commands::sound_asr::transcribe_media_file,
|
||||
commands::audio::list_generated_audios,
|
||||
commands::audio::insert_generated_audio,
|
||||
commands::audio::delete_generated_audio,
|
||||
|
||||
Reference in New Issue
Block a user