Files
live-caption/apps/gui/main.cpp
T
blumia 736ec14d5b refactor: split core/ui/apps targets and add unit tests
Move all business logic into liblivecaption_core (no Qt dependency) and
the widgets into liblivecaption_ui, leaving two executables: the caption
window and the CLI, which no longer links Qt Widgets. Abstract the ASR
backend behind IAsr so the streaming policy and the live pipeline can be
tested with a scripted fake backend instead of a real model.

把全部业务逻辑下沉到不依赖 Qt 的 liblivecaption_core,界面独立成
liblivecaption_ui,拆出悬浮窗与命令行两个可执行程序(CLI 不再链接 Qt
Widgets);并用 IAsr 抽象推理后端,使流式策略与实时管线可脱离真模型
用假实现做单元测试。测试还抓到一个真 bug:段恰好结束在 chunk 边界时
只提交了稳定部分,最后几个词被丢掉,已修。
2026-10-12 13:43:50 +08:00

103 lines
3.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 图形界面版:悬浮字幕窗。
#include <cstdio>
#include <QApplication>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QGuiApplication>
#include <QTimer>
#include "appcli.h"
#include "captionwindow.h"
#include "livetranscriber.h"
#include "qwen3asr.h"
#include "streamingasr.h"
#include "transcript.h"
namespace {
int runGui(lc::Qwen3Asr & asr, const lc::StreamOptions & sop,
const QString & screenshotPath, int screenshotDelaySec, int opacityPercent)
{
lc::CaptionWindow window;
window.setPanelOpacity(opacityPercent);
lc::LiveTranscriber live(asr);
QObject::connect(&live, &lc::LiveTranscriber::caption, &window,
[&window](const QString & committed, const QString & tail,
const QString & lang) {
window.setLanguage(
QString::fromStdString(lc::languageDisplayName(lang.toStdString())));
window.setCaption(committed, tail);
});
QObject::connect(&live, &lc::LiveTranscriber::failed, &window, [](const QString & m) {
qWarning("采集失败: %s", qUtf8Printable(m));
});
QObject::connect(&window, &lc::CaptionWindow::closed, qApp, &QCoreApplication::quit);
QObject::connect(&window, &lc::CaptionWindow::languageClicked, &window, [] {
qInfo() << "语言标签被点击(设置界面将在 Phase 4 接入)";
});
QString err;
if (!live.start(sop, &err)) {
qCritical("启动采集失败: %s", qUtf8Printable(err));
return 1;
}
qInfo("正在采集: %s", qUtf8Printable(live.deviceName()));
window.show();
if (!screenshotPath.isEmpty()) {
// 自截图:用于验证渲染(不依赖外部截图工具)
QTimer::singleShot(screenshotDelaySec * 1000, &window, [&window, screenshotPath] {
const bool ok1 = window.grab().save(screenshotPath);
window.setCollapsed(false);
QString expandedPath = screenshotPath;
expandedPath.replace(QStringLiteral(".png"), QStringLiteral("-expanded.png"));
const bool ok2 = window.grab().save(expandedPath);
std::fprintf(stderr, "截图: 折叠态 %s / 展开态 %s\n",
ok1 ? "成功" : "失败", ok2 ? "成功" : "失败");
QCoreApplication::quit();
});
}
return QCoreApplication::exec();
}
} // namespace
int main(int argc, char ** argv)
{
// 说明意图:DPI 取整策略必须在 QGuiApplication 之前定。
// (Deepin 的会话环境已经设了 QT_SCALE_FACTOR_ROUNDING_POLICY,
// 所以本机仍会看到 Qt 的那条提醒,那不是本程序的问题。)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
QApplication app(argc, argv);
QCoreApplication::setApplicationName(QStringLiteral("live-caption"));
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("实时字幕(悬浮窗)"));
parser.addHelpOption();
lc::app::addModelOptions(parser);
lc::app::addStreamOptions(parser);
parser.addOption(QCommandLineOption("opacity", "字幕背景不透明度(20-100,百分比)", "n", "84"));
parser.addOption(QCommandLineOption("screenshot", "启动后自动截图并退出(验证渲染用)", "path"));
parser.addOption(QCommandLineOption("screenshot-delay", "截图前等待秒数", "sec", "6"));
parser.process(app);
const lc::AsrOptions asrOpt = lc::app::asrOptions(parser);
const lc::StreamOptions sop = lc::app::streamOptions(parser);
lc::Qwen3Asr asr;
if (!lc::app::loadModel(asr, asrOpt)) return 1;
return runGui(asr, sop,
parser.value("screenshot"),
parser.value("screenshot-delay").toInt(),
parser.value("opacity").toInt());
}