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 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
+ [![CI](https://github.com/Joe-Heffer/sys2txt/actions/workflows/ci.yml/badge.svg)](https://github.com/Joe-Heffer/sys2txt/actions/workflows/ci.yml)
17
+ [![PyPI version](https://badge.fury.io/py/sys2txt.svg)](https://badge.fury.io/py/sys2txt)
18
+ [![Python versions](https://img.shields.io/pypi/pyversions/sys2txt.svg)](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
+ ```
@@ -0,0 +1,152 @@
1
+ [![CI](https://github.com/Joe-Heffer/sys2txt/actions/workflows/ci.yml/badge.svg)](https://github.com/Joe-Heffer/sys2txt/actions/workflows/ci.yml)
2
+ [![PyPI version](https://badge.fury.io/py/sys2txt.svg)](https://badge.fury.io/py/sys2txt)
3
+ [![Python versions](https://img.shields.io/pypi/pyversions/sys2txt.svg)](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 = []
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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