chore(pack): add CPack configuration to build a deb package

Add install rules and CPack settings so cpack produces an installable
.deb. Dependencies are derived with dpkg-shlibdeps, so they follow the
binaries. The sherpa-onnx runtime the streaming backend needs is bundled
because no distro package provides it. The fetch-models script now also
locates its registry when installed system-wide.

增加 CPack 配置,可直接用 cpack 打出 deb:安装规则含桌面入口、应用图标、
模型注册表与下载脚本、用户文档;依赖由 dpkg-shlibdeps 自动推导;流式后端
需要的 sherpa-onnx 运行时(发行版无对应包)随包携带;fetch-models 脚本
装到系统后也能找到注册表。
This commit is contained in:
blumia committed 2026-10-13 11:53:07 +08:00
1 parent 898763eb74
commit 8b9a8c420e
9 files changed
+401 -5

No files matched your search

+24 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.24)
project(live-caption LANGUAGES CXX)
project(live-caption VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -153,8 +153,31 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
endforeach()
endif()
# ---------------------------------------------------------------------------
# 安装后的 RPATH
#
# 1) $ORIGIN/../<私有库目录>:打包时随包携带的 sherpa-onnx 运行时(lib/ 下的相对路径);
# 2) llama / mtmd:发行版把这两个库放在 .../llama 子目录,而该目录不在 ld 的默认搜索
# 路径里(ldconfig -p 里看不到),所以必须显式写进 RPATH,否则安装后起不来。
# ---------------------------------------------------------------------------
set(LC_PRIVATE_LIBDIR "lib/live-caption")
set(_lc_install_rpath "\$ORIGIN/../${LC_PRIVATE_LIBDIR}")
get_filename_component(_lc_llama_dir "${LC_MTMD_LIB}" DIRECTORY)
if(IS_DIRECTORY "${_lc_llama_dir}")
list(APPEND _lc_install_rpath "${_lc_llama_dir}")
endif()
set_target_properties(live-caption live-caption-cli PROPERTIES
INSTALL_RPATH "${_lc_install_rpath}"
INSTALL_RPATH_USE_LINK_PATH OFF)
message(STATUS "安装后 RPATH: ${_lc_install_rpath}")
# ---------------------------------------------------------------------------
if(LC_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# ---------------------------------------------------------------------------
# 安装与打包(CPack → .deb)
# ---------------------------------------------------------------------------
include(cmake/Packaging.cmake)
+17 -1
View File
@@ -108,13 +108,28 @@ apps/gui/ → live-caption (悬浮窗 + 托盘 + 设置)
apps/cli/ → live-caption-cli (命令行程序,只链 Qt6::Core)
apps/common/ 两个程序共用的命令行选项与后端工厂
data/models.json 模型注册表(编译进二进制,加模型=加一条数据)
data/live-caption.desktop / .svg 桌面入口与应用图标
data/deb/ deb 的 postinst / postrm(刷新应用菜单与图标缓存)
cmake/Packaging.cmake 安装规则与 CPack 配置(打 deb)
tests/ 单元测试(QtTest,只链 core)
scripts/fetch-models.sh 命令行下载模型
scripts/fetch-models.sh 命令行下载模型(装到系统后叫 live-caption-fetch-models)
experiments/ Phase 0/1 的验证脚本
```
拆分的意义:**业务逻辑全在 core 且不依赖 Qt**,所以流式策略、VAD、输出解析这些最容易出错的部分可以脱离真模型和显示环境做单元测试;模型管理单独一层,GUI 与 CLI 各自独立演进,CLI 也不再需要链接 Qt Widgets。
## 打包成 deb
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
(cd build && cpack) # → build/live-caption_<版本>-<修订>_<架构>.deb
sudo apt install ./build/live-caption_*.deb
```
依赖由 `dpkg-shlibdeps` 从二进制自动推导;流式 ONNX 后端需要的 sherpa-onnx 运行时
(发行版没有对应包)默认随包携带。细节与可调选项见 [docs/packaging.md](docs/packaging.md)。
## 文档
| 文档 | 内容 |
@@ -122,6 +137,7 @@ experiments/ Phase 0/1 的验证脚本
| [docs/gui.md](docs/gui.md) | 图形界面:首次配置、设置项说明、托盘、下载与校验 |
| [docs/cli.md](docs/cli.md) | 全部命令行选项(含仅测试用的那些) |
| [docs/asr-backends.md](docs/asr-backends.md) | 双后端结构、模型注册表、实测对比、语言推荐 |
| [docs/packaging.md](docs/packaging.md) | 用 CPack 打 deb:包内容、依赖、RPATH、可调选项 |
| [docs/troubleshooting.md](docs/troubleshooting.md) | 常见问题(窗口不见了、模型没下全、Wayland 置顶…) |
| [docs/testing.md](docs/testing.md) | 测试分层、回归用例、GUI 交互测试钩子 |
| [docs/phase0-verification.md](docs/phase0-verification.md) | Phase 0 可行性验证报告 |
+118
View File
@@ -0,0 +1,118 @@
# ---------------------------------------------------------------------------
# 安装规则与打包(CPack → .deb)
#
# cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
# cmake --build build -j
# cpack --config build/CPackConfig.cmake # 产 deb
# cpack --config build/CPackConfig.cmake -G DEB # 只产 deb 更明确
#
# 产物:build/live-caption_<版本>-<修订>_<架构>.deb
# dpkg -c <deb> # 看文件清单
# dpkg -I <deb> # 看控制信息 / 依赖
# sudo apt install ./<deb>
#
# 说明与取舍见 docs/packaging.md。
# ---------------------------------------------------------------------------
include(GNUInstallDirs)
option(LC_INSTALL "生成安装与打包规则" ON)
option(LC_BUNDLE_SHERPA "把 sherpa-onnx 运行时打进包里(流式后端依赖它,而发行版没有对应包)" ON)
option(LC_PACKAGE_STRIP "打包时 strip 二进制(发布建议打开;要用 coredump 调试符号就关掉)" OFF)
if(NOT LC_INSTALL)
return()
endif()
# 包私有库目录。Debian 允许包把自己的私有共享库放在 /usr/lib/<包名>/,
# 这样不必占用 multiarch 目录,也避免和系统库撞名。
# 顶层 CMakeLists.txt 里已经定义(RPATH 要用),这里只是兜底。
if(NOT DEFINED LC_PRIVATE_LIBDIR)
set(LC_PRIVATE_LIBDIR "lib/live-caption")
endif()
# ---------------------------------------------------------------------------
# 安装规则
# ---------------------------------------------------------------------------
install(TARGETS live-caption live-caption-cli
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# 桌面入口与图标(GUI 才会出现在应用菜单里;CLI 无入口)
install(FILES data/live-caption.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES data/live-caption.svg
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
# 模型注册表与下载脚本。注意:程序自己也把注册表编进了二进制(qt_add_resources),
# 这里装一份是为了让脚本在没有程序的情况下也能用。
install(FILES data/models.json
DESTINATION ${CMAKE_INSTALL_DATADIR}/live-caption)
install(PROGRAMS scripts/fetch-models.sh
DESTINATION ${CMAKE_INSTALL_BINDIR}
RENAME live-caption-fetch-models)
# sherpa-onnx 运行时:官方只发预编译 tar 包,没有系统包可依赖,
# 所以默认随包携带(约 32MB)。关掉的话流式 ONNX 后端在安装后的机器上会起不来。
if(LC_HAS_SHERPA AND LC_BUNDLE_SHERPA)
install(FILES ${LC_SHERPA_C_API} ${LC_SHERPA_ORT}
DESTINATION ${LC_PRIVATE_LIBDIR})
message(STATUS "打包: sherpa-onnx 运行时会随包安装到 /usr/${LC_PRIVATE_LIBDIR}")
endif()
# 文档
install(FILES README.md DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES docs/gui.md docs/cli.md docs/asr-backends.md docs/troubleshooting.md
DESTINATION ${CMAKE_INSTALL_DOCDIR}/docs)
# ---------------------------------------------------------------------------
# CPack
# ---------------------------------------------------------------------------
set(CPACK_GENERATOR "DEB")
set(CPACK_PACKAGE_NAME "live-caption")
set(CPACK_PACKAGE_VENDOR "live-caption")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
"系统播放音频的实时字幕(类似 Chrome 实时字幕)")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "live-caption")
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
set(CPACK_STRIP_FILES ${LC_PACKAGE_STRIP})
# 维护者信息:发布前用 -DLC_DEB_MAINTAINER="名字 <邮箱>" 覆盖(Debian 要求可联系到人)
set(LC_DEB_MAINTAINER "live-caption maintainers <live-caption@example.com>"
CACHE STRING "Debian Maintainer 字段")
set(LC_DEB_HOMEPAGE "" CACHE STRING "Debian Homepage 字段(留空则不写)")
set(CPACK_PACKAGE_CONTACT "${LC_DEB_MAINTAINER}")
# Debian 控制字段
set(CPACK_DEBIAN_PACKAGE_NAME "live-caption")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${LC_DEB_MAINTAINER}")
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_RELEASE "1") # Debian 修订号
set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT") # live-caption_0.1.0-1_amd64.deb
if(NOT LC_DEB_HOMEPAGE STREQUAL "")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "${LC_DEB_HOMEPAGE}")
endif()
# 维护者脚本:刷新应用菜单与图标缓存(见 data/deb/)
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
"${CMAKE_CURRENT_SOURCE_DIR}/data/deb/postinst;${CMAKE_CURRENT_SOURCE_DIR}/data/deb/postrm")
# 依赖由 dpkg-shlibdeps 从二进制里推出来(libllama0 / libpulse0 / libsamplerate0 /
# Qt6 各库…),比手写列表可靠。包内自带的 sherpa 库(RPATH 指向 $ORIGIN/../lib/
# live-caption)会被识别为私有库,不会变成外部依赖。
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
# 需要手动补依赖时,用 -DLC_DEB_EXTRA_DEPENDS=... 追加(逗号分隔;给了就覆盖自动结果)
set(LC_DEB_EXTRA_DEPENDS "" CACHE STRING "额外写入 Depends 的包(逗号分隔)")
if(NOT LC_DEB_EXTRA_DEPENDS STREQUAL "")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${LC_DEB_EXTRA_DEPENDS}")
endif()
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION
"实时字幕:采集系统播放声音的输出(monitor),把语音实时转写成字幕并叠加显示。
.
两条推理后端:llama.cpp(Qwen3-ASR 的 GGUF 模型,准确率高、语言最全)与
sherpa-onnx(流式 ONNX 模型,原生流式、CPU 占用低)。模型不随包分发,
首次启动时在设置界面里按需下载(支持镜像与代理断点续传)。")
include(CPack)
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
# 安装后刷新桌面数据库与图标缓存,否则刚装完的应用菜单里可能看不到这个条目。
# (本包的共享库在 /usr/lib/live-caption/,靠二进制的 RPATH 查找,不走 ldconfig,
# 这里仍然调一下只是兜底。)
set -e
case "$1" in
configure)
ldconfig
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database -q /usr/share/applications || true
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true
fi
;;
esac
exit 0
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# 卸载后同样刷新缓存,避免菜单里留下一个指向已删除程序的条目。
set -e
case "$1" in
remove|purge)
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database -q /usr/share/applications || true
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true
fi
;;
esac
exit 0
+17
View File
@@ -0,0 +1,17 @@
[Desktop Entry]
Type=Application
Version=1.0
Name=Live Caption
Name[zh_CN]=实时字幕
GenericName=Real-time captions
GenericName[zh_CN]=实时字幕
Comment=Show live subtitles for the audio your system is playing
Comment[zh_CN]=把系统正在播放的声音实时转写成字幕
Exec=live-caption
TryExec=live-caption
Icon=live-caption
Terminal=false
Categories=AudioVideo;Audio;
Keywords=caption;subtitle;subtitles;speech;transcription;asr;
Keywords[zh_CN]=字幕;实时字幕;语音识别;听写;
StartupNotify=false
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
live-caption 图标。
刻意不用文字(字形)而画成「字幕面板」:SVG 里的 <text> 依赖渲染环境有对应字体,
而这条图标要跟着主题一起渲染。所以画成和悬浮窗一样的样子 ——
深色圆角面板 + 一行已稳定文本(白)+ 一行不稳定尾部(灰)。
-->
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<defs>
<clipPath id="panel">
<rect x="2" y="11" width="60" height="42" rx="10"/>
</clipPath>
</defs>
<!-- 面板 -->
<rect x="2" y="11" width="60" height="42" rx="10" fill="#1b1b1b"/>
<rect x="2" y="11" width="60" height="42" rx="10" fill="none"
stroke="#3a3a3a" stroke-width="1.5"/>
<!-- 已稳定文本 -->
<g clip-path="url(#panel)">
<rect x="11" y="24" width="42" height="6" rx="3" fill="#f5f5f5"/>
<!-- 不稳定尾部 -->
<rect x="11" y="35" width="26" height="6" rx="3" fill="#8d8d8d"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1016 B

+144
View File
@@ -0,0 +1,144 @@
# 打包(CPack → .deb)
用 CMake 的 CPack 生成 `.deb`,配置在 [`cmake/Packaging.cmake`](../cmake/Packaging.cmake)。
## 三条命令
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
(cd build && cpack) # 默认生成器就是 DEB,等价于 cpack -G DEB
```
产物:`build/live-caption_<版本>-<修订>_<架构>.deb`,例如 `live-caption_0.1.0-1_amd64.deb`(约 11MB)。
```bash
dpkg -c build/live-caption_0.1.0-1_amd64.deb # 看文件清单
dpkg -I build/live-caption_0.1.0-1_amd64.deb # 看控制信息与依赖
sudo apt install ./build/live-caption_0.1.0-1_amd64.deb
sudo apt remove live-caption # 卸载(模型文件与用户设置不会被删)
```
> 用 `apt install ./xxx.deb` 而不是 `dpkg -i`:apt 会把上面自动推导出来的依赖一起装好。
## 包内容
| 路径 | 说明 |
|---|---|
| `/usr/bin/live-caption` | 图形界面(悬浮窗 + 托盘 + 设置) |
| `/usr/bin/live-caption-cli` | 命令行版(单文件/流式文件/无窗口实时) |
| `/usr/bin/live-caption-fetch-models` | 命令行下载模型(`scripts/fetch-models.sh` 装的) |
| `/usr/lib/live-caption/` | 包私有共享库:`libsherpa-onnx-c-api.so`、`libonnxruntime.so` |
| `/usr/share/applications/live-caption.desktop` | 应用菜单入口 |
| `/usr/share/icons/hicolor/scalable/apps/live-caption.svg` | 图标 |
| `/usr/share/live-caption/models.json` | 模型注册表(给上面的下载脚本用;程序自己也内嵌了一份) |
| `/usr/share/doc/live-caption/` | README 与用户文档 |
**模型不随包分发**(注册表里有 4 个、合计约 2.4GB)。首次启动时在设置界面里按需下载,
也可以装完之后跑:
```bash
live-caption-fetch-models # 列出模型与安装状态
live-caption-fetch-models --all # 全部下载
```
## 依赖是怎么确定的
`CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON` —— 用 `dpkg-shlibdeps` 直接扫二进制,推导出:
```
Depends: libc6 (>= 2.34), libgcc-s1 (>= 4.0), libllama0 (>= 9951+dfsg), libpulse0 (>= 0.99.1),
libqt6core6 (>= 6.8.0), libqt6gui6 (>= 6.1.2), libqt6network6 (>= 6.1.2),
libqt6widgets6 (>= 6.3.0), libsamplerate0 (>= 0.1.7), libstdc++6 (>= 11)
```
这比手写列表可靠(跟着二进制走,不会漏)。`libllama0` 同时提供 `libllama`、`libmtmd`
和 `libllama-common`,所以一个依赖就够。
包内自带的 sherpa 库是**私有库**:`dpkg-shlibdeps` 通过二进制的 RPATH 找到它们、
识别为同包文件,因此不会产生外部依赖。
需要手动补依赖时:
```bash
cmake -S . -B build -DLC_DEB_EXTRA_DEPENDS="foo (>= 1.2), bar"
```
## 为什么打包带上 sherpa-onnx
流式 ONNX 后端(zipformer / X-ASR / Nemotron)依赖 `libsherpa-onnx-c-api.so` 与
`libonnxruntime.so`。官方只发预编译 tar 包,**发行版里没有对应包可依赖**,所以默认随包携带
(约 32MB 未压缩,deb 里压到约 10MB)。
关掉它(包变小,但安装后的机器上流式后端起不来):
```bash
cmake -S . -B build -DLC_BUNDLE_SHERPA=OFF # 包约 0.5MB,只剩 llama 后端可用
```
许可:sherpa-onnx 是 Apache-2.0,ONNX Runtime 是 MIT,都可以再分发;官方 tar 包里没带
LICENSE 文件,自己分发时建议一并附上。
## RPATH:两个必须写进二进制的路径
安装后的二进制带这样的 RUNPATH:
```
$ORIGIN/../lib/live-caption:/usr/lib/x86_64-linux-gnu/llama
```
两条都有原因:
1. `$ORIGIN/../lib/live-caption` —— 找包内自带的 sherpa 库。用 `$ORIGIN` 相对路径,
包装到哪个前缀都能用,也不需要 `ldconfig`。
2. `/usr/lib/x86_64-linux-gnu/llama` —— `libllama.so.0` / `libmtmd.so.0` 在发行版里放在
**子目录** `llama/` 里,而该目录不在 ld 的默认搜索路径(`ldconfig -p` 里看不到它们),
也没有 `/etc/ld.so.conf.d` 条目。所以只能靠 RPATH,否则装完直接起不来。
## 可调选项
| 选项 | 默认 | 说明 |
|---|---|---|
| `LC_INSTALL` | `ON` | 生成安装与打包规则 |
| `LC_BUNDLE_SHERPA` | `ON` | 把 sherpa-onnx 运行时打进包 |
| `LC_PACKAGE_STRIP` | `OFF` | 打包时 strip 二进制。发布建议打开;**要用 coredump 调符号就保持关闭** |
| `LC_DEB_MAINTAINER` | 占位值 | Debian `Maintainer` 字段,发布前改成真人 |
| `LC_DEB_HOMEPAGE` | 空 | Debian `Homepage` 字段;留空则不写这一行 |
| `LC_DEB_EXTRA_DEPENDS` | 空 | 覆盖/追加 `Depends` |
| `LC_BUILD_TESTS` | `ON` | 打包时用 `-DLC_BUILD_TESTS=OFF` 可以少编测试 |
例:
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DLC_BUILD_TESTS=OFF -DLC_PACKAGE_STRIP=ON \
-DLC_DEB_MAINTAINER="你的名字 <you@example.com>" \
-DLC_DEB_HOMEPAGE="https://example.com/live-caption"
(cd build && cpack)
```
## 版本号与修订号
版本来自顶层 `CMakeLists.txt` 的 `project(live-caption VERSION x.y.z)`,
Debian 修订号是 `LC_...`/`CPACK_DEBIAN_PACKAGE_RELEASE`(当前 `1`),
所以包名形如 `live-caption_0.1.0-1_amd64.deb`。升级时改 `project(... VERSION)`。
## 排错
- **`找不到 libmtmd`**:需要 `libllama0`(提供 `libmtmd.so.0`),与开发时同一个包。
- **装完之后 `live-caption` 报找不到 `libllama.so.0`**:说明 RPATH 丢了。用
`readelf -d /usr/bin/live-caption | grep RUNPATH` 确认里面有
`/usr/lib/x86_64-linux-gnu/llama`。
- **流式模型报加载失败**:确认 `/usr/lib/live-caption/` 下有那两个 `.so`(即打包时
`LC_BUNDLE_SHERPA` 没关掉)。
- **菜单里没有图标/条目**:`sudo update-desktop-database /usr/share/applications`、
`sudo gtk-update-icon-cache -f /usr/share/icons/hicolor`(`postinst` 里已经会自动做,
但某些桌面环境需要重新登录)。
## 与 Debian 原生打包的关系
这是 **CPack 生成的二进制包**,不是用 `debian/` + `dh` 源码构建的 Debian 官方包。
差异:没有源码包(`.dsc`/`.orig.tar.gz`)、没有 `debian/changelog`、没有 split 调试包
(`-dbg`)、也没走 `dh_*` 的一整套规范化。
要往发行版仓库里送包的话,需要另配 `debian/` 目录;本地分发和内部部署用现在这套就够。
+18 -3
View File
@@ -10,11 +10,26 @@
# ${XDG_DATA_HOME:-~/.local/share}/live-caption/models/<id>/
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REG="$ROOT/data/models.json"
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 注册表位置:既支持在源码树里跑(<repo>/scripts/ 与 <repo>/data/),
# 也支持装到系统之后跑(/usr/bin/ 与 /usr/share/live-caption/)。
REG="${LIVE_CAPTION_REGISTRY:-}"
if [ -z "$REG" ]; then
for cand in "$SELF/../data/models.json" \
"$SELF/../share/live-caption/models.json" \
"/usr/share/live-caption/models.json" \
"/usr/local/share/live-caption/models.json"; do
if [ -f "$cand" ]; then REG="$(cd "$(dirname "$cand")" && pwd)/$(basename "$cand")"; break; fi
done
fi
DEST="${XDG_DATA_HOME:-$HOME/.local/share}/live-caption/models"
[ -f "$REG" ] || { echo "找不到注册表: $REG" >&2; exit 1; }
[ -n "$REG" ] && [ -f "$REG" ] || {
echo "找不到模型注册表(models.json)。" >&2
echo "可用 LIVE_CAPTION_REGISTRY=<path> 指定。" >&2
exit 1
}
ids=$(python3 -c "
import json,sys