Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions RealtimeSTT/transcription_engines/faster_whisper_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,18 @@ def __init__(self, config):
Initializes the faster-whisper model.
"""
super().__init__(config)
engine_options = dict(self.config.engine_options or {})
self.transcribe_options = dict(engine_options.get("transcribe", {}))
faster_whisper, batched_inference_pipeline = _load_faster_whisper()
model = faster_whisper.WhisperModel(
model_size_or_path=self.config.model,
device=self.config.device,
compute_type=self.config.compute_type,
device_index=self.config.gpu_device_index,
download_root=self.config.download_root,
)
model_kwargs = {
"model_size_or_path": self.config.model,
"device": self.config.device,
"compute_type": self.config.compute_type,
"device_index": self.config.gpu_device_index,
"download_root": self.config.download_root,
}
model_kwargs.update(engine_options.get("model", {}))
model = faster_whisper.WhisperModel(**model_kwargs)
if self.config.batch_size > 0:
model = batched_inference_pipeline(model=model)
self.model = model
Expand Down Expand Up @@ -89,6 +93,7 @@ def transcribe(self, audio, language=None, use_prompt=True, word_timestamps=Fals
kwargs["batch_size"] = self.config.batch_size
if not self.config.vad_filter:
kwargs["clip_timestamps"] = self._batched_clip_timestamps(audio)
kwargs.update(self.transcribe_options)

segments, info = self.model.transcribe(audio, **kwargs)
segments = list(segments)
Expand Down
26 changes: 26 additions & 0 deletions docs/engines/faster-whisper.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,32 @@ CPU runs are usually more practical with small models and `compute_type="int8"`.
| `faster_whisper_vad_filter` | Passed as `vad_filter`. |
| `normalize_audio` | Normalizes audio before transcription when enabled. |

## Engine Options

Use `transcription_engine_options` (and `realtime_transcription_engine_options`
for the realtime model) to pass backend-specific options this adapter does not
map directly:

| Option bucket | Meaning |
| --- | --- |
| `transcription_engine_options["model"]` | Merged into `WhisperModel(...)` construction. |
| `transcription_engine_options["transcribe"]` | Merged into `model.transcribe(...)`, overriding the mapped defaults above. |

For example, Whisper's built-in speech translation task (any supported source
language to English) can be enabled with:

```python
recorder = AudioToTextRecorder(
model="small",
language="es",
transcription_engine_options={"transcribe": {"task": "translate"}},
)
```

Other useful `transcribe` options include `temperature`,
`condition_on_previous_text`, and `word_timestamps`; `model` options include
`cpu_threads` and `num_workers`.

## Realtime Suggestions

Use a smaller realtime model than the final model:
Expand Down
116 changes: 116 additions & 0 deletions tests/unit/test_faster_whisper_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@
from RealtimeSTT.transcription_engines.faster_whisper_engine import FasterWhisperEngine


class FakeSegment:
def __init__(self, text):
self.text = text


class FakeInfo:
language = "en"
language_probability = 0.9


class FakeWhisperModel:
def __init__(self, **kwargs):
self.kwargs = kwargs
self.calls = []

def transcribe(self, audio, **params):
self.calls.append((audio, params))
return [FakeSegment(" hello"), FakeSegment("world ")], FakeInfo()


class FakeAudio:
size = 1


class FakeWhisperModule:
loaded = []

@classmethod
def WhisperModel(cls, **kwargs):
model = FakeWhisperModel(**kwargs)
cls.loaded.append(model)
return model


def make_engine(config):
with patch(
"RealtimeSTT.transcription_engines.faster_whisper_engine._load_faster_whisper",
return_value=(FakeWhisperModule, None),
):
return FasterWhisperEngine(config)


class FasterWhisperEngineDependencyTests(unittest.TestCase):
def test_missing_dependency_mentions_extra(self):
config = TranscriptionEngineConfig(model="tiny")
Expand Down Expand Up @@ -79,5 +121,79 @@ class FakeFasterWhisper:
)


class FasterWhisperEngineOptionsTests(unittest.TestCase):
def tearDown(self):
FakeWhisperModule.loaded.clear()

def test_defaults_unchanged_without_engine_options(self):
engine = make_engine(
TranscriptionEngineConfig(model="tiny", initial_prompt="domain words")
)

result = engine.transcribe(FakeAudio(), language="en")

model = FakeWhisperModule.loaded[0]
self.assertEqual(
model.kwargs,
{
"model_size_or_path": "tiny",
"device": "cpu",
"compute_type": "default",
"device_index": 0,
"download_root": None,
},
)
self.assertEqual(
model.calls[0][1],
{
"language": "en",
"beam_size": 5,
"initial_prompt": "domain words",
"suppress_tokens": None,
"vad_filter": True,
},
)
self.assertEqual(result.text, "hello world")
self.assertEqual(result.info.language, "en")

def test_model_options_merge_into_model_init(self):
make_engine(
TranscriptionEngineConfig(
model="tiny",
engine_options={"model": {"cpu_threads": 4, "compute_type": "int8"}},
)
)

self.assertEqual(
FakeWhisperModule.loaded[0].kwargs,
{
"model_size_or_path": "tiny",
"device": "cpu",
"compute_type": "int8",
"device_index": 0,
"download_root": None,
"cpu_threads": 4,
},
)

def test_transcribe_options_merge_and_override(self):
engine = make_engine(
TranscriptionEngineConfig(
model="tiny",
engine_options={
"transcribe": {"task": "translate", "beam_size": 3},
},
)
)

engine.transcribe(FakeAudio(), language="es")

params = FakeWhisperModule.loaded[0].calls[0][1]
self.assertEqual(params["task"], "translate")
self.assertEqual(params["beam_size"], 3)
self.assertEqual(params["language"], "es")
self.assertTrue(params["vad_filter"])


if __name__ == "__main__":
unittest.main()