sys2txt 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sys2txt-0.1.1/LICENSE +21 -0
- sys2txt-0.1.1/PKG-INFO +167 -0
- sys2txt-0.1.1/README.md +152 -0
- sys2txt-0.1.1/pyproject.toml +37 -0
- sys2txt-0.1.1/setup.cfg +4 -0
- sys2txt-0.1.1/src/sys2txt/__init__.py +1 -0
- sys2txt-0.1.1/src/sys2txt/__main__.py +172 -0
- sys2txt-0.1.1/src/sys2txt/audio.py +169 -0
- sys2txt-0.1.1/src/sys2txt/constants.py +4 -0
- sys2txt-0.1.1/src/sys2txt/pulse.py +48 -0
- sys2txt-0.1.1/src/sys2txt/transcribe.py +82 -0
- sys2txt-0.1.1/src/sys2txt/utils.py +11 -0
- sys2txt-0.1.1/src/sys2txt.egg-info/PKG-INFO +167 -0
- sys2txt-0.1.1/src/sys2txt.egg-info/SOURCES.txt +20 -0
- sys2txt-0.1.1/src/sys2txt.egg-info/dependency_links.txt +1 -0
- sys2txt-0.1.1/src/sys2txt.egg-info/entry_points.txt +2 -0
- sys2txt-0.1.1/src/sys2txt.egg-info/requires.txt +5 -0
- sys2txt-0.1.1/src/sys2txt.egg-info/top_level.txt +1 -0
- sys2txt-0.1.1/tests/test_audio.py +204 -0
- sys2txt-0.1.1/tests/test_pulse.py +133 -0
- sys2txt-0.1.1/tests/test_transcribe.py +182 -0
- sys2txt-0.1.1/tests/test_utils.py +31 -0
sys2txt-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Joe Heffer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sys2txt-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sys2txt
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Record system audio and transcribe to text using AI
|
|
5
|
+
Author-email: Joe Heffer <jheffer@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: faster-whisper>=1.0.0
|
|
11
|
+
Requires-Dist: openai-whisper>=20231117
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: ruff>=0.8.0; extra == "dev"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
[](https://github.com/Joe-Heffer/sys2txt/actions/workflows/ci.yml)
|
|
17
|
+
[](https://badge.fury.io/py/sys2txt)
|
|
18
|
+
[](https://pypi.org/project/sys2txt/)
|
|
19
|
+
|
|
20
|
+
# System audio to text
|
|
21
|
+
|
|
22
|
+
Record system audio and automatically transcribe to text using ✨AI✨.
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
`sys2txt` is a command-line tool that records your system audio (via PulseAudio/PipeWire monitor sources) with `ffmpeg` and transcribes it locally using [Whisper](https://github.com/openai/whisper). It supports both:
|
|
27
|
+
|
|
28
|
+
- On-demand: Record until you stop, then transcribe once
|
|
29
|
+
- Live-ish: Segment the recording every *N* seconds and transcribe each segment as it’s created (prints continuously)
|
|
30
|
+
|
|
31
|
+
You can use either the `openai-whisper` (Python) reference implementation or the [`faster-whisper`](https://github.com/SYSTRAN/faster-whisper) engine if installed. The tool auto-selects `faster-whisper` when available for better speed on CPU and especially GPU.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### Prerequisites
|
|
36
|
+
|
|
37
|
+
- Ubuntu with PulseAudio or PipeWire (default on modern Ubuntu)
|
|
38
|
+
- ffmpeg
|
|
39
|
+
- Python 3.9+ (recommended)
|
|
40
|
+
|
|
41
|
+
### Install
|
|
42
|
+
|
|
43
|
+
1) System packages
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
sudo apt update
|
|
47
|
+
sudo apt install -y ffmpeg python3-venv python3-pip
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2) Create a virtual environment and install sys2txt
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd sys2txt
|
|
54
|
+
python3 -m venv .venv
|
|
55
|
+
source .venv/bin/activate
|
|
56
|
+
pip install -e .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This installs both faster-whisper (for speed) and openai-whisper (reference implementation). The tool auto-selects faster-whisper when available or falls back to openai-whisper.
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
Record and transcribe once (press Ctrl-C to stop recording):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
sys2txt once --model small.en
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Live segmented transcription (prints ongoing transcript every 8s by default; Ctrl-C to stop):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
sys2txt live --model small.en --segment-seconds 8
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Useful flags
|
|
76
|
+
|
|
77
|
+
- `--source <pulse_source_name>` - Explicit PulseAudio/PipeWire source (e.g., alsa_output.pci-0000_00_1f.3.analog-stereo.monitor)
|
|
78
|
+
- `--list-sources` - List available Pulse sources and exit
|
|
79
|
+
- `--model <size>` - tiny|base|small|medium|large-v2 (default: small)
|
|
80
|
+
- `--engine <auto|faster|whisper>` - Force a specific engine (default: auto)
|
|
81
|
+
- `--language <code>` - Force language code (e.g., en). Omit to auto-detect
|
|
82
|
+
- `--output <path>` - Write final transcript to a file (in live mode, appends)
|
|
83
|
+
- `--duration <seconds>` - (once mode) Record fixed duration instead of waiting for Ctrl-C
|
|
84
|
+
- `--segment-seconds <n>` - (live mode) Segment length in seconds (default: 8)
|
|
85
|
+
- `--timestamps` - Print timestamps alongside text
|
|
86
|
+
|
|
87
|
+
## Examples
|
|
88
|
+
|
|
89
|
+
Record 30s of system audio from the default monitor and transcribe:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
sys2txt once --duration 30 --model small --output transcript.txt
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use a specific PulseAudio source:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
sys2txt once --source alsa_output.usb-Focusrite_Scarlett.monitor --model base
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Live mode with shorter latency and timestamps:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
sys2txt live --segment-seconds 5 --timestamps
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Force the reference openai-whisper engine:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
sys2txt once --engine whisper --model base
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Transcribe an existing audio file:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
sys2txt once --input recording.wav --model small
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Just want one-liners (no sys2txt)?
|
|
120
|
+
|
|
121
|
+
Find the default sink and its monitor source:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pactl get-default-sink
|
|
125
|
+
pactl list short sources | grep monitor
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Record 30s of system audio from the default monitor to a WAV at 16 kHz mono (good for Whisper):
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
ffmpeg -hide_banner -loglevel error -f pulse -i "$(pactl get-default-sink).monitor" -ac 1 -ar 16000 -t 30 out.wav
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Transcribe with openai-whisper CLI:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
whisper out.wav --model small --task transcribe --language en
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Tips and troubleshooting
|
|
141
|
+
|
|
142
|
+
- If you get silence, ensure you are using the monitor source for your output device (the name ends with `.monitor`). Use `--list-sources` to view options.
|
|
143
|
+
- Make sure the application you want to capture is playing through the same output sink as your default sink. You can manage routes with `pavucontrol`.
|
|
144
|
+
- PipeWire systems expose PulseAudio-compatible sources, so `-f pulse` in ffmpeg still works.
|
|
145
|
+
- For better performance on CPU, use faster-whisper with model `base` or `small`. For the best accuracy, use `medium` or `large-v2` (these are heavier).
|
|
146
|
+
- GPU acceleration for faster-whisper requires a compatible ctranslate2 CUDA wheel. Set `SYS2TXT_DEVICE=cuda` to enable it. If not available, it will run on CPU.
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
Install with development dependencies:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install -e ".[dev]"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Run unit tests:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python -m unittest discover -s tests -p "test_*.py"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Format and lint code:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
ruff format src/
|
|
166
|
+
ruff check src/
|
|
167
|
+
```
|
sys2txt-0.1.1/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
[](https://github.com/Joe-Heffer/sys2txt/actions/workflows/ci.yml)
|
|
2
|
+
[](https://badge.fury.io/py/sys2txt)
|
|
3
|
+
[](https://pypi.org/project/sys2txt/)
|
|
4
|
+
|
|
5
|
+
# System audio to text
|
|
6
|
+
|
|
7
|
+
Record system audio and automatically transcribe to text using ✨AI✨.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`sys2txt` is a command-line tool that records your system audio (via PulseAudio/PipeWire monitor sources) with `ffmpeg` and transcribes it locally using [Whisper](https://github.com/openai/whisper). It supports both:
|
|
12
|
+
|
|
13
|
+
- On-demand: Record until you stop, then transcribe once
|
|
14
|
+
- Live-ish: Segment the recording every *N* seconds and transcribe each segment as it’s created (prints continuously)
|
|
15
|
+
|
|
16
|
+
You can use either the `openai-whisper` (Python) reference implementation or the [`faster-whisper`](https://github.com/SYSTRAN/faster-whisper) engine if installed. The tool auto-selects `faster-whisper` when available for better speed on CPU and especially GPU.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Prerequisites
|
|
21
|
+
|
|
22
|
+
- Ubuntu with PulseAudio or PipeWire (default on modern Ubuntu)
|
|
23
|
+
- ffmpeg
|
|
24
|
+
- Python 3.9+ (recommended)
|
|
25
|
+
|
|
26
|
+
### Install
|
|
27
|
+
|
|
28
|
+
1) System packages
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
sudo apt update
|
|
32
|
+
sudo apt install -y ffmpeg python3-venv python3-pip
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2) Create a virtual environment and install sys2txt
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd sys2txt
|
|
39
|
+
python3 -m venv .venv
|
|
40
|
+
source .venv/bin/activate
|
|
41
|
+
pip install -e .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This installs both faster-whisper (for speed) and openai-whisper (reference implementation). The tool auto-selects faster-whisper when available or falls back to openai-whisper.
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
Record and transcribe once (press Ctrl-C to stop recording):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
sys2txt once --model small.en
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Live segmented transcription (prints ongoing transcript every 8s by default; Ctrl-C to stop):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
sys2txt live --model small.en --segment-seconds 8
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Useful flags
|
|
61
|
+
|
|
62
|
+
- `--source <pulse_source_name>` - Explicit PulseAudio/PipeWire source (e.g., alsa_output.pci-0000_00_1f.3.analog-stereo.monitor)
|
|
63
|
+
- `--list-sources` - List available Pulse sources and exit
|
|
64
|
+
- `--model <size>` - tiny|base|small|medium|large-v2 (default: small)
|
|
65
|
+
- `--engine <auto|faster|whisper>` - Force a specific engine (default: auto)
|
|
66
|
+
- `--language <code>` - Force language code (e.g., en). Omit to auto-detect
|
|
67
|
+
- `--output <path>` - Write final transcript to a file (in live mode, appends)
|
|
68
|
+
- `--duration <seconds>` - (once mode) Record fixed duration instead of waiting for Ctrl-C
|
|
69
|
+
- `--segment-seconds <n>` - (live mode) Segment length in seconds (default: 8)
|
|
70
|
+
- `--timestamps` - Print timestamps alongside text
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
Record 30s of system audio from the default monitor and transcribe:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
sys2txt once --duration 30 --model small --output transcript.txt
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use a specific PulseAudio source:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
sys2txt once --source alsa_output.usb-Focusrite_Scarlett.monitor --model base
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Live mode with shorter latency and timestamps:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
sys2txt live --segment-seconds 5 --timestamps
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Force the reference openai-whisper engine:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
sys2txt once --engine whisper --model base
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Transcribe an existing audio file:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
sys2txt once --input recording.wav --model small
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Just want one-liners (no sys2txt)?
|
|
105
|
+
|
|
106
|
+
Find the default sink and its monitor source:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pactl get-default-sink
|
|
110
|
+
pactl list short sources | grep monitor
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Record 30s of system audio from the default monitor to a WAV at 16 kHz mono (good for Whisper):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
ffmpeg -hide_banner -loglevel error -f pulse -i "$(pactl get-default-sink).monitor" -ac 1 -ar 16000 -t 30 out.wav
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Transcribe with openai-whisper CLI:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
whisper out.wav --model small --task transcribe --language en
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Tips and troubleshooting
|
|
126
|
+
|
|
127
|
+
- If you get silence, ensure you are using the monitor source for your output device (the name ends with `.monitor`). Use `--list-sources` to view options.
|
|
128
|
+
- Make sure the application you want to capture is playing through the same output sink as your default sink. You can manage routes with `pavucontrol`.
|
|
129
|
+
- PipeWire systems expose PulseAudio-compatible sources, so `-f pulse` in ffmpeg still works.
|
|
130
|
+
- For better performance on CPU, use faster-whisper with model `base` or `small`. For the best accuracy, use `medium` or `large-v2` (these are heavier).
|
|
131
|
+
- GPU acceleration for faster-whisper requires a compatible ctranslate2 CUDA wheel. Set `SYS2TXT_DEVICE=cuda` to enable it. If not available, it will run on CPU.
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
Install with development dependencies:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pip install -e ".[dev]"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Run unit tests:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
python -m unittest discover -s tests -p "test_*.py"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Format and lint code:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
ruff format src/
|
|
151
|
+
ruff check src/
|
|
152
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=80", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sys2txt"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Record system audio and transcribe to text using AI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Joe Heffer", email = "jheffer@gmail.com"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"faster-whisper>=1.0.0",
|
|
17
|
+
"openai-whisper>=20231117",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
sys2txt = "sys2txt.__main__:main"
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
"ruff>=0.8.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
|
30
|
+
|
|
31
|
+
[tool.ruff]
|
|
32
|
+
line-length = 120
|
|
33
|
+
target-version = "py39"
|
|
34
|
+
|
|
35
|
+
[tool.ruff.lint]
|
|
36
|
+
select = ["E", "F", "I", "W"]
|
|
37
|
+
ignore = []
|
sys2txt-0.1.1/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Main entry point for sys2txt CLI."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from .audio import record_once, segment_and_transcribe_live
|
|
11
|
+
from .constants import WHISPER_MODEL
|
|
12
|
+
from .pulse import get_default_monitor_source, list_pulse_sources
|
|
13
|
+
from .transcribe import transcribe_file
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_timestamp_filename() -> str:
|
|
17
|
+
"""Generate a timestamp-based filename for output files.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
A filename string in the format: YYYY-MM-DD_HH-MM-SS.txt
|
|
21
|
+
"""
|
|
22
|
+
return datetime.now().strftime("%Y-%m-%d_%H-%M-%S.txt")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def ensure_output_dir() -> str:
|
|
26
|
+
"""Ensure the output directory exists and return its path.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Absolute path to the output directory
|
|
30
|
+
"""
|
|
31
|
+
output_dir = os.path.join(os.getcwd(), "output")
|
|
32
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
33
|
+
return output_dir
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main():
|
|
37
|
+
"""Main CLI entry point."""
|
|
38
|
+
parser = argparse.ArgumentParser(description="Record Ubuntu system audio and transcribe with Whisper.")
|
|
39
|
+
sub = parser.add_subparsers(dest="mode", required=True)
|
|
40
|
+
|
|
41
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
42
|
+
common.add_argument(
|
|
43
|
+
"--source", help="PulseAudio source name (e.g., <sink>.monitor). Defaults to auto.", default=None
|
|
44
|
+
)
|
|
45
|
+
common.add_argument(
|
|
46
|
+
"--model",
|
|
47
|
+
dest="model_size",
|
|
48
|
+
default=WHISPER_MODEL,
|
|
49
|
+
help=f"Whisper model size (default: {WHISPER_MODEL})",
|
|
50
|
+
)
|
|
51
|
+
common.add_argument(
|
|
52
|
+
"--engine", choices=["auto", "faster", "whisper"], default="auto", help="Transcription engine (default: auto)"
|
|
53
|
+
)
|
|
54
|
+
common.add_argument("--language", default=None, help="Force language code (e.g., en). Defaults to auto-detect")
|
|
55
|
+
common.add_argument("--timestamps", action="store_true", help="Print timestamps with transcript")
|
|
56
|
+
common.add_argument("--list-sources", action="store_true", help="List PulseAudio sources and exit")
|
|
57
|
+
|
|
58
|
+
once = sub.add_parser("once", parents=[common], help="Record once and transcribe after")
|
|
59
|
+
once.add_argument("--duration", type=int, default=None, help="Record for N seconds instead of Ctrl-C")
|
|
60
|
+
once.add_argument("--output", default=None, help="Write transcript to file")
|
|
61
|
+
once.add_argument("--input", default=None, help="Skip recording and transcribe this existing audio file")
|
|
62
|
+
|
|
63
|
+
live = sub.add_parser("live", parents=[common], help="Segmented live transcription")
|
|
64
|
+
live.add_argument("--segment-seconds", type=int, default=8, help="Segment length in seconds (default: 8)")
|
|
65
|
+
live.add_argument("--output", default=None, help="Append live transcript to this file as it's produced")
|
|
66
|
+
|
|
67
|
+
args = parser.parse_args()
|
|
68
|
+
|
|
69
|
+
if args.list_sources:
|
|
70
|
+
sources = list_pulse_sources()
|
|
71
|
+
if not sources:
|
|
72
|
+
print("No PulseAudio sources found. Is PulseAudio/PipeWire running?", file=sys.stderr)
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
print("Available PulseAudio sources:")
|
|
75
|
+
for name, _ in sources:
|
|
76
|
+
print(" ", name)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
# Determine source
|
|
80
|
+
source = args.source or get_default_monitor_source()
|
|
81
|
+
|
|
82
|
+
if args.mode == "once":
|
|
83
|
+
# Determine output file path
|
|
84
|
+
output_dir = ensure_output_dir()
|
|
85
|
+
if args.output:
|
|
86
|
+
# If user specified a path, use it as-is (could be relative or absolute)
|
|
87
|
+
output_file = args.output
|
|
88
|
+
else:
|
|
89
|
+
# Generate timestamp-based filename in output/ directory
|
|
90
|
+
output_file = os.path.join(output_dir, get_timestamp_filename())
|
|
91
|
+
|
|
92
|
+
if args.input:
|
|
93
|
+
audio_path = args.input
|
|
94
|
+
else:
|
|
95
|
+
# Make a temp WAV, record until duration/ctrl-c, then transcribe
|
|
96
|
+
with tempfile.TemporaryDirectory(prefix="sys2txt_") as tmp:
|
|
97
|
+
wav = os.path.join(tmp, "capture.wav")
|
|
98
|
+
record_once(source=source, out_wav=wav, sample_rate=16000, channels=1, duration=args.duration)
|
|
99
|
+
audio_path = wav
|
|
100
|
+
text = transcribe_file(
|
|
101
|
+
audio_path,
|
|
102
|
+
engine=args.engine,
|
|
103
|
+
model_size=args.model_size,
|
|
104
|
+
language=args.language,
|
|
105
|
+
timestamps=args.timestamps,
|
|
106
|
+
)
|
|
107
|
+
print(text)
|
|
108
|
+
with open(output_file, "w", encoding="utf-8") as w:
|
|
109
|
+
w.write(text + "\n")
|
|
110
|
+
print(f"Transcript saved to: {output_file}")
|
|
111
|
+
return
|
|
112
|
+
# If input provided, just transcribe it
|
|
113
|
+
text = transcribe_file(
|
|
114
|
+
audio_path,
|
|
115
|
+
engine=args.engine,
|
|
116
|
+
model_size=args.model_size,
|
|
117
|
+
language=args.language,
|
|
118
|
+
timestamps=args.timestamps,
|
|
119
|
+
)
|
|
120
|
+
print(text)
|
|
121
|
+
with open(output_file, "w", encoding="utf-8") as w:
|
|
122
|
+
w.write(text + "\n")
|
|
123
|
+
print(f"Transcript saved to: {output_file}")
|
|
124
|
+
|
|
125
|
+
elif args.mode == "live":
|
|
126
|
+
# Determine output file path
|
|
127
|
+
output_dir = ensure_output_dir()
|
|
128
|
+
if args.output:
|
|
129
|
+
# If user specified a path, use it as-is (could be relative or absolute)
|
|
130
|
+
output_file = args.output
|
|
131
|
+
else:
|
|
132
|
+
# Generate timestamp-based filename in output/ directory
|
|
133
|
+
output_file = os.path.join(output_dir, get_timestamp_filename())
|
|
134
|
+
|
|
135
|
+
print(f"Live transcript will be saved to: {output_file}")
|
|
136
|
+
|
|
137
|
+
def transcribe_segment(file_path: str, segment_index: int) -> str:
|
|
138
|
+
"""Transcribe a segment and format with optional timestamp prefix."""
|
|
139
|
+
text = transcribe_file(
|
|
140
|
+
file_path,
|
|
141
|
+
engine=args.engine,
|
|
142
|
+
model_size=args.model_size,
|
|
143
|
+
language=args.language,
|
|
144
|
+
timestamps=args.timestamps,
|
|
145
|
+
)
|
|
146
|
+
if args.timestamps:
|
|
147
|
+
# Add segment time window prefix
|
|
148
|
+
start = segment_index * args.segment_seconds
|
|
149
|
+
end = start + args.segment_seconds
|
|
150
|
+
prefix = f"[{start:>5d}-{end:>5d}s] "
|
|
151
|
+
return prefix + text.strip()
|
|
152
|
+
else:
|
|
153
|
+
return text.strip()
|
|
154
|
+
|
|
155
|
+
segment_and_transcribe_live(
|
|
156
|
+
source=source,
|
|
157
|
+
sample_rate=16000,
|
|
158
|
+
channels=1,
|
|
159
|
+
segment_seconds=args.segment_seconds,
|
|
160
|
+
transcribe_callback=transcribe_segment,
|
|
161
|
+
output_path=output_file,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
try:
|
|
167
|
+
main()
|
|
168
|
+
except RuntimeError as e:
|
|
169
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
except KeyboardInterrupt:
|
|
172
|
+
pass
|