generic-audio-transcriber 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,6 @@
1
+ """Drop-in speech-to-text for any Python project."""
2
+
3
+ from .result import Segment, TranscriptionResult
4
+ from .transcriber import TranscriptionError, transcribe
5
+
6
+ __all__ = ["transcribe", "TranscriptionResult", "Segment", "TranscriptionError"]
@@ -0,0 +1,32 @@
1
+ """Lazy, cached loading of the speech recognition model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ from typing import Any, Dict, Tuple
7
+
8
+ _cache: Dict[Tuple[str, str, str], Any] = {}
9
+ _lock = threading.Lock()
10
+
11
+
12
+ def get_model(name: str, device: str, compute_type: str) -> Any:
13
+ """Return a loaded model, loading (and downloading) it only the first time.
14
+
15
+ ``faster_whisper`` is imported here rather than at module level so that
16
+ importing this package stays instant and tests can run without it.
17
+ """
18
+ key = (name, device, compute_type)
19
+ with _lock:
20
+ model = _cache.get(key)
21
+ if model is None:
22
+ from faster_whisper import WhisperModel
23
+
24
+ model = WhisperModel(name, device=device, compute_type=compute_type)
25
+ _cache[key] = model
26
+ return model
27
+
28
+
29
+ def clear_cache() -> None:
30
+ """Drop every loaded model so its memory can be reclaimed."""
31
+ with _lock:
32
+ _cache.clear()
@@ -0,0 +1,39 @@
1
+ """Command line entry point: ``audio-transcriber FILE`` prints JSON."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from typing import List, Optional
8
+
9
+ from .transcriber import DEFAULT_MODEL, TranscriptionError, transcribe
10
+
11
+
12
+ def build_parser() -> argparse.ArgumentParser:
13
+ parser = argparse.ArgumentParser(
14
+ prog="audio-transcriber",
15
+ description="Transcribe an audio file to text and print the result as JSON.",
16
+ )
17
+ parser.add_argument("audio", help="Path to an audio file, or '-' to read bytes from stdin.")
18
+ parser.add_argument("--model", default=DEFAULT_MODEL, help=f"Model size (default: {DEFAULT_MODEL}).")
19
+ parser.add_argument("--language", default=None, help="Language code such as 'pt'. Auto-detected if omitted.")
20
+ parser.add_argument("--text", action="store_true", help="Print only the plain text instead of JSON.")
21
+ return parser
22
+
23
+
24
+ def main(argv: Optional[List[str]] = None) -> int:
25
+ args = build_parser().parse_args(argv)
26
+ audio = sys.stdin.buffer.read() if args.audio == "-" else args.audio
27
+
28
+ try:
29
+ result = transcribe(audio, model=args.model, language=args.language)
30
+ except TranscriptionError as exc:
31
+ print(f"error: {exc}", file=sys.stderr)
32
+ return 1
33
+
34
+ print(result.text if args.text else result.to_json(indent=2))
35
+ return 0
36
+
37
+
38
+ if __name__ == "__main__":
39
+ sys.exit(main())
File without changes
@@ -0,0 +1,36 @@
1
+ """Plain data types returned by :func:`audio_transcriber.transcribe`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import asdict, dataclass, field
7
+ from typing import Any, Dict, Tuple
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class Segment:
12
+ """A timed piece of the transcript. Times are in seconds."""
13
+
14
+ start: float
15
+ end: float
16
+ text: str
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class TranscriptionResult:
21
+ """The outcome of a transcription, easy to turn into JSON."""
22
+
23
+ text: str
24
+ language: str
25
+ language_probability: float
26
+ duration: float
27
+ segments: Tuple[Segment, ...] = field(default_factory=tuple)
28
+
29
+ def to_dict(self) -> Dict[str, Any]:
30
+ data = asdict(self)
31
+ data["segments"] = [asdict(s) for s in self.segments]
32
+ return data
33
+
34
+ def to_json(self, **kwargs: Any) -> str:
35
+ kwargs.setdefault("ensure_ascii", False)
36
+ return json.dumps(self.to_dict(), **kwargs)
@@ -0,0 +1,82 @@
1
+ """The public ``transcribe`` function."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import os
7
+ from typing import BinaryIO, Optional, Union
8
+
9
+ from av.error import FFmpegError
10
+
11
+ from . import _model
12
+ from .result import Segment, TranscriptionResult
13
+
14
+ AudioInput = Union[bytes, bytearray, memoryview, str, "os.PathLike[str]", BinaryIO]
15
+
16
+ DEFAULT_MODEL = "small"
17
+
18
+
19
+ class TranscriptionError(Exception):
20
+ """Raised when the audio is empty or cannot be decoded."""
21
+
22
+
23
+ def _prepare_audio(audio: AudioInput) -> Union[str, BinaryIO]:
24
+ if isinstance(audio, (bytes, bytearray, memoryview)):
25
+ data = bytes(audio)
26
+ if not data:
27
+ raise TranscriptionError("Audio is empty.")
28
+ return io.BytesIO(data)
29
+ if isinstance(audio, (str, os.PathLike)):
30
+ return os.fspath(audio)
31
+ return audio
32
+
33
+
34
+ def transcribe(
35
+ audio: AudioInput,
36
+ *,
37
+ model: str = DEFAULT_MODEL,
38
+ language: Optional[str] = None,
39
+ device: str = "cpu",
40
+ compute_type: str = "int8",
41
+ beam_size: int = 5,
42
+ vad_filter: bool = True,
43
+ ) -> TranscriptionResult:
44
+ """Convert speech to text.
45
+
46
+ Args:
47
+ audio: Raw audio bytes (mp3, wav, webm, ogg, m4a, ...), a file path,
48
+ or a binary file-like object. The format is detected automatically.
49
+ model: Model size or name: ``tiny``, ``base``, ``small``, ``medium``,
50
+ ``large-v3``, ... The model is downloaded on first use and cached.
51
+ language: ISO code such as ``"pt"`` or ``"en"``. ``None`` detects it.
52
+ device: ``"cpu"`` (works everywhere), ``"cuda"`` or ``"auto"``.
53
+ compute_type: Quantization, e.g. ``"int8"`` (light) or ``"float16"``.
54
+ beam_size: Higher is slightly more accurate and slower.
55
+ vad_filter: Skip silent parts, which also reduces hallucinated text.
56
+
57
+ Raises:
58
+ TranscriptionError: If the audio is empty or cannot be decoded.
59
+ """
60
+ source = _prepare_audio(audio)
61
+ whisper = _model.get_model(model, device, compute_type)
62
+
63
+ try:
64
+ raw_segments, info = whisper.transcribe(
65
+ source,
66
+ language=language,
67
+ beam_size=beam_size,
68
+ vad_filter=vad_filter,
69
+ )
70
+ segments = tuple(
71
+ Segment(start=s.start, end=s.end, text=s.text.strip()) for s in raw_segments
72
+ )
73
+ except FFmpegError as exc:
74
+ raise TranscriptionError(f"Could not decode audio: {exc}") from exc
75
+
76
+ return TranscriptionResult(
77
+ text=" ".join(s.text for s in segments if s.text),
78
+ language=info.language,
79
+ language_probability=info.language_probability,
80
+ duration=info.duration,
81
+ segments=segments,
82
+ )
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: generic-audio-transcriber
3
+ Version: 0.1.0
4
+ Summary: Drop-in speech-to-text for any Python project: audio bytes in, JSON-ready text out.
5
+ Author: Eduardo Milani
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/EduardoMilani8/generic-audio-transcriber
8
+ Keywords: speech-to-text,transcription,whisper,audio,asr
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: faster-whisper>=1.0
16
+ Requires-Dist: av<19,>=11
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # generic-audio-transcriber
22
+
23
+ Drop-in speech-to-text for any Python project. Audio bytes in, JSON-ready text out.
24
+
25
+ ```python
26
+ from audio_transcriber import transcribe
27
+
28
+ result = transcribe(audio_bytes) # mp3, wav, webm, ogg, m4a... detected automatically
29
+ print(result.text) # "Hello, this is a test."
30
+ print(result.to_json()) # {"text": "...", "language": "en", ...}
31
+ ```
32
+
33
+ No API keys, no servers, no cloud. It runs locally on the CPU, and the model is
34
+ downloaded automatically the first time you use it.
35
+
36
+ ## Why
37
+
38
+ Many apps need the same small feature: the user sends or records audio, and the app
39
+ needs the text. This package is that feature as one function call, so you can add it to
40
+ a project without learning anything about speech recognition.
41
+
42
+ Under the hood it uses [faster-whisper](https://github.com/SYSTRAN/faster-whisper)
43
+ (OpenAI's Whisper model), which is accurate, supports about 99 languages, and runs well
44
+ on a plain CPU. You never have to train or tune a model.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install git+https://github.com/EduardoMilani8/generic-audio-transcriber.git
50
+ ```
51
+
52
+ Requires Python 3.9+. You do **not** need to install ffmpeg: audio decoding is bundled.
53
+
54
+ ## Usage
55
+
56
+ ### From bytes (the main use case)
57
+
58
+ ```python
59
+ from audio_transcriber import transcribe
60
+
61
+ result = transcribe(audio_bytes)
62
+ ```
63
+
64
+ ### From a file path or file object
65
+
66
+ ```python
67
+ transcribe("recording.mp3")
68
+
69
+ with open("recording.wav", "rb") as f:
70
+ transcribe(f)
71
+ ```
72
+
73
+ ### Inside a web endpoint
74
+
75
+ ```python
76
+ from fastapi import FastAPI, UploadFile
77
+ from audio_transcriber import transcribe
78
+
79
+ app = FastAPI()
80
+
81
+ @app.post("/transcribe")
82
+ async def endpoint(file: UploadFile):
83
+ return transcribe(await file.read()).to_dict()
84
+ ```
85
+
86
+ ### The result
87
+
88
+ ```python
89
+ result.text # full transcript
90
+ result.language # detected language code, e.g. "pt"
91
+ result.language_probability # confidence of the language detection
92
+ result.duration # audio length in seconds
93
+ result.segments # timed pieces: .start, .end, .text
94
+ result.to_dict() # plain dict
95
+ result.to_json() # JSON string (non-ASCII kept readable)
96
+ ```
97
+
98
+ Silent audio gives an empty `text`, not an error.
99
+
100
+ ## Options
101
+
102
+ ```python
103
+ transcribe(
104
+ audio,
105
+ model="small", # tiny | base | small | medium | large-v3
106
+ language=None, # "pt", "en", ... None = auto-detect
107
+ device="cpu", # "cpu" | "cuda" | "auto"
108
+ compute_type="int8", # "int8" is light; "float16" suits GPUs
109
+ beam_size=5,
110
+ vad_filter=True, # skip silence, reduces made-up text
111
+ )
112
+ ```
113
+
114
+ Choosing a model is a trade-off between weight and accuracy:
115
+
116
+ | Model | Size on disk | Speed | Accuracy |
117
+ |---|---|---|---|
118
+ | `tiny` | smallest | fastest | lowest |
119
+ | `base` | small | fast | fair |
120
+ | `small` (default) | medium | good | good |
121
+ | `medium` / `large-v3` | large | slow on CPU | best |
122
+
123
+ The model is loaded once per process and reused, so only the first call is slow.
124
+ Setting `language` explicitly is faster and more accurate than auto-detection.
125
+
126
+ ## Errors
127
+
128
+ ```python
129
+ from audio_transcriber import transcribe, TranscriptionError
130
+
131
+ try:
132
+ transcribe(data)
133
+ except TranscriptionError as e:
134
+ ... # empty input or audio that cannot be decoded
135
+ ```
136
+
137
+ ## Command line
138
+
139
+ ```bash
140
+ audio-transcriber recording.mp3 # prints JSON
141
+ audio-transcriber recording.mp3 --text # prints plain text
142
+ audio-transcriber recording.mp3 --model base --language pt
143
+ cat recording.mp3 | audio-transcriber - # read bytes from stdin
144
+ ```
145
+
146
+ ## Development
147
+
148
+ ```bash
149
+ python -m venv .venv && source .venv/bin/activate
150
+ pip install -e ".[dev]"
151
+ pytest # fast unit tests, no model needed
152
+ RUN_SLOW=1 pytest -m slow # end-to-end tests, downloads the tiny model
153
+ ```
154
+
155
+ ## Notes
156
+
157
+ - The default device is `cpu` because it works on every machine. Pass `device="cuda"`
158
+ if you have a working CUDA setup and want GPU speed.
159
+ - `av` is pinned below version 19 because faster-whisper still uses an argument that
160
+ newer releases removed. The pin can be lifted once faster-whisper fixes it.
161
+
162
+ ## License
163
+
164
+ MIT
@@ -0,0 +1,12 @@
1
+ audio_transcriber/__init__.py,sha256=csz215f2487u2IV6QZlleCPHdBO8UxR1g_WfsCjq2nQ,241
2
+ audio_transcriber/_model.py,sha256=D0eoBLKMuvWF0FCbVLuoaBj-pIDMBmU-mRD2M2Xa09o,958
3
+ audio_transcriber/cli.py,sha256=sdPMxvx-Xzv4DIQPJYGHcxJUf1Z7Uy6YrJUas2j1r3g,1385
4
+ audio_transcriber/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ audio_transcriber/result.py,sha256=ljMlkIA-nN3d4ya9GcnDkPLD-z-LHMMHZeTN1_q8zbs,928
6
+ audio_transcriber/transcriber.py,sha256=WPnkyVSyK6h0ECNDOUDR5vFIHCPkA-RqqorLYHAngpE,2646
7
+ generic_audio_transcriber-0.1.0.dist-info/licenses/LICENSE,sha256=kAmtq-Jj2Hm1y9c4SDWwgaWyptnE6aOXUpZ8tyGfpa0,1071
8
+ generic_audio_transcriber-0.1.0.dist-info/METADATA,sha256=4tKHk8w9KQ_xpLW6qGYP2xidgcObx8M_MLA-TpKi0S0,4857
9
+ generic_audio_transcriber-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
10
+ generic_audio_transcriber-0.1.0.dist-info/entry_points.txt,sha256=3KxjmWj9RBiMgOhhq0NRbxZmlJLSyUTLi42Vw9husOE,65
11
+ generic_audio_transcriber-0.1.0.dist-info/top_level.txt,sha256=1VoY9Hc8Rydt3ZgX1wBf0SeukQk-X2yjSr5nCAwAjm0,18
12
+ generic_audio_transcriber-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ audio-transcriber = audio_transcriber.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eduardo Milani
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.
@@ -0,0 +1 @@
1
+ audio_transcriber