summaryrefslogtreecommitdiff
path: root/src/mic_clipper/cli.py
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-29 10:50:12 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-29 10:50:12 +0800
commit60f05cebd8e7968621a0ce015cf72987ec7577c7 (patch)
tree7f3c572ae82015b17ceaff6eb901ec88ba809856 /src/mic_clipper/cli.py
downloadmic-clipper-60f05cebd8e7968621a0ce015cf72987ec7577c7.tar.gz
feat(recorder): add offline voice clip pipeline
[变更性质] - 本提交新增本机离线的语音片段录音能力,不涉及网络服务或语音识别。 [新增功能] - 通过 Pulse 默认输入持续采集音频,以本地 Silero VAD 触发片段。 - 以私有目录和权限写入 24 kbps Ogg/Opus 录音,并提供 systemd 用户服务安装器。 [实现方案] - 使用有限前置缓冲、静音封段和最长段滚动状态机,避免静音落盘及内存无限增长。 - 增加真实 Opus 编解码、服务 dry-run 和纯逻辑状态转换测试;记录模型归属和部署前置条件。 [影响范围] - 新增 Python CLI、运行时模块、中文运维文档和自动测试。 - 未安装、启用或启动任何用户 systemd 服务;不删除或修改录音数据。
Diffstat (limited to 'src/mic_clipper/cli.py')
-rw-r--r--src/mic_clipper/cli.py66
1 files changed, 66 insertions, 0 deletions
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())