🔨 refactor whisper_live according to flake8
This commit is contained in:
+57
-124
@@ -2,68 +2,14 @@ import os
|
||||
import wave
|
||||
|
||||
import numpy as np
|
||||
import scipy
|
||||
import ffmpeg
|
||||
import pyaudio
|
||||
import threading
|
||||
import textwrap
|
||||
import json
|
||||
import websocket
|
||||
import uuid
|
||||
import time
|
||||
|
||||
|
||||
def format_time(s):
|
||||
"""Convert seconds (float) to SRT time format."""
|
||||
hours = int(s // 3600)
|
||||
minutes = int((s % 3600) // 60)
|
||||
seconds = int(s % 60)
|
||||
milliseconds = int((s - int(s)) * 1000)
|
||||
return f"{hours:02}:{minutes:02}:{seconds:02},{milliseconds:03}"
|
||||
|
||||
def create_srt_file(segments, output_file):
|
||||
with open(output_file, 'w', encoding='utf-8') as srt_file:
|
||||
segment_number = 1
|
||||
for segment in segments:
|
||||
start_time = format_time(float(segment['start']))
|
||||
end_time = format_time(float(segment['end']))
|
||||
text = segment['text']
|
||||
|
||||
srt_file.write(f"{segment_number}\n")
|
||||
srt_file.write(f"{start_time} --> {end_time}\n")
|
||||
srt_file.write(f"{text}\n\n")
|
||||
|
||||
segment_number += 1
|
||||
|
||||
|
||||
def resample(file: str, sr: int = 16000):
|
||||
"""
|
||||
# https://github.com/openai/whisper/blob/7858aa9c08d98f75575035ecd6481f462d66ca27/whisper/audio.py#L22
|
||||
Open an audio file and read as mono waveform, resampling as necessary,
|
||||
save the resampled audio
|
||||
|
||||
Args:
|
||||
file (str): The audio file to open
|
||||
sr (int): The sample rate to resample the audio if necessary
|
||||
|
||||
Returns:
|
||||
resampled_file (str): The resampled audio file
|
||||
"""
|
||||
try:
|
||||
# This launches a subprocess to decode audio while down-mixing and resampling as necessary.
|
||||
# Requires the ffmpeg CLI and `ffmpeg-python` package to be installed.
|
||||
out, _ = (
|
||||
ffmpeg.input(file, threads=0)
|
||||
.output("-", format="s16le", acodec="pcm_s16le", ac=1, ar=sr)
|
||||
.run(cmd=["ffmpeg", "-nostdin"], capture_stdout=True, capture_stderr=True)
|
||||
)
|
||||
except ffmpeg.Error as e:
|
||||
raise RuntimeError(f"Failed to load audio: {e.stderr.decode()}") from e
|
||||
np_buffer = np.frombuffer(out, dtype=np.int16)
|
||||
|
||||
resampled_file = f"{file.split('.')[0]}_resampled.wav"
|
||||
scipy.io.wavfile.write(resampled_file, sr, np_buffer.astype(np.int16))
|
||||
return resampled_file
|
||||
import ffmpeg
|
||||
import whisper_live.utils as utils
|
||||
|
||||
|
||||
class Client:
|
||||
@@ -150,10 +96,40 @@ class Client:
|
||||
self.transcript = []
|
||||
print("[INFO]: * recording")
|
||||
|
||||
def handle_status_messages(self, message_data):
|
||||
"""Handles server status messages."""
|
||||
status = message_data["status"]
|
||||
if status == "WAIT":
|
||||
self.waiting = True
|
||||
print(f"[INFO]: Server is full. Estimated wait time {round(message_data['message'])} minutes.")
|
||||
elif status == "ERROR":
|
||||
print(f"Message from Server: {message_data['message']}")
|
||||
self.server_error = True
|
||||
elif status == "WARNING":
|
||||
print(f"Message from Server: {message_data['message']}")
|
||||
|
||||
def process_segments(self, segments):
|
||||
"""Processes transcript segments."""
|
||||
text = []
|
||||
for i, seg in enumerate(segments):
|
||||
if not text or text[-1] != seg["text"]:
|
||||
text.append(seg["text"])
|
||||
if i == len(segments) - 1:
|
||||
self.last_segment = seg
|
||||
elif (self.server_backend == "faster_whisper" and
|
||||
(not self.transcript or
|
||||
float(seg['start']) >= float(self.transcript[-1]['end']))):
|
||||
self.transcript.append(seg)
|
||||
|
||||
# Truncate to last 3 entries for brevity.
|
||||
text = text[-3:]
|
||||
# utils.clear_screen()
|
||||
utils.print_transcript(text)
|
||||
|
||||
def on_message(self, ws, message):
|
||||
"""
|
||||
Callback function called when a message is received from the server.
|
||||
|
||||
|
||||
It updates various attributes of the client based on the received message, including
|
||||
recording status, language detection, and server messages. If a disconnect message
|
||||
is received, it sets the recording status to False.
|
||||
@@ -171,14 +147,7 @@ class Client:
|
||||
return
|
||||
|
||||
if "status" in message.keys():
|
||||
if message["status"] == "WAIT":
|
||||
self.waiting = True
|
||||
print(
|
||||
f"[INFO]:Server is full. Estimated wait time {round(message['message'])} minutes."
|
||||
)
|
||||
elif message["status"] == "ERROR":
|
||||
print(f"Message from Server: {message['message']}")
|
||||
self.server_error = True
|
||||
self.handle_status_messages(message)
|
||||
return
|
||||
|
||||
if "message" in message.keys() and message["message"] == "DISCONNECT":
|
||||
@@ -199,38 +168,8 @@ class Client:
|
||||
)
|
||||
return
|
||||
|
||||
if "segments" not in message.keys():
|
||||
return
|
||||
|
||||
message = message["segments"]
|
||||
text = []
|
||||
n_segments = len(message)
|
||||
|
||||
if n_segments:
|
||||
for i, seg in enumerate(message):
|
||||
if text and text[-1] == seg["text"]:
|
||||
# already got it
|
||||
continue
|
||||
text.append(seg["text"])
|
||||
|
||||
if i == n_segments-1:
|
||||
self.last_segment = seg
|
||||
elif self.server_backend == "faster_whisper":
|
||||
if not len(self.transcript) or float(seg['start']) >= float(self.transcript[-1]['end']):
|
||||
self.transcript.append(seg)
|
||||
|
||||
# keep only last 3
|
||||
if len(text) > 3:
|
||||
text = text[-3:]
|
||||
wrapper = textwrap.TextWrapper(width=60)
|
||||
word_list = wrapper.wrap(text="".join(text))
|
||||
# Print each line.
|
||||
if os.name == "nt":
|
||||
os.system("cls")
|
||||
else:
|
||||
os.system("clear")
|
||||
for element in word_list:
|
||||
print(element)
|
||||
if "segments" in message.keys():
|
||||
self.process_segments(message["segments"])
|
||||
|
||||
def on_error(self, ws, error):
|
||||
print(f"[ERROR] WebSocket Error: {error}")
|
||||
@@ -246,7 +185,7 @@ class Client:
|
||||
def on_open(self, ws):
|
||||
"""
|
||||
Callback function called when the WebSocket connection is successfully opened.
|
||||
|
||||
|
||||
Sends an initial configuration message to the server, including client UID,
|
||||
language selection, and task type.
|
||||
|
||||
@@ -270,8 +209,8 @@ class Client:
|
||||
def bytes_to_float_array(audio_bytes):
|
||||
"""
|
||||
Convert audio data from bytes to a NumPy float array.
|
||||
|
||||
It assumes that the audio data is in 16-bit PCM format. The audio data is normalized to
|
||||
|
||||
It assumes that the audio data is in 16-bit PCM format. The audio data is normalized to
|
||||
have values between -1 and 1.
|
||||
|
||||
Args:
|
||||
@@ -299,10 +238,10 @@ class Client:
|
||||
def play_file(self, filename):
|
||||
"""
|
||||
Play an audio file and send it to the server for processing.
|
||||
|
||||
|
||||
Reads an audio file, plays it through the audio output, and simultaneously sends
|
||||
the audio data to the server for processing. It uses PyAudio to create an audio
|
||||
stream for playback. The audio data is read from the file in chunks, converted to
|
||||
the audio data to the server for processing. It uses PyAudio to create an audio
|
||||
stream for playback. The audio data is read from the file in chunks, converted to
|
||||
floating-point format, and sent to the server using WebSocket communication.
|
||||
This method is typically used when you want to process pre-recorded audio and send it
|
||||
to the server in real-time.
|
||||
@@ -310,7 +249,7 @@ class Client:
|
||||
Args:
|
||||
filename (str): The path to the audio file to be played and sent to the server.
|
||||
"""
|
||||
|
||||
|
||||
# read audio and create pyaudio stream
|
||||
with wave.open(filename, "rb") as wavfile:
|
||||
self.stream = self.p.open(
|
||||
@@ -356,7 +295,7 @@ class Client:
|
||||
"""
|
||||
Close the WebSocket connection and join the WebSocket thread.
|
||||
|
||||
First attempts to close the WebSocket connection using `self.client_socket.close()`. After
|
||||
First attempts to close the WebSocket connection using `self.client_socket.close()`. After
|
||||
closing the connection, it joins the WebSocket thread to ensure proper termination.
|
||||
|
||||
"""
|
||||
@@ -383,7 +322,7 @@ class Client:
|
||||
"""
|
||||
Write audio frames to a WAV file.
|
||||
|
||||
The WAV file is created or overwritten with the specified name. The audio frames should be
|
||||
The WAV file is created or overwritten with the specified name. The audio frames should be
|
||||
in the correct format and match the specified channel, sample width, and sample rate.
|
||||
|
||||
Args:
|
||||
@@ -433,7 +372,6 @@ class Client:
|
||||
|
||||
print("[INFO]: HLS stream processing finished.")
|
||||
|
||||
|
||||
def record(self, out_file="output_recording.wav"):
|
||||
"""
|
||||
Record audio data from the input stream and save it to a WAV file.
|
||||
@@ -444,11 +382,12 @@ class Client:
|
||||
|
||||
Audio data is saved in chunks to the "chunks" directory. Each chunk is saved as a separate WAV file.
|
||||
The recording will continue until the specified duration is reached or until the `RECORDING` flag is set to `False`.
|
||||
The recording process can be interrupted by sending a KeyboardInterrupt (e.g., pressing Ctrl+C). After recording,
|
||||
The recording process can be interrupted by sending a KeyboardInterrupt (e.g., pressing Ctrl+C). After recording,
|
||||
the method combines all the saved audio chunks into the specified `out_file`.
|
||||
|
||||
Args:
|
||||
out_file (str, optional): The name of the output WAV file to save the entire recording. Default is "output_recording.wav".
|
||||
out_file (str, optional): The name of the output WAV file to save the entire recording.
|
||||
Default is "output_recording.wav".
|
||||
|
||||
"""
|
||||
n_audio_file = 0
|
||||
@@ -458,7 +397,7 @@ class Client:
|
||||
for _ in range(0, int(self.rate / self.chunk * self.record_seconds)):
|
||||
if not self.recording:
|
||||
break
|
||||
data = self.stream.read(self.chunk, exception_on_overflow = False)
|
||||
data = self.stream.read(self.chunk, exception_on_overflow=False)
|
||||
self.frames += data
|
||||
|
||||
audio_array = Client.bytes_to_float_array(data)
|
||||
@@ -498,8 +437,8 @@ class Client:
|
||||
def write_output_recording(self, n_audio_file, out_file):
|
||||
"""
|
||||
Combine and save recorded audio chunks into a single WAV file.
|
||||
|
||||
The individual audio chunk files are expected to be located in the "chunks" directory. Reads each chunk
|
||||
|
||||
The individual audio chunk files are expected to be located in the "chunks" directory. Reads each chunk
|
||||
file, appends its audio data to the final recording, and then deletes the chunk file. After combining
|
||||
and saving, the final recording is stored in the specified `out_file`.
|
||||
|
||||
@@ -532,7 +471,7 @@ class Client:
|
||||
|
||||
def write_srt_file(self, output_path="output.srt"):
|
||||
self.transcript.append(self.last_segment)
|
||||
create_srt_file(self.transcript, output_path)
|
||||
utils.create_srt_file(self.transcript, output_path)
|
||||
|
||||
|
||||
class TranscriptionClient:
|
||||
@@ -558,13 +497,7 @@ class TranscriptionClient:
|
||||
transcription_client()
|
||||
```
|
||||
"""
|
||||
def __init__(self,
|
||||
host,
|
||||
port,
|
||||
lang=None,
|
||||
translate=False,
|
||||
model="small",
|
||||
):
|
||||
def __init__(self, host, port, lang=None, translate=False, model="small"):
|
||||
self.client = Client(host, port, lang, translate, model)
|
||||
|
||||
def __call__(self, audio=None, hls_url=None):
|
||||
@@ -572,12 +505,12 @@ class TranscriptionClient:
|
||||
Start the transcription process.
|
||||
|
||||
Initiates the transcription process by connecting to the server via a WebSocket. It waits for the server
|
||||
to be ready to receive audio data and then sends audio for transcription. If an audio file is provided, it
|
||||
to be ready to receive audio data and then sends audio for transcription. If an audio file is provided, it
|
||||
will be played and streamed to the server; otherwise, it will perform live recording.
|
||||
|
||||
Args:
|
||||
audio (str, optional): Path to an audio file for transcription. Default is None, which triggers live recording.
|
||||
|
||||
|
||||
"""
|
||||
print("[INFO]: Waiting for server ready ...")
|
||||
while not self.client.recording:
|
||||
@@ -589,7 +522,7 @@ class TranscriptionClient:
|
||||
if hls_url is not None:
|
||||
self.client.process_hls_stream(hls_url)
|
||||
elif audio is not None:
|
||||
resampled_file = resample(audio)
|
||||
resampled_file = utils.resample(audio)
|
||||
self.client.play_file(resampled_file)
|
||||
else:
|
||||
self.client.record()
|
||||
self.client.record()
|
||||
|
||||
Reference in New Issue
Block a user