Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc070d6688 | |||
| 8e7e329a39 | |||
| 380f07394b | |||
| 30f78a2cc6 | |||
| 01c6bc1ecd | |||
| bdaed45820 | |||
| 4870e9fb9e | |||
| ccb183b4d8 | |||
| fac62aaccc | |||
| aade67736a | |||
| abfe830eee | |||
| cb392cbb93 | |||
| 42733da59a | |||
| 26c517021f | |||
| cf721e8b53 | |||
| 5985ec82b6 | |||
| 2f1c934ea2 | |||
| b220ccb330 | |||
| 5e3906fc7b | |||
| a8b9275013 |
@@ -36,7 +36,7 @@ python3 run_server.py --port 9090 \
|
||||
|
||||
# running with custom model
|
||||
python3 run_server.py --port 9090 \
|
||||
--backend faster_whisper
|
||||
--backend faster_whisper \
|
||||
-fw "/path/to/custom/faster/whisper/model"
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
faster-whisper==1.0.1
|
||||
torch
|
||||
torch==2.3.0
|
||||
websockets
|
||||
onnxruntime==1.16.0
|
||||
numba
|
||||
@@ -9,4 +9,5 @@ soundfile
|
||||
ffmpeg-python
|
||||
scipy
|
||||
jiwer
|
||||
evaluate
|
||||
evaluate
|
||||
numpy<2
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.5.0"
|
||||
__version__ = "0.5.1"
|
||||
|
||||
+24
-8
@@ -2,6 +2,7 @@ import os
|
||||
import shutil
|
||||
import wave
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
import pyaudio
|
||||
import threading
|
||||
@@ -28,7 +29,8 @@ class Client:
|
||||
translate=False,
|
||||
model="small",
|
||||
srt_file_path="output.srt",
|
||||
use_vad=True
|
||||
use_vad=True,
|
||||
log_transcription=True
|
||||
):
|
||||
"""
|
||||
Initializes a Client instance for audio recording and streaming to a server.
|
||||
@@ -56,11 +58,11 @@ class Client:
|
||||
self.use_vad = use_vad
|
||||
self.last_segment = None
|
||||
self.last_received_segment = None
|
||||
self.log_transcription = log_transcription
|
||||
|
||||
if translate:
|
||||
self.task = "translate"
|
||||
|
||||
self.timestamp_offset = 0.0
|
||||
self.audio_bytes = None
|
||||
|
||||
if host is not None and port is not None:
|
||||
@@ -117,10 +119,11 @@ class Client:
|
||||
self.last_response_received = time.time()
|
||||
self.last_received_segment = segments[-1]["text"]
|
||||
|
||||
# Truncate to last 3 entries for brevity.
|
||||
text = text[-3:]
|
||||
utils.clear_screen()
|
||||
utils.print_transcript(text)
|
||||
if self.log_transcription:
|
||||
# Truncate to last 3 entries for brevity.
|
||||
text = text[-3:]
|
||||
utils.clear_screen()
|
||||
utils.print_transcript(text)
|
||||
|
||||
def on_message(self, ws, message):
|
||||
"""
|
||||
@@ -431,6 +434,8 @@ class TranscriptionTeeClient:
|
||||
|
||||
def handle_ffmpeg_process(self, process, stream_type):
|
||||
print(f"[INFO]: Connecting to {stream_type} stream...")
|
||||
stderr_thread = threading.Thread(target=self.consume_stderr, args=(process,))
|
||||
stderr_thread.start()
|
||||
try:
|
||||
# Process the stream
|
||||
while True:
|
||||
@@ -477,6 +482,16 @@ class TranscriptionTeeClient:
|
||||
|
||||
return process
|
||||
|
||||
def consume_stderr(self, process):
|
||||
"""
|
||||
Consume and log the stderr output of a process in a separate thread.
|
||||
|
||||
Args:
|
||||
process (subprocess.Popen): The process whose stderr output will be logged.
|
||||
"""
|
||||
for line in iter(process.stderr.readline, b""):
|
||||
logging.debug(f'[STDERR]: {line.decode()}')
|
||||
|
||||
def save_chunk(self, n_audio_file):
|
||||
"""
|
||||
Saves the current audio frames to a WAV file in a separate thread.
|
||||
@@ -664,9 +679,10 @@ class TranscriptionClient(TranscriptionTeeClient):
|
||||
use_vad=True,
|
||||
save_output_recording=False,
|
||||
output_recording_filename="./output_recording.wav",
|
||||
output_transcription_path="./output.srt"
|
||||
output_transcription_path="./output.srt",
|
||||
log_transcription=True,
|
||||
):
|
||||
self.client = Client(host, port, lang, translate, model, srt_file_path=output_transcription_path, use_vad=use_vad)
|
||||
self.client = Client(host, port, lang, translate, model, srt_file_path=output_transcription_path, use_vad=use_vad, log_transcription=log_transcription)
|
||||
if save_output_recording and not output_recording_filename.endswith(".wav"):
|
||||
raise ValueError(f"Please provide a valid `output_recording_filename`: {output_recording_filename}")
|
||||
if not output_transcription_path.endswith(".srt"):
|
||||
|
||||
+55
-15
@@ -4,6 +4,9 @@ import threading
|
||||
import json
|
||||
import functools
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
from websockets.sync.server import serve
|
||||
@@ -121,6 +124,25 @@ class ClientManager:
|
||||
return False
|
||||
|
||||
|
||||
class BackendType(Enum):
|
||||
FASTER_WHISPER = "faster_whisper"
|
||||
TENSORRT = "tensorrt"
|
||||
|
||||
@staticmethod
|
||||
def valid_types() -> List[str]:
|
||||
return [backend_type.value for backend_type in BackendType]
|
||||
|
||||
@staticmethod
|
||||
def is_valid(backend: str) -> bool:
|
||||
return backend in BackendType.valid_types()
|
||||
|
||||
def is_faster_whisper(self) -> bool:
|
||||
return self == BackendType.FASTER_WHISPER
|
||||
|
||||
def is_tensorrt(self) -> bool:
|
||||
return self == BackendType.TENSORRT
|
||||
|
||||
|
||||
class TranscriptionServer:
|
||||
RATE = 16000
|
||||
|
||||
@@ -134,7 +156,9 @@ class TranscriptionServer:
|
||||
self, websocket, options, faster_whisper_custom_model_path,
|
||||
whisper_tensorrt_path, trt_multilingual
|
||||
):
|
||||
if self.backend == "tensorrt":
|
||||
client: Optional[ServeClientBase] = None
|
||||
|
||||
if self.backend.is_tensorrt():
|
||||
try:
|
||||
client = ServeClientTensorRT(
|
||||
websocket,
|
||||
@@ -155,9 +179,9 @@ class TranscriptionServer:
|
||||
"message": "TensorRT-LLM not supported on Server yet. "
|
||||
"Reverting to available backend: 'faster_whisper'"
|
||||
}))
|
||||
self.backend = "faster_whisper"
|
||||
self.backend = BackendType.FASTER_WHISPER
|
||||
|
||||
if self.backend == "faster_whisper":
|
||||
if self.backend.is_faster_whisper():
|
||||
if faster_whisper_custom_model_path is not None and os.path.exists(faster_whisper_custom_model_path):
|
||||
logging.info(f"Using custom model {faster_whisper_custom_model_path}")
|
||||
options["model"] = faster_whisper_custom_model_path
|
||||
@@ -174,6 +198,9 @@ class TranscriptionServer:
|
||||
)
|
||||
logging.info("Running faster_whisper backend.")
|
||||
|
||||
if client is None:
|
||||
raise ValueError(f"Backend type {self.backend.value} not recognised or not handled.")
|
||||
|
||||
self.client_manager.add_client(websocket, client)
|
||||
|
||||
def get_audio_from_websocket(self, websocket):
|
||||
@@ -202,7 +229,7 @@ class TranscriptionServer:
|
||||
websocket.close()
|
||||
return False # Indicates that the connection should not continue
|
||||
|
||||
if self.backend == "tensorrt":
|
||||
if self.backend.is_tensorrt():
|
||||
self.vad_detector = VoiceActivityDetector(frame_rate=self.RATE)
|
||||
self.initialize_client(websocket, options, faster_whisper_custom_model_path,
|
||||
whisper_tensorrt_path, trt_multilingual)
|
||||
@@ -221,11 +248,11 @@ class TranscriptionServer:
|
||||
frame_np = self.get_audio_from_websocket(websocket)
|
||||
client = self.client_manager.get_client(websocket)
|
||||
if frame_np is False:
|
||||
if self.backend == "tensorrt":
|
||||
if self.backend.is_tensorrt():
|
||||
client.set_eos(True)
|
||||
return False
|
||||
|
||||
if self.backend == "tensorrt":
|
||||
if self.backend.is_tensorrt():
|
||||
voice_active = self.voice_activity(websocket, frame_np)
|
||||
if voice_active:
|
||||
self.no_voice_activity_chunks = 0
|
||||
@@ -238,7 +265,7 @@ class TranscriptionServer:
|
||||
|
||||
def recv_audio(self,
|
||||
websocket,
|
||||
backend="faster_whisper",
|
||||
backend: BackendType = BackendType.FASTER_WHISPER,
|
||||
faster_whisper_custom_model_path=None,
|
||||
whisper_tensorrt_path=None,
|
||||
trt_multilingual=False):
|
||||
@@ -311,10 +338,12 @@ class TranscriptionServer:
|
||||
# TODO: load model initially
|
||||
else:
|
||||
logging.info("Single model mode currently only works with custom models.")
|
||||
if not BackendType.is_valid(backend):
|
||||
raise ValueError(f"{backend} is not a valid backend type. Choose backend from {BackendType.valid_types()}")
|
||||
with serve(
|
||||
functools.partial(
|
||||
self.recv_audio,
|
||||
backend=backend,
|
||||
backend=BackendType(backend),
|
||||
faster_whisper_custom_model_path=faster_whisper_custom_model_path,
|
||||
whisper_tensorrt_path=whisper_tensorrt_path,
|
||||
trt_multilingual=trt_multilingual
|
||||
@@ -758,9 +787,15 @@ class ServeClientFasterWhisper(ServeClientBase):
|
||||
self.no_speech_thresh = 0.45
|
||||
|
||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
if device == "cuda":
|
||||
major, _ = torch.cuda.get_device_capability(device)
|
||||
self.compute_type = "float16" if major >= 7 else "float32"
|
||||
else:
|
||||
self.compute_type = "int8"
|
||||
|
||||
if self.model_size_or_path is None:
|
||||
return
|
||||
logging.info(f"Using Device={device} with precision {self.compute_type}")
|
||||
|
||||
if single_model:
|
||||
if ServeClientFasterWhisper.SINGLE_MODEL is None:
|
||||
@@ -793,7 +828,7 @@ class ServeClientFasterWhisper(ServeClientBase):
|
||||
self.transcriber = WhisperModel(
|
||||
self.model_size_or_path,
|
||||
device=device,
|
||||
compute_type="int8" if device == "cpu" else "float16",
|
||||
compute_type=self.compute_type,
|
||||
local_files_only=False,
|
||||
)
|
||||
|
||||
@@ -944,6 +979,7 @@ class ServeClientFasterWhisper(ServeClientBase):
|
||||
|
||||
input_bytes, duration = self.get_audio_chunk_for_processing()
|
||||
if duration < 1.0:
|
||||
time.sleep(0.1) # wait for audio chunks to arrive
|
||||
continue
|
||||
try:
|
||||
input_sample = input_bytes.copy()
|
||||
@@ -1002,6 +1038,8 @@ class ServeClientFasterWhisper(ServeClientBase):
|
||||
"""
|
||||
offset = None
|
||||
self.current_out = ''
|
||||
last_segment = None
|
||||
|
||||
# process complete segments
|
||||
if len(segments) > 1:
|
||||
for i, s in enumerate(segments[:-1]):
|
||||
@@ -1017,12 +1055,14 @@ class ServeClientFasterWhisper(ServeClientBase):
|
||||
self.transcript.append(self.format_segment(start, end, text_))
|
||||
offset = min(duration, s.end)
|
||||
|
||||
self.current_out += segments[-1].text
|
||||
last_segment = self.format_segment(
|
||||
self.timestamp_offset + segments[-1].start,
|
||||
self.timestamp_offset + min(duration, segments[-1].end),
|
||||
self.current_out
|
||||
)
|
||||
# only process the segments if it satisfies the no_speech_thresh
|
||||
if segments[-1].no_speech_prob <= self.no_speech_thresh:
|
||||
self.current_out += segments[-1].text
|
||||
last_segment = self.format_segment(
|
||||
self.timestamp_offset + segments[-1].start,
|
||||
self.timestamp_offset + min(duration, segments[-1].end),
|
||||
self.current_out
|
||||
)
|
||||
|
||||
# if same incomplete segment is seen multiple times then update the offset
|
||||
# and append the segment to the list
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ class VoiceActivityDetection():
|
||||
return stacked.cpu()
|
||||
|
||||
@staticmethod
|
||||
def download(model_url="https://github.com/snakers4/silero-vad/raw/master/files/silero_vad.onnx"):
|
||||
def download(model_url="https://github.com/snakers4/silero-vad/raw/v4.0/files/silero_vad.onnx"):
|
||||
target_dir = os.path.expanduser("~/.cache/whisper-live/")
|
||||
|
||||
# Ensure the target directory exists
|
||||
|
||||
Reference in New Issue
Block a user