s2t-py 0.1.0__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.
- s2t_py-0.1.0/LICENSE +21 -0
- s2t_py-0.1.0/PKG-INFO +144 -0
- s2t_py-0.1.0/README.md +116 -0
- s2t_py-0.1.0/pyproject.toml +40 -0
- s2t_py-0.1.0/s2t.py +333 -0
- s2t_py-0.1.0/s2t_py.egg-info/PKG-INFO +144 -0
- s2t_py-0.1.0/s2t_py.egg-info/SOURCES.txt +10 -0
- s2t_py-0.1.0/s2t_py.egg-info/dependency_links.txt +1 -0
- s2t_py-0.1.0/s2t_py.egg-info/entry_points.txt +2 -0
- s2t_py-0.1.0/s2t_py.egg-info/requires.txt +1 -0
- s2t_py-0.1.0/s2t_py.egg-info/top_level.txt +1 -0
- s2t_py-0.1.0/setup.cfg +4 -0
s2t_py-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Draxon
|
|
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.
|
s2t_py-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: s2t-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast, multimodal speech-to-text CLI powered by DraxonLLMs
|
|
5
|
+
Author: Draxon
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DraxonV1/Speech2Text
|
|
8
|
+
Project-URL: Repository, https://github.com/DraxonV1/Speech2Text
|
|
9
|
+
Project-URL: Issues, https://github.com/DraxonV1/Speech2Text/issues
|
|
10
|
+
Keywords: speech-to-text,audio,transcription,llm,draxonllms,cli,gemini,qwen
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: requests>=2.28.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# s2t (Speech-to-Text CLI)
|
|
30
|
+
|
|
31
|
+
A lightweight CLI tool for transcribing audio (`.mp3`, `.opus`, `.m4a`, `.wav`, `.flac`, `.ogg`, etc.) directly to plain text or clean multi-page PDF documents using [DraxonLLMs](https://ai.draxon.one).
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Multi-Format Support**: Handles `.mp3`, `.opus`, `.m4a`, `.wav`, `.flac`, `.ogg`, etc. Auto-normalizes inputs to 16kHz mono WAV via `ffmpeg` if available.
|
|
36
|
+
- **Multilingual LLM Audio Models**: Powered by audio-native models (`gemini-flash`, `gemini-pro`, `qwen3.8-omni-flash`, `mimo-v2.5-asr`).
|
|
37
|
+
- **Flexible Outputs**:
|
|
38
|
+
- Direct raw text to `stdout`
|
|
39
|
+
- Output to plain text file (`.txt`)
|
|
40
|
+
- Standalone multi-page PDF export (`.pdf`) with automatic page wrapping and zero external dependencies
|
|
41
|
+
- **Live Streaming**: Stream tokens directly to terminal in real-time (`--stream`).
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Via pip:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install s2t-py
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### From source:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/DraxonV1/Speech2Text.git
|
|
57
|
+
cd Speech2Text
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
### 1. Set API Key
|
|
66
|
+
|
|
67
|
+
Set your DraxonLLMs API key via environment variable:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Linux / macOS
|
|
71
|
+
export DRAXON_API_KEY="drx_your_api_key_here"
|
|
72
|
+
|
|
73
|
+
# Windows PowerShell
|
|
74
|
+
$env:DRAXON_API_KEY="drx_your_api_key_here"
|
|
75
|
+
|
|
76
|
+
# Windows Command Prompt
|
|
77
|
+
set DRAXON_API_KEY=drx_your_api_key_here
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
*(You can also pass it explicitly with `--api-key="drx_..."`)*
|
|
81
|
+
|
|
82
|
+
### 2. Transcribe Audio
|
|
83
|
+
|
|
84
|
+
#### Print raw text directly to terminal:
|
|
85
|
+
```bash
|
|
86
|
+
s2t --input=voice.opus
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### Output to plain text file:
|
|
90
|
+
```bash
|
|
91
|
+
s2t --input=meeting.mp3 --output=transcript.txt
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Output to formatted PDF document:
|
|
95
|
+
```bash
|
|
96
|
+
s2t --input=interview.m4a --output=interview.pdf
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Real-time streaming transcription:
|
|
100
|
+
```bash
|
|
101
|
+
s2t --input=speech.wav --stream
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## CLI Options
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
usage: s2t [-h] -i INPUT [-o OUTPUT] [--api-key API_KEY] [--api-base API_BASE]
|
|
110
|
+
[--model MODEL] [--stream]
|
|
111
|
+
|
|
112
|
+
options:
|
|
113
|
+
-h, --help show this help message and exit
|
|
114
|
+
-i INPUT, --input INPUT
|
|
115
|
+
Input audio file (.mp3, .opus, .m4a, .wav, etc.)
|
|
116
|
+
-o OUTPUT, --output OUTPUT
|
|
117
|
+
Output file (.txt or .pdf). If omitted, prints raw text to stdout.
|
|
118
|
+
--api-key API_KEY DraxonLLMs API key (default: env DRAXON_API_KEY)
|
|
119
|
+
--api-base API_BASE API base URL (default: https://ai.draxon.one/api/v1)
|
|
120
|
+
--model MODEL Model name (default: gemini-flash)
|
|
121
|
+
--stream Stream raw text from model in real time
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Supported Models
|
|
127
|
+
|
|
128
|
+
Refer to `models.txt` for the current audio model roster:
|
|
129
|
+
|
|
130
|
+
- `gemini-flash` *(default)*: High accuracy, low latency, multilingual (English, Hindi, Spanish, etc.)
|
|
131
|
+
- `gemini-pro`: Maximum reasoning and contextual transcription
|
|
132
|
+
- `qwen3.8-omni-flash`: Omni-channel audio model
|
|
133
|
+
- `mimo-v2.5-asr`: Specialized Chinese speech recognition
|
|
134
|
+
|
|
135
|
+
To select a different model:
|
|
136
|
+
```bash
|
|
137
|
+
s2t --input=audio.mp3 --model=gemini-pro
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
s2t_py-0.1.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# s2t (Speech-to-Text CLI)
|
|
2
|
+
|
|
3
|
+
A lightweight CLI tool for transcribing audio (`.mp3`, `.opus`, `.m4a`, `.wav`, `.flac`, `.ogg`, etc.) directly to plain text or clean multi-page PDF documents using [DraxonLLMs](https://ai.draxon.one).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-Format Support**: Handles `.mp3`, `.opus`, `.m4a`, `.wav`, `.flac`, `.ogg`, etc. Auto-normalizes inputs to 16kHz mono WAV via `ffmpeg` if available.
|
|
8
|
+
- **Multilingual LLM Audio Models**: Powered by audio-native models (`gemini-flash`, `gemini-pro`, `qwen3.8-omni-flash`, `mimo-v2.5-asr`).
|
|
9
|
+
- **Flexible Outputs**:
|
|
10
|
+
- Direct raw text to `stdout`
|
|
11
|
+
- Output to plain text file (`.txt`)
|
|
12
|
+
- Standalone multi-page PDF export (`.pdf`) with automatic page wrapping and zero external dependencies
|
|
13
|
+
- **Live Streaming**: Stream tokens directly to terminal in real-time (`--stream`).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Via pip:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install s2t-py
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### From source:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/DraxonV1/Speech2Text.git
|
|
29
|
+
cd Speech2Text
|
|
30
|
+
pip install -e .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
### 1. Set API Key
|
|
38
|
+
|
|
39
|
+
Set your DraxonLLMs API key via environment variable:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Linux / macOS
|
|
43
|
+
export DRAXON_API_KEY="drx_your_api_key_here"
|
|
44
|
+
|
|
45
|
+
# Windows PowerShell
|
|
46
|
+
$env:DRAXON_API_KEY="drx_your_api_key_here"
|
|
47
|
+
|
|
48
|
+
# Windows Command Prompt
|
|
49
|
+
set DRAXON_API_KEY=drx_your_api_key_here
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
*(You can also pass it explicitly with `--api-key="drx_..."`)*
|
|
53
|
+
|
|
54
|
+
### 2. Transcribe Audio
|
|
55
|
+
|
|
56
|
+
#### Print raw text directly to terminal:
|
|
57
|
+
```bash
|
|
58
|
+
s2t --input=voice.opus
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Output to plain text file:
|
|
62
|
+
```bash
|
|
63
|
+
s2t --input=meeting.mp3 --output=transcript.txt
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Output to formatted PDF document:
|
|
67
|
+
```bash
|
|
68
|
+
s2t --input=interview.m4a --output=interview.pdf
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Real-time streaming transcription:
|
|
72
|
+
```bash
|
|
73
|
+
s2t --input=speech.wav --stream
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## CLI Options
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
usage: s2t [-h] -i INPUT [-o OUTPUT] [--api-key API_KEY] [--api-base API_BASE]
|
|
82
|
+
[--model MODEL] [--stream]
|
|
83
|
+
|
|
84
|
+
options:
|
|
85
|
+
-h, --help show this help message and exit
|
|
86
|
+
-i INPUT, --input INPUT
|
|
87
|
+
Input audio file (.mp3, .opus, .m4a, .wav, etc.)
|
|
88
|
+
-o OUTPUT, --output OUTPUT
|
|
89
|
+
Output file (.txt or .pdf). If omitted, prints raw text to stdout.
|
|
90
|
+
--api-key API_KEY DraxonLLMs API key (default: env DRAXON_API_KEY)
|
|
91
|
+
--api-base API_BASE API base URL (default: https://ai.draxon.one/api/v1)
|
|
92
|
+
--model MODEL Model name (default: gemini-flash)
|
|
93
|
+
--stream Stream raw text from model in real time
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Supported Models
|
|
99
|
+
|
|
100
|
+
Refer to `models.txt` for the current audio model roster:
|
|
101
|
+
|
|
102
|
+
- `gemini-flash` *(default)*: High accuracy, low latency, multilingual (English, Hindi, Spanish, etc.)
|
|
103
|
+
- `gemini-pro`: Maximum reasoning and contextual transcription
|
|
104
|
+
- `qwen3.8-omni-flash`: Omni-channel audio model
|
|
105
|
+
- `mimo-v2.5-asr`: Specialized Chinese speech recognition
|
|
106
|
+
|
|
107
|
+
To select a different model:
|
|
108
|
+
```bash
|
|
109
|
+
s2t --input=audio.mp3 --model=gemini-pro
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "s2t-py"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Fast, multimodal speech-to-text CLI powered by DraxonLLMs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Draxon" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["speech-to-text", "audio", "transcription", "llm", "draxonllms", "cli", "gemini", "qwen"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.8",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"requests>=2.28.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
s2t = "s2t:cli"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/DraxonV1/Speech2Text"
|
|
39
|
+
Repository = "https://github.com/DraxonV1/Speech2Text"
|
|
40
|
+
Issues = "https://github.com/DraxonV1/Speech2Text/issues"
|
s2t_py-0.1.0/s2t.py
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
s2t - Speech-to-Text CLI using DraxonLLMs and Qwen 3.8 Omni Flash
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import base64
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
import textwrap
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
import requests
|
|
20
|
+
|
|
21
|
+
DEFAULT_API_BASE = os.environ.get("DRAXON_API_BASE", "https://ai.draxon.one/api/v1")
|
|
22
|
+
DEFAULT_API_KEY = os.environ.get("DRAXON_API_KEY", "drx_e1O-gFJJSaLDp9Uv_l1grVDi97vMydQ0")
|
|
23
|
+
DEFAULT_MODEL = os.environ.get("DRAXON_MODEL", "gemini-flash")
|
|
24
|
+
PROMPT = "Output only raw text nothing else, make sure every single letter in audio is outputed"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def convert_audio_to_wav(input_path: Path) -> tuple[bytes, str]:
|
|
28
|
+
"""
|
|
29
|
+
Read input audio file. If ffmpeg is available and file is not already wav,
|
|
30
|
+
normalize to 16kHz mono WAV for maximum model fidelity.
|
|
31
|
+
"""
|
|
32
|
+
ext = input_path.suffix.lower().lstrip(".")
|
|
33
|
+
if not input_path.exists():
|
|
34
|
+
raise FileNotFoundError(f"Input file not found: {input_path}")
|
|
35
|
+
|
|
36
|
+
# If already wav, read directly
|
|
37
|
+
if ext == "wav":
|
|
38
|
+
return input_path.read_bytes(), "wav"
|
|
39
|
+
|
|
40
|
+
# Check for ffmpeg
|
|
41
|
+
ffmpeg_bin = shutil.which("ffmpeg")
|
|
42
|
+
if ffmpeg_bin:
|
|
43
|
+
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
|
44
|
+
tmp_wav = tmp.name
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
cmd = [
|
|
48
|
+
ffmpeg_bin,
|
|
49
|
+
"-y",
|
|
50
|
+
"-i",
|
|
51
|
+
str(input_path),
|
|
52
|
+
"-vn",
|
|
53
|
+
"-ar",
|
|
54
|
+
"16000",
|
|
55
|
+
"-ac",
|
|
56
|
+
"1",
|
|
57
|
+
"-c:a",
|
|
58
|
+
"pcm_s16le",
|
|
59
|
+
tmp_wav,
|
|
60
|
+
]
|
|
61
|
+
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
62
|
+
if res.returncode == 0 and os.path.exists(tmp_wav) and os.path.getsize(tmp_wav) > 0:
|
|
63
|
+
with open(tmp_wav, "rb") as f:
|
|
64
|
+
data = f.read()
|
|
65
|
+
return data, "wav"
|
|
66
|
+
finally:
|
|
67
|
+
if os.path.exists(tmp_wav):
|
|
68
|
+
try:
|
|
69
|
+
os.remove(tmp_wav)
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# Fallback to direct raw bytes
|
|
74
|
+
fmt = "mp3" if ext in {"mp3", "opus", "m4a", "aac", "flac", "ogg"} else ext
|
|
75
|
+
return input_path.read_bytes(), fmt
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def transcribe_audio(
|
|
79
|
+
audio_bytes: bytes,
|
|
80
|
+
audio_format: str,
|
|
81
|
+
api_key: str,
|
|
82
|
+
api_base: str,
|
|
83
|
+
model: str = DEFAULT_MODEL,
|
|
84
|
+
stream: bool = False,
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Send audio to DraxonLLMs OpenAI-compatible chat completions endpoint."""
|
|
87
|
+
b64_data = base64.b64encode(audio_bytes).decode("utf-8")
|
|
88
|
+
|
|
89
|
+
payload = {
|
|
90
|
+
"model": model,
|
|
91
|
+
"messages": [
|
|
92
|
+
{
|
|
93
|
+
"role": "user",
|
|
94
|
+
"content": [
|
|
95
|
+
{"type": "text", "text": PROMPT},
|
|
96
|
+
{
|
|
97
|
+
"type": "input_audio",
|
|
98
|
+
"input_audio": {
|
|
99
|
+
"data": b64_data,
|
|
100
|
+
"format": audio_format,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"temperature": 0.0,
|
|
107
|
+
"stream": stream,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
url = f"{api_base.rstrip('/')}/chat/completions"
|
|
111
|
+
headers = {
|
|
112
|
+
"Authorization": f"Bearer {api_key}",
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if stream:
|
|
117
|
+
resp = requests.post(url, headers=headers, json=payload, stream=True, timeout=180)
|
|
118
|
+
resp.raise_for_status()
|
|
119
|
+
collected: list[str] = []
|
|
120
|
+
for line in resp.iter_lines(decode_unicode=True):
|
|
121
|
+
if not line:
|
|
122
|
+
continue
|
|
123
|
+
if line.startswith("data: "):
|
|
124
|
+
data_str = line[6:].strip()
|
|
125
|
+
if data_str == "[DONE]":
|
|
126
|
+
break
|
|
127
|
+
try:
|
|
128
|
+
chunk = json.loads(data_str)
|
|
129
|
+
delta = chunk.get("choices", [{}])[0].get("delta", {})
|
|
130
|
+
val = delta.get("content") or delta.get("reasoning_content") or ""
|
|
131
|
+
if val:
|
|
132
|
+
collected.append(val)
|
|
133
|
+
sys.stdout.write(val)
|
|
134
|
+
sys.stdout.flush()
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
if collected:
|
|
138
|
+
sys.stdout.write("\n")
|
|
139
|
+
sys.stdout.flush()
|
|
140
|
+
for attempt in range(5):
|
|
141
|
+
resp = requests.post(url, headers=headers, json=payload, timeout=180)
|
|
142
|
+
if resp.status_code == 200:
|
|
143
|
+
break
|
|
144
|
+
if resp.status_code in {429, 500, 502, 503, 504} and attempt < 4:
|
|
145
|
+
import time
|
|
146
|
+
sleep_sec = 3.0 * (attempt + 1)
|
|
147
|
+
time.sleep(sleep_sec)
|
|
148
|
+
continue
|
|
149
|
+
raise RuntimeError(f"API Error ({resp.status_code}): {resp.text[:300]}")
|
|
150
|
+
|
|
151
|
+
data = resp.json()
|
|
152
|
+
msg = data.get("choices", [{}])[0].get("message", {})
|
|
153
|
+
content = msg.get("content") or msg.get("reasoning_content") or ""
|
|
154
|
+
return (content or "").strip()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def write_pdf(text: str, output_path: Path) -> None:
|
|
158
|
+
"""Pure Python PDF generator supporting multiline text without third-party dependencies."""
|
|
159
|
+
max_chars_per_line = 85
|
|
160
|
+
lines_per_page = 48
|
|
161
|
+
|
|
162
|
+
all_lines: list[str] = []
|
|
163
|
+
for paragraph in text.splitlines():
|
|
164
|
+
if not paragraph.strip():
|
|
165
|
+
all_lines.append("")
|
|
166
|
+
else:
|
|
167
|
+
wrapped = textwrap.wrap(paragraph, width=max_chars_per_line)
|
|
168
|
+
all_lines.extend(wrapped)
|
|
169
|
+
|
|
170
|
+
if not all_lines:
|
|
171
|
+
all_lines = [""]
|
|
172
|
+
|
|
173
|
+
pages_lines = [all_lines[i : i + lines_per_page] for i in range(0, len(all_lines), lines_per_page)]
|
|
174
|
+
num_pages = len(pages_lines)
|
|
175
|
+
|
|
176
|
+
font_obj_idx = 3 + num_pages * 2
|
|
177
|
+
objects: dict[int, bytes] = {}
|
|
178
|
+
page_obj_ids = [3 + i for i in range(num_pages)]
|
|
179
|
+
content_obj_ids = [3 + num_pages + i for i in range(num_pages)]
|
|
180
|
+
|
|
181
|
+
objects[1] = b"<< /Type /Catalog /Pages 2 0 R >>"
|
|
182
|
+
kids_str = " ".join(f"{pid} 0 R" for pid in page_obj_ids)
|
|
183
|
+
objects[2] = f"<< /Type /Pages /Kids [{kids_str}] /Count {num_pages} >>".encode("ascii")
|
|
184
|
+
|
|
185
|
+
for i in range(num_pages):
|
|
186
|
+
pid = page_obj_ids[i]
|
|
187
|
+
cid = content_obj_ids[i]
|
|
188
|
+
objects[pid] = (
|
|
189
|
+
f"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents {cid} 0 R "
|
|
190
|
+
f"/Resources << /Font << /F1 {font_obj_idx} 0 R >> >> >>"
|
|
191
|
+
).encode("ascii")
|
|
192
|
+
|
|
193
|
+
page_content = ["BT", "/F1 11 Tf", "50 740 Td", "14 TL"]
|
|
194
|
+
first = True
|
|
195
|
+
for line in pages_lines[i]:
|
|
196
|
+
escaped = line.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
|
|
197
|
+
escaped = "".join(c if ord(c) < 256 else "?" for c in escaped)
|
|
198
|
+
if first:
|
|
199
|
+
page_content.append(f"({escaped}) Tj")
|
|
200
|
+
first = False
|
|
201
|
+
else:
|
|
202
|
+
page_content.append(f"T* ({escaped}) Tj")
|
|
203
|
+
page_content.append("ET")
|
|
204
|
+
|
|
205
|
+
stream_bytes = "\n".join(page_content).encode("latin1", errors="replace")
|
|
206
|
+
objects[cid] = (
|
|
207
|
+
f"<< /Length {len(stream_bytes)} >>\nstream\n".encode("ascii")
|
|
208
|
+
+ stream_bytes
|
|
209
|
+
+ b"\nendstream"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
objects[font_obj_idx] = b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>"
|
|
213
|
+
|
|
214
|
+
buf = bytearray()
|
|
215
|
+
buf += b"%PDF-1.4\n"
|
|
216
|
+
total_objs = font_obj_idx
|
|
217
|
+
xref_offsets = [0] * (total_objs + 1)
|
|
218
|
+
|
|
219
|
+
for idx in range(1, total_objs + 1):
|
|
220
|
+
xref_offsets[idx] = len(buf)
|
|
221
|
+
buf += f"{idx} 0 obj\n".encode("ascii")
|
|
222
|
+
buf += objects[idx]
|
|
223
|
+
buf += b"\nendobj\n"
|
|
224
|
+
|
|
225
|
+
startxref = len(buf)
|
|
226
|
+
buf += b"0000000000 65535 f \n"
|
|
227
|
+
for idx in range(1, total_objs + 1):
|
|
228
|
+
buf += f"{xref_offsets[idx]:010d} 00000 n \n".encode("ascii")
|
|
229
|
+
buf += f"trailer\n<< /Size {total_objs + 1} /Root 1 0 R >>\nstartxref\n{startxref}\n%%EOF\n".encode("ascii")
|
|
230
|
+
|
|
231
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
232
|
+
output_path.write_bytes(buf)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
236
|
+
parser = argparse.ArgumentParser(
|
|
237
|
+
prog="s2t",
|
|
238
|
+
description="Speech-to-Text CLI using DraxonLLMs and Qwen 3.8 Omni Flash",
|
|
239
|
+
)
|
|
240
|
+
parser.add_argument(
|
|
241
|
+
"-i",
|
|
242
|
+
"--input",
|
|
243
|
+
required=True,
|
|
244
|
+
help="Input audio file (.mp3, .opus, .m4a, .wav, .flac, .ogg, etc.)",
|
|
245
|
+
)
|
|
246
|
+
parser.add_argument(
|
|
247
|
+
"-o",
|
|
248
|
+
"--output",
|
|
249
|
+
required=False,
|
|
250
|
+
default=None,
|
|
251
|
+
help="Output file (.txt or .pdf). If omitted, prints raw text to stdout.",
|
|
252
|
+
)
|
|
253
|
+
parser.add_argument(
|
|
254
|
+
"--api-key",
|
|
255
|
+
default=DEFAULT_API_KEY,
|
|
256
|
+
help="DraxonLLMs API key (default: env DRAXON_API_KEY or built-in)",
|
|
257
|
+
)
|
|
258
|
+
parser.add_argument(
|
|
259
|
+
"--api-base",
|
|
260
|
+
default=DEFAULT_API_BASE,
|
|
261
|
+
help="DraxonLLMs API base URL (default: env DRAXON_API_BASE or https://ai.draxon.one/api/v1)",
|
|
262
|
+
)
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
"--model",
|
|
265
|
+
default=DEFAULT_MODEL,
|
|
266
|
+
help=f"Model identifier (default: {DEFAULT_MODEL})",
|
|
267
|
+
)
|
|
268
|
+
parser.add_argument(
|
|
269
|
+
"--stream",
|
|
270
|
+
action="store_true",
|
|
271
|
+
help="Stream raw text from model in real time",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
args = parser.parse_args(argv)
|
|
275
|
+
|
|
276
|
+
input_path = Path(args.input)
|
|
277
|
+
if not input_path.exists():
|
|
278
|
+
sys.stderr.write(f"Error: input file '{args.input}' does not exist.\n")
|
|
279
|
+
return 1
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
audio_bytes, audio_format = convert_audio_to_wav(input_path)
|
|
283
|
+
except Exception as exc:
|
|
284
|
+
sys.stderr.write(f"Error reading audio file: {exc}\n")
|
|
285
|
+
return 1
|
|
286
|
+
|
|
287
|
+
# Transcribe via DraxonLLMs
|
|
288
|
+
try:
|
|
289
|
+
raw_text = transcribe_audio(
|
|
290
|
+
audio_bytes=audio_bytes,
|
|
291
|
+
audio_format=audio_format,
|
|
292
|
+
api_key=args.api_key,
|
|
293
|
+
api_base=args.api_base,
|
|
294
|
+
model=args.model,
|
|
295
|
+
stream=args.stream and not args.output,
|
|
296
|
+
)
|
|
297
|
+
except Exception as exc:
|
|
298
|
+
sys.stderr.write(f"Transcription error: {exc}\n")
|
|
299
|
+
return 1
|
|
300
|
+
|
|
301
|
+
# Output handling
|
|
302
|
+
if args.output:
|
|
303
|
+
out_path = Path(args.output)
|
|
304
|
+
ext = out_path.suffix.lower()
|
|
305
|
+
if ext == ".pdf":
|
|
306
|
+
try:
|
|
307
|
+
write_pdf(raw_text, out_path)
|
|
308
|
+
print(f"Saved PDF transcription to {out_path}")
|
|
309
|
+
except Exception as exc:
|
|
310
|
+
sys.stderr.write(f"Failed to write PDF: {exc}\n")
|
|
311
|
+
return 1
|
|
312
|
+
else:
|
|
313
|
+
try:
|
|
314
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
315
|
+
out_path.write_text(raw_text, encoding="utf-8")
|
|
316
|
+
print(f"Saved text transcription to {out_path}")
|
|
317
|
+
except Exception as exc:
|
|
318
|
+
sys.stderr.write(f"Failed to write output text: {exc}\n")
|
|
319
|
+
return 1
|
|
320
|
+
else:
|
|
321
|
+
if not args.stream:
|
|
322
|
+
print(raw_text)
|
|
323
|
+
|
|
324
|
+
return 0
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def cli() -> None:
|
|
328
|
+
"""Entrypoint for setuptools / console_scripts."""
|
|
329
|
+
sys.exit(main())
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
if __name__ == "__main__":
|
|
333
|
+
cli()
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: s2t-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast, multimodal speech-to-text CLI powered by DraxonLLMs
|
|
5
|
+
Author: Draxon
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DraxonV1/Speech2Text
|
|
8
|
+
Project-URL: Repository, https://github.com/DraxonV1/Speech2Text
|
|
9
|
+
Project-URL: Issues, https://github.com/DraxonV1/Speech2Text/issues
|
|
10
|
+
Keywords: speech-to-text,audio,transcription,llm,draxonllms,cli,gemini,qwen
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: requests>=2.28.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# s2t (Speech-to-Text CLI)
|
|
30
|
+
|
|
31
|
+
A lightweight CLI tool for transcribing audio (`.mp3`, `.opus`, `.m4a`, `.wav`, `.flac`, `.ogg`, etc.) directly to plain text or clean multi-page PDF documents using [DraxonLLMs](https://ai.draxon.one).
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Multi-Format Support**: Handles `.mp3`, `.opus`, `.m4a`, `.wav`, `.flac`, `.ogg`, etc. Auto-normalizes inputs to 16kHz mono WAV via `ffmpeg` if available.
|
|
36
|
+
- **Multilingual LLM Audio Models**: Powered by audio-native models (`gemini-flash`, `gemini-pro`, `qwen3.8-omni-flash`, `mimo-v2.5-asr`).
|
|
37
|
+
- **Flexible Outputs**:
|
|
38
|
+
- Direct raw text to `stdout`
|
|
39
|
+
- Output to plain text file (`.txt`)
|
|
40
|
+
- Standalone multi-page PDF export (`.pdf`) with automatic page wrapping and zero external dependencies
|
|
41
|
+
- **Live Streaming**: Stream tokens directly to terminal in real-time (`--stream`).
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Via pip:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install s2t-py
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### From source:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/DraxonV1/Speech2Text.git
|
|
57
|
+
cd Speech2Text
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
### 1. Set API Key
|
|
66
|
+
|
|
67
|
+
Set your DraxonLLMs API key via environment variable:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Linux / macOS
|
|
71
|
+
export DRAXON_API_KEY="drx_your_api_key_here"
|
|
72
|
+
|
|
73
|
+
# Windows PowerShell
|
|
74
|
+
$env:DRAXON_API_KEY="drx_your_api_key_here"
|
|
75
|
+
|
|
76
|
+
# Windows Command Prompt
|
|
77
|
+
set DRAXON_API_KEY=drx_your_api_key_here
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
*(You can also pass it explicitly with `--api-key="drx_..."`)*
|
|
81
|
+
|
|
82
|
+
### 2. Transcribe Audio
|
|
83
|
+
|
|
84
|
+
#### Print raw text directly to terminal:
|
|
85
|
+
```bash
|
|
86
|
+
s2t --input=voice.opus
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### Output to plain text file:
|
|
90
|
+
```bash
|
|
91
|
+
s2t --input=meeting.mp3 --output=transcript.txt
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Output to formatted PDF document:
|
|
95
|
+
```bash
|
|
96
|
+
s2t --input=interview.m4a --output=interview.pdf
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Real-time streaming transcription:
|
|
100
|
+
```bash
|
|
101
|
+
s2t --input=speech.wav --stream
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## CLI Options
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
usage: s2t [-h] -i INPUT [-o OUTPUT] [--api-key API_KEY] [--api-base API_BASE]
|
|
110
|
+
[--model MODEL] [--stream]
|
|
111
|
+
|
|
112
|
+
options:
|
|
113
|
+
-h, --help show this help message and exit
|
|
114
|
+
-i INPUT, --input INPUT
|
|
115
|
+
Input audio file (.mp3, .opus, .m4a, .wav, etc.)
|
|
116
|
+
-o OUTPUT, --output OUTPUT
|
|
117
|
+
Output file (.txt or .pdf). If omitted, prints raw text to stdout.
|
|
118
|
+
--api-key API_KEY DraxonLLMs API key (default: env DRAXON_API_KEY)
|
|
119
|
+
--api-base API_BASE API base URL (default: https://ai.draxon.one/api/v1)
|
|
120
|
+
--model MODEL Model name (default: gemini-flash)
|
|
121
|
+
--stream Stream raw text from model in real time
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Supported Models
|
|
127
|
+
|
|
128
|
+
Refer to `models.txt` for the current audio model roster:
|
|
129
|
+
|
|
130
|
+
- `gemini-flash` *(default)*: High accuracy, low latency, multilingual (English, Hindi, Spanish, etc.)
|
|
131
|
+
- `gemini-pro`: Maximum reasoning and contextual transcription
|
|
132
|
+
- `qwen3.8-omni-flash`: Omni-channel audio model
|
|
133
|
+
- `mimo-v2.5-asr`: Specialized Chinese speech recognition
|
|
134
|
+
|
|
135
|
+
To select a different model:
|
|
136
|
+
```bash
|
|
137
|
+
s2t --input=audio.mp3 --model=gemini-pro
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
s2t
|
s2t_py-0.1.0/setup.cfg
ADDED