242 lines
7.5 KiB
Rust
242 lines
7.5 KiB
Rust
mod audio;
|
|
mod models;
|
|
mod state;
|
|
mod subtitle;
|
|
mod task;
|
|
mod translate;
|
|
mod vad;
|
|
mod whisper;
|
|
|
|
use models::{
|
|
DefaultModelPaths, StartTaskPayload, SubtitleSegment, SubtitleTask, TranslationConfig,
|
|
};
|
|
#[cfg(target_os = "macos")]
|
|
use objc2_app_kit::NSWindow;
|
|
#[cfg(target_os = "macos")]
|
|
use objc2_foundation::NSSize;
|
|
use state::AppState;
|
|
#[cfg(target_os = "macos")]
|
|
use tauri::{
|
|
menu::{MenuBuilder, MenuItemBuilder, PredefinedMenuItem, SubmenuBuilder},
|
|
Emitter,
|
|
};
|
|
use tauri::{AppHandle, Manager, PhysicalSize, Size};
|
|
|
|
#[cfg(target_os = "macos")]
|
|
const WINDOW_RATIO_WIDTH: f64 = 16.0;
|
|
#[cfg(target_os = "macos")]
|
|
const WINDOW_RATIO_HEIGHT: f64 = 10.0;
|
|
const DEFAULT_WINDOW_WIDTH: u32 = 1440;
|
|
const DEFAULT_WINDOW_HEIGHT: u32 = 900;
|
|
const MIN_WINDOW_WIDTH: u32 = 1280;
|
|
const MIN_WINDOW_HEIGHT: u32 = 800;
|
|
|
|
#[tauri::command]
|
|
async fn start_subtitle_task(
|
|
app: tauri::AppHandle,
|
|
window: tauri::Window,
|
|
state: tauri::State<'_, AppState>,
|
|
payload: StartTaskPayload,
|
|
) -> std::result::Result<SubtitleTask, String> {
|
|
task::start_task(app, window, state, payload)
|
|
.await
|
|
.map_err(error_to_string)
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn list_tasks(state: tauri::State<'_, AppState>) -> std::result::Result<Vec<SubtitleTask>, String> {
|
|
task::list_tasks(state).map_err(error_to_string)
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn update_segment_text(
|
|
state: tauri::State<'_, AppState>,
|
|
segment: SubtitleSegment,
|
|
) -> std::result::Result<SubtitleTask, String> {
|
|
task::update_segment_text(state, segment).map_err(error_to_string)
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn delete_task(
|
|
state: tauri::State<'_, AppState>,
|
|
task_id: String,
|
|
) -> std::result::Result<(), String> {
|
|
task::delete_task(state, task_id).map_err(error_to_string)
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn export_subtitles(
|
|
state: tauri::State<'_, AppState>,
|
|
task_id: String,
|
|
format: String,
|
|
output_path: String,
|
|
) -> std::result::Result<String, String> {
|
|
task::export_task(state, task_id, format, output_path).map_err(error_to_string)
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn get_default_model_paths(
|
|
app: tauri::AppHandle,
|
|
) -> std::result::Result<DefaultModelPaths, String> {
|
|
task::get_default_model_paths(&app).map_err(error_to_string)
|
|
}
|
|
|
|
#[tauri::command]
|
|
async fn retry_translation(
|
|
app: tauri::AppHandle,
|
|
window: tauri::Window,
|
|
state: tauri::State<'_, AppState>,
|
|
task_id: String,
|
|
translation_config: TranslationConfig,
|
|
) -> std::result::Result<SubtitleTask, String> {
|
|
task::retry_translation(app, window, state, task_id, translation_config)
|
|
.await
|
|
.map_err(error_to_string)
|
|
}
|
|
|
|
fn error_to_string(error: anyhow::Error) -> String {
|
|
format!("{error:#}")
|
|
}
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
tauri::Builder::default()
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.manage(AppState::default())
|
|
.setup(|app| {
|
|
configure_window(app.handle())?;
|
|
#[cfg(target_os = "macos")]
|
|
configure_macos_menu(app.handle())?;
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
start_subtitle_task,
|
|
list_tasks,
|
|
update_segment_text,
|
|
delete_task,
|
|
export_subtitles,
|
|
get_default_model_paths,
|
|
retry_translation
|
|
])
|
|
.run(tauri::generate_context!())
|
|
.expect("error while running tauri application");
|
|
}
|
|
|
|
fn configure_window(app: &AppHandle) -> tauri::Result<()> {
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
window.set_min_size(Some(Size::Physical(PhysicalSize::new(
|
|
MIN_WINDOW_WIDTH,
|
|
MIN_WINDOW_HEIGHT,
|
|
))))?;
|
|
window.set_size(Size::Physical(PhysicalSize::new(
|
|
DEFAULT_WINDOW_WIDTH,
|
|
DEFAULT_WINDOW_HEIGHT,
|
|
)))?;
|
|
#[cfg(target_os = "macos")]
|
|
apply_macos_aspect_ratio(&window)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn configure_macos_menu(app: &AppHandle) -> tauri::Result<()> {
|
|
let app_name = app.package_info().name.clone();
|
|
|
|
let app_menu = SubmenuBuilder::new(app, app_name)
|
|
.item(&PredefinedMenuItem::about(app, None, None)?)
|
|
.separator()
|
|
.item(&PredefinedMenuItem::services(app, None)?)
|
|
.separator()
|
|
.item(&PredefinedMenuItem::hide(app, None)?)
|
|
.item(&PredefinedMenuItem::hide_others(app, None)?)
|
|
.item(&PredefinedMenuItem::show_all(app, None)?)
|
|
.separator()
|
|
.item(&PredefinedMenuItem::quit(app, None)?)
|
|
.build()?;
|
|
|
|
let file_menu = SubmenuBuilder::new(app, "文件")
|
|
.item(
|
|
&MenuItemBuilder::with_id("pick_files", "选择媒体文件")
|
|
.accelerator("CmdOrCtrl+O")
|
|
.build(app)?,
|
|
)
|
|
.separator()
|
|
.item(&MenuItemBuilder::with_id("export_srt", "导出 SRT").build(app)?)
|
|
.item(&MenuItemBuilder::with_id("export_vtt", "导出 VTT").build(app)?)
|
|
.item(&MenuItemBuilder::with_id("export_ass", "导出 ASS").build(app)?)
|
|
.build()?;
|
|
|
|
let edit_menu = SubmenuBuilder::new(app, "编辑")
|
|
.item(&PredefinedMenuItem::undo(app, None)?)
|
|
.item(&PredefinedMenuItem::redo(app, None)?)
|
|
.separator()
|
|
.item(&PredefinedMenuItem::cut(app, None)?)
|
|
.item(&PredefinedMenuItem::copy(app, None)?)
|
|
.item(&PredefinedMenuItem::paste(app, None)?)
|
|
.item(&PredefinedMenuItem::select_all(app, None)?)
|
|
.build()?;
|
|
|
|
let settings_menu = SubmenuBuilder::new(app, "设置")
|
|
.item(&MenuItemBuilder::with_id("toggle_advanced", "显示或隐藏高级设置").build(app)?)
|
|
.item(&MenuItemBuilder::with_id("toggle_bilingual", "切换双语导出").build(app)?)
|
|
.item(&MenuItemBuilder::with_id("reset_models", "恢复默认模型路径").build(app)?)
|
|
.build()?;
|
|
|
|
let language_menu = SubmenuBuilder::new(app, "语言")
|
|
.item(&MenuItemBuilder::with_id("set_lang_zh_cn", "中文").build(app)?)
|
|
.item(&MenuItemBuilder::with_id("set_lang_en", "English").build(app)?)
|
|
.build()?;
|
|
|
|
let window_menu = SubmenuBuilder::new(app, "窗口")
|
|
.item(&PredefinedMenuItem::minimize(app, None)?)
|
|
.item(&PredefinedMenuItem::maximize(app, None)?)
|
|
.separator()
|
|
.item(&PredefinedMenuItem::close_window(app, None)?)
|
|
.build()?;
|
|
|
|
let menu = MenuBuilder::new(app)
|
|
.item(&app_menu)
|
|
.item(&file_menu)
|
|
.item(&edit_menu)
|
|
.item(&settings_menu)
|
|
.item(&language_menu)
|
|
.item(&window_menu)
|
|
.build()?;
|
|
|
|
app.set_menu(menu)?;
|
|
app.on_menu_event(|app, event| {
|
|
let action = match event.id().0.as_str() {
|
|
"pick_files" => Some("pick-files"),
|
|
"export_srt" => Some("export-srt"),
|
|
"export_vtt" => Some("export-vtt"),
|
|
"export_ass" => Some("export-ass"),
|
|
"toggle_advanced" => Some("toggle-advanced"),
|
|
"toggle_bilingual" => Some("toggle-bilingual"),
|
|
"reset_models" => Some("reset-models"),
|
|
"set_lang_zh_cn" => Some("set-lang-zh-CN"),
|
|
"set_lang_en" => Some("set-lang-en"),
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(action) = action {
|
|
let _ = app.emit("menu:action", action);
|
|
}
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
fn apply_macos_aspect_ratio(window: &tauri::WebviewWindow) -> tauri::Result<()> {
|
|
let ns_window = window.ns_window()?;
|
|
let ns_window = ns_window.cast::<NSWindow>();
|
|
let aspect_ratio = NSSize::new(WINDOW_RATIO_WIDTH, WINDOW_RATIO_HEIGHT);
|
|
|
|
unsafe {
|
|
let ns_window = &*ns_window;
|
|
ns_window.setContentAspectRatio(aspect_ratio);
|
|
}
|
|
|
|
Ok(())
|
|
}
|