From 60f05cebd8e7968621a0ce015cf72987ec7577c7 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 29 Sep 2026 10:50:12 +0800 Subject: feat(recorder): add offline voice clip pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [变更性质] - 本提交新增本机离线的语音片段录音能力,不涉及网络服务或语音识别。 [新增功能] - 通过 Pulse 默认输入持续采集音频,以本地 Silero VAD 触发片段。 - 以私有目录和权限写入 24 kbps Ogg/Opus 录音,并提供 systemd 用户服务安装器。 [实现方案] - 使用有限前置缓冲、静音封段和最长段滚动状态机,避免静音落盘及内存无限增长。 - 增加真实 Opus 编解码、服务 dry-run 和纯逻辑状态转换测试;记录模型归属和部署前置条件。 [影响范围] - 新增 Python CLI、运行时模块、中文运维文档和自动测试。 - 未安装、启用或启动任何用户 systemd 服务;不删除或修改录音数据。 --- src/mic_clipper/cli.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/mic_clipper/cli.py (limited to 'src/mic_clipper/cli.py') diff --git a/src/mic_clipper/cli.py b/src/mic_clipper/cli.py new file mode 100644 index 0000000..cd62cef --- /dev/null +++ b/src/mic_clipper/cli.py @@ -0,0 +1,66 @@ +"""Command-line entry points for recording and service administration.""" + +from __future__ import annotations + +import argparse +import logging +import sys +from pathlib import Path + +from mic_clipper import service +from mic_clipper.runner import run_forever + + +DEFAULT_OUTPUT = Path.home() / "Documents" / "Mic Clips" +DEFAULT_UNIT = Path.home() / ".config" / "systemd" / "user" / service.UNIT_NAME +PROJECT_ROOT = Path(__file__).resolve().parents[2] + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Offline voice-activated microphone clips") + commands = parser.add_subparsers(dest="command", required=True) + + run = commands.add_parser("run", help="listen and save speech clips") + run.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT) + run.add_argument("--threshold", type=float, default=0.5) + + service_parser = commands.add_parser("service", help="manage the systemd user service") + service_commands = service_parser.add_subparsers(dest="service_command", required=True) + install = service_commands.add_parser("install", help="install and enable the user service") + install.add_argument("--dry-run", action="store_true") + install.add_argument("--unit-path", type=Path, default=DEFAULT_UNIT) + install.add_argument("--project-root", type=Path, default=PROJECT_ROOT) + install.add_argument("--python", type=Path, default=Path(sys.executable)) + uninstall = service_commands.add_parser("uninstall", help="remove only this tool's service") + uninstall.add_argument("--unit-path", type=Path, default=DEFAULT_UNIT) + service_commands.add_parser("status", help="show systemd user service status") + return parser + + +def main(arguments: list[str] | None = None) -> int: + args = build_parser().parse_args(arguments) + if args.command == "run": + if not 0 < args.threshold <= 1: + raise SystemExit("--threshold must be between 0 and 1") + logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") + run_forever(args.output_dir, threshold=args.threshold) + return 0 + if args.service_command == "install": + commands = service.install( + unit_path=args.unit_path, + python=args.python, + project_root=args.project_root, + dry_run=args.dry_run, + ) + if args.dry_run: + for command in commands: + print(" ".join(command)) + return 0 + if args.service_command == "uninstall": + service.uninstall(unit_path=args.unit_path) + return 0 + return service.status() + + +if __name__ == "__main__": + raise SystemExit(main()) -- cgit v1.2.3