diff --git a/apps/gui/main.cpp b/apps/gui/main.cpp index 90f0f7f..2c906df 100644 --- a/apps/gui/main.cpp +++ b/apps/gui/main.cpp @@ -50,12 +50,13 @@ QIcon makeTrayIcon() return QIcon(pm); } -// 把设置应用到字幕窗(字号/不透明度/行数/位置) +// 把设置应用到字幕窗(字号/不透明度/行数/位置/灰色尾部) void applyAppearance(lc::CaptionWindow & window, const lc::AppSettings & cfg) { window.setPanelOpacity(cfg.opacityPercent); window.setTextFontSize(cfg.fontSize); window.setLineCounts(cfg.collapsedLines, cfg.expandedLines); + window.setShowTail(cfg.showUnstableTail); if (cfg.hasWindowPos) window.move(cfg.windowPos); } @@ -193,7 +194,8 @@ int main(int argc, char ** argv) applyAppearance(window, cfg); // 语言 pill:流式模型不提供语言识别,就显示设置里选的语言 if (!cfg.language.isEmpty()) { - window.setLanguage(QString::fromStdString(lc::languageDisplayName(cfg.language.toStdString()))); + window.setLanguage( + QString::fromStdString(lc::languageDisplayNameFromCode(cfg.language.toStdString()))); } // 注意声明顺序:asr 必须在 live 之前声明, @@ -259,24 +261,31 @@ int main(int argc, char ** argv) QString activeKey = backendKeyOf(cfg); const auto openSettings = [&] { + // 后端相关项变化时:先把语言标签跟设置对齐(流式模型没有自动检测结果来更新它), + // 再重启识别后端。 + const auto applyBackend = [&]() -> bool { + if (backendKeyOf(cfg) == activeKey) return true; + window.setLanguage(cfg.language.isEmpty() + ? QString() + : QString::fromStdString( + lc::languageDisplayNameFromCode(cfg.language.toStdString()))); + if (!startTranscriber()) return false; + activeKey = backendKeyOf(cfg); + return true; + }; + lc::SettingsDialog dlg(false); QObject::connect(&dlg, &lc::SettingsDialog::settingsApplied, &window, [&] { lc::models::ModelStore::setRootOverride(cfg.modelDir); applyAppearance(window, cfg); // 透明度/字号等立刻可见 - if (backendKeyOf(cfg) != activeKey) { - if (startTranscriber()) activeKey = backendKeyOf(cfg); - } + applyBackend(); }); if (dlg.exec() == QDialog::Accepted) { lc::models::ModelStore::setRootOverride(cfg.modelDir); applyAppearance(window, cfg); - if (backendKeyOf(cfg) != activeKey) { - if (!startTranscriber()) { - QMessageBox::warning(&window, QObject::tr("启动失败"), - QObject::tr("模型加载失败,请检查设置。")); - } else { - activeKey = backendKeyOf(cfg); - } + if (!applyBackend()) { + QMessageBox::warning(&window, QObject::tr("启动失败"), + QObject::tr("模型加载失败,请检查设置。")); } } }; diff --git a/docs/gui.md b/docs/gui.md index 88c605f..19d55e7 100644 --- a/docs/gui.md +++ b/docs/gui.md @@ -65,8 +65,9 @@ 也可以指定某个具体输出设备(如 HDMI);列不出来或没列全时见 [troubleshooting.md](troubleshooting.md)。 - **当前使用**:选择实际生效的模型(只列出**已安装**的)。 -- **识别语言**:语言提示,多语言模型用它(例如 Nemotron 需要指定语言才不出错); - 「自动检测」= 留空。目前提供 自动/中文/英文/日语。 +- **识别语言**:语言提示,多语言模型用它(例如 Nemotron 必须指定语言,否则「自动」可能识别成别的语言); + 「自动检测」= 留空。**候选项按所选模型支持的语言生成** —— 中文模型只给中文,Nemotron 给它的 16 种 + 语言,Qwen3-ASR 给常用语言。改完之后左上角的语言标签会同步更新。 - **下载源**:默认 `https://hf-mirror.com`(网络可达性最好的镜像);可改成其它镜像或官方源。 - **代理**:不使用 / 跟随系统 / 手动设置(主机、端口、用户名、密码)。 diff --git a/docs/testing.md b/docs/testing.md index a5a61df..12dc905 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -53,6 +53,7 @@ ctest --test-dir build --output-on-failure # 也可以单独跑 build/tests/t | `tst_livepipeline.silenceEndsSegment` | 静音收束分段 | | `tst_vad.quietSpeechIsDetected` | 噪声底用均值跟踪,把很轻的话音全判成静音 | | `tst_transcript.*` | 输出解析与语言名映射 | +| `tst_transcript.isoCodeDisplayName` / `registryLanguagesAllHaveDisplayNames` | 设置里的语言提示用的是 ISO 码(`zh`/`uk`…),漏了显示名的话「识别语言」下拉会直接露出 `uk` 这种码 | 写测试的过程中还**抓到一个真 bug**:段恰好结束在 chunk 边界(没有待处理音频)时,`flush()` 只提交"已稳定"部分,最后几个词会被丢掉。已修(用完整文本 `lastRawFull_` 提交)。 diff --git a/src/core/transcript.cpp b/src/core/transcript.cpp index c6f6554..5b417ae 100644 --- a/src/core/transcript.cpp +++ b/src/core/transcript.cpp @@ -76,4 +76,38 @@ std::string languageDisplayName(const std::string & lang) return lang; } +// ISO 639-1 码(注册表里用的形式,如 zh / en / ja)→ 显示名。 +// ASR 模型自己吐的是完整语言名(走上面的 languageDisplayName), +// 但设置里的语言提示是 ISO 码,界面要显示成中文。 +std::string languageDisplayNameFromCode(const std::string & code) +{ + if (code.empty()) return {}; + + static const struct { const char * key; const char * display; } kMap[] = { + { "zh", "中文" }, + { "en", "英语" }, + { "ja", "日语" }, + { "ko", "韩语" }, + { "fr", "法语" }, + { "de", "德语" }, + { "es", "西班牙语" }, + { "ru", "俄语" }, + { "it", "意大利语" }, + { "pt", "葡萄牙语" }, + { "ar", "阿拉伯语" }, + { "hi", "印地语" }, + { "vi", "越南语" }, + { "nl", "荷兰语" }, + { "tr", "土耳其语" }, + { "uk", "乌克兰语" }, + { "yue", "粤语" }, + { "auto", "自动检测" }, + }; + + for (const auto & e : kMap) { + if (code == e.key) return e.display; + } + return code; +} + } // namespace lc diff --git a/src/core/transcript.h b/src/core/transcript.h index b99435f..7ce01e2 100644 --- a/src/core/transcript.h +++ b/src/core/transcript.h @@ -17,4 +17,7 @@ ParsedTranscript parseAsrOutput(const std::string & raw); // "English" -> "英语","Chinese" -> "中文";未知则原样返回 std::string languageDisplayName(const std::string & lang); +// ISO 码(注册表里用的形式)-> 显示名:"zh" -> "中文"、"ja" -> "日语";未知则原样返回 +std::string languageDisplayNameFromCode(const std::string & code); + } // namespace lc diff --git a/src/ui/appsettings.cpp b/src/ui/appsettings.cpp index 04b8fde..af6bfcd 100644 --- a/src/ui/appsettings.cpp +++ b/src/ui/appsettings.cpp @@ -16,7 +16,6 @@ void AppSettings::load() s.beginGroup(QStringLiteral("app")); modelId = s.value(QStringLiteral("modelId")).toString(); language = s.value(QStringLiteral("language")).toString(); - autoPickByLanguage = s.value(QStringLiteral("autoPickByLanguage"), false).toBool(); opacityPercent = s.value(QStringLiteral("opacity"), 84).toInt(); fontSize = s.value(QStringLiteral("fontSize"), 15).toInt(); @@ -60,7 +59,6 @@ void AppSettings::save() s.beginGroup(QStringLiteral("app")); s.setValue(QStringLiteral("modelId"), modelId); s.setValue(QStringLiteral("language"), language); - s.setValue(QStringLiteral("autoPickByLanguage"), autoPickByLanguage); s.setValue(QStringLiteral("opacity"), opacityPercent); s.setValue(QStringLiteral("fontSize"), fontSize); diff --git a/src/ui/appsettings.h b/src/ui/appsettings.h index 585a639..7c3b714 100644 --- a/src/ui/appsettings.h +++ b/src/ui/appsettings.h @@ -14,7 +14,6 @@ public: // 模型 QString modelId; // 当前使用的模型注册表 id QString language; // 识别语言提示;"" = 自动 - bool autoPickByLanguage = false; // 显示 int opacityPercent = 84; diff --git a/src/ui/captionwindow.cpp b/src/ui/captionwindow.cpp index 135e73a..f82fbae 100644 --- a/src/ui/captionwindow.cpp +++ b/src/ui/captionwindow.cpp @@ -171,8 +171,7 @@ void CaptionWindow::setShowTail(bool on) { if (showTail_ == on) return; showTail_ = on; - setCaption(QString(), QString()); // 强制重画需要外部再喂一次;这里先清空缓存 - committed_.clear(); + renderCaption(); // 用缓存的文本立刻重画(不能走 setCaption:参数没变会被早退) } void CaptionWindow::setPanelOpacity(int percent) @@ -235,8 +234,12 @@ void CaptionWindow::setCaption(const QString & committed, const QString & tail) if (committed == committed_ && tail == tail_) return; committed_ = committed; tail_ = tail; + renderCaption(); +} - // 已稳定部分正常色,不稳定尾部暗色 —— 和 CLI 版渲染一致 +// 已稳定部分正常色,不稳定尾部暗色 —— 和 CLI 版渲染一致 +void CaptionWindow::renderCaption() +{ const QString html = showTail_ ? QStringLiteral("