skillok meeting
778ade180d60c9f7692423132486bd257954c517
Перейти к файлу
Код
meeting src-taurisrclib.rs
anc Разбор по голосам: «Спикер 1», «Спикер 2»… на компьютере, без сети - crates/voices: отпечаток голоса реплики (WeSpeaker ResNet34 через sherpa-onnx, статические сборки под Windows и macOS) и разбиение снизу вверх со средней связью. Из отпечатков вычитается средний по записи: без этого у подкаста любые две реплики были похожи на 0,5–0,8. - После распознавания реплики «Собеседников» и файла раскладываются по голосам; микрофон — «Я». Отпечатки и реплики — в voices.json рядом с записью: «Говорящих: N» пересобирает разбиение без новой расшифровки. - Ход работы: этап «Разделяю по голосам» с процентом; по ходу распознавания у реплик разбираемых дорожек ещё нет имени. - Исправлено: файл без смены говорящего ложился одним абзацем 00:00:00 (подкаст 1 ч 52 мин — 170 КБ одной строкой). Абзац теперь — реплики одного говорящего без пауз больше 1,5 с и не длиннее ~600 знаков. У реплики появился конец (из ответа Whisper или до следующей). - Модель (26 МБ) не в git: scripts/fetch-models.sh с проверкой SHA-256, в сборку — ресурсом. Windows — со статической средой C (+crt-static, явная libucrt): установщику не нужен VC++ Redistributable; crate-type ядра — только rlib (cdylib нужен лишь мобильным платформам). - Проверено на подкасте Kuji 230 (1 ч 52 мин, трое в студии): трое, 48 % / 36 % / 15 % речи, отпечатки 1184 реплик за ~3 мин. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> d0d9d7d 30 сентября 2026 г.
lib.rs
//! Skillok Meet — ядро приложения. Интерфейс зовёт команды отсюда через
//! `invoke`; всё, что трогает диск, сеть, звук и ключи, живёт в Rust.
//! Программа ничего не делает сама — только по действию человека.

pub mod ai;
pub mod meetings;
pub mod notes;
pub mod openrouter;
pub mod recorder;
pub mod settings;
pub mod speakers;
pub mod transcribe;
pub mod transcript;

mod commands;

use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};

use tauri::Manager;

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .setup(|app| {
            let config_dir = app.path().app_config_dir()?;
            let data_dir = app.path().app_data_dir()?;
            let settings_path = settings::settings_path(&config_dir);
            let default_meetings_dir = data_dir.join("meetings");
            // Модель голосов лежит в ресурсах программы (tauri.conf.json,
            // bundle.resources); в `tauri dev` — там же, куда их копирует сборка.
            let voices_model = app
                .path()
                .resource_dir()
                .map(|d| d.join("models").join("voices.onnx"))
                .unwrap_or_default();
            let chosen = settings::Settings::load(&settings_path)
                .map(|s| s.storage_dir)
                .unwrap_or_default();
            let root = if chosen.trim().is_empty() {
                default_meetings_dir.clone()
            } else {
                PathBuf::from(chosen)
            };
            let store = Arc::new(meetings::Store::new(root));
            // Запись, оборванная выключением компьютера, перестаёт считаться идущей.
            let _ = store.recover_interrupted();
            app.manage(commands::AppState {
                settings_path,
                store: RwLock::new(store),
                default_meetings_dir,
                secrets: Arc::new(settings::Keyring),
                recording: Mutex::new(None),
                jobs: Mutex::new(HashMap::new()),
                voices_model,
            });
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            commands::get_settings,
            commands::save_settings,
            commands::set_openrouter_key,
            commands::check_openrouter_key,
            commands::delete_openrouter_key,
            commands::list_meetings,
            commands::get_meeting,
            commands::save_note,
            commands::remove_section,
            commands::cancel_job,
            commands::set_storage_dir,
            commands::open_folder,
            commands::rename_meeting,
            commands::delete_meeting,
            commands::add_audio,
            commands::transcribe_meeting,
            commands::regroup_speakers,
            commands::run_command,
            commands::recorder_status,
            commands::recorder_start,
            commands::recorder_pause,
            commands::recorder_stop,
        ])
        .run(tauri::generate_context!())
        .expect("Skillok Meet не запустился");
}