From c574f5d6a600044e0f9114991e8f216f77fb6c03 Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 15 Sep 2023 11:34:18 +0530 Subject: [PATCH 01/13] document server --- whisper_live/server.py | 212 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 186 insertions(+), 26 deletions(-) diff --git a/whisper_live/server.py b/whisper_live/server.py index aa4439a..7a9d4ca 100644 --- a/whisper_live/server.py +++ b/whisper_live/server.py @@ -24,39 +24,76 @@ class TranscriptionServer: Represents a transcription server that handles incoming audio from clients. Attributes: + RATE (int): The audio sampling rate (constant) set to 16000. + vad_model (torch.Module): The voice activity detection model. + vad_threshold (float): The voice activity detection threshold. clients (dict): A dictionary to store connected clients. + websockets (dict): A dictionary to store WebSocket connections. + clients_start_time (dict): A dictionary to track client start times. + max_clients (int): Maximum allowed connected clients. + max_connection_time (int): Maximum allowed connection time in seconds. """ + RATE = 16000 + def __init__(self): - # voice activity detection model - self.vad_model, _ = torch.hub.load(repo_or_dir='snakers4/silero-vad', - model='silero_vad', - force_reload=True, - onnx=True - ) + """ + Initialize the TranscriptionServer. + """ + # Load the voice activity detection model + self.vad_model, _ = torch.hub.load( + repo_or_dir='snakers4/silero-vad', + model='silero_vad', + force_reload=True, + onnx=True + ) + + # Voice activity detection threshold self.vad_threshold = 0.4 + self.clients = {} self.websockets = {} self.clients_start_time = {} self.max_clients = 4 - self.max_connection_time = 600 # in seconds - + self.max_connection_time = 600 + def get_wait_time(self): + """ + Calculate and return the estimated wait time for clients. + + Returns: + float: The estimated wait time in minutes. + """ wait_time = None - for k,v in self.clients_start_time.items(): + + for k, v in self.clients_start_time.items(): current_client_time_remaining = self.max_connection_time - (time.time() - v) - if wait_time is None: + + if wait_time is None or current_client_time_remaining < wait_time: wait_time = current_client_time_remaining - elif current_client_time_remaining < wait_time: - wait_time = current_client_time_remaining - return wait_time/60 + + return wait_time / 60 def recv_audio(self, websocket): """ Receive audio chunks from a client in an infinite loop. + + Continuously receives audio frames from a connected client + over a WebSocket connection. It processes the audio frames using a + voice activity detection (VAD) model to determine if they contain speech + or not. If the audio frame contains speech, it is added to the client's + audio data for ASR. + If the maximum number of clients is reached, the method sends a + "WAIT" status to the client, indicating that they should wait + until a slot is available. + If a client's connection exceeds the maximum allowed time, it will + be disconnected, and the client's resources will be cleaned up. Args: websocket (WebSocket): The WebSocket connection for the client. + + Raises: + Exception: If there is an error during the audio frame processing. """ logging.info("New client connected") options = websocket.recv() @@ -66,7 +103,7 @@ class TranscriptionServer: logging.warning("Client Queue Full. Asking client to wait ...") wait_time = self.get_wait_time() response = { - "uid" : options["uid"], + "uid": options["uid"], "status": "WAIT", "message": wait_time, } @@ -74,7 +111,7 @@ class TranscriptionServer: websocket.close() del websocket return - + client = ServeClient( websocket, multilingual=options["multilingual"], @@ -82,9 +119,9 @@ class TranscriptionServer: task=options["task"], client_uid=options["uid"] ) - + self.clients[websocket] = client - self.clients_start_time[websocket] = time.time() + self.clients_start_time[websocket] = time.time() while True: try: @@ -95,10 +132,11 @@ class TranscriptionServer: speech_prob = self.vad_model(torch.from_numpy(frame_np.copy()), self.RATE).item() if speech_prob < self.vad_threshold: continue - + except Exception as e: logging.error(e) return + self.clients[websocket].add_frames(frame_np) elapsed_time = time.time() - self.clients_start_time[websocket] @@ -112,7 +150,6 @@ class TranscriptionServer: del websocket break - except Exception as e: logging.error(e) self.clients[websocket].cleanup() @@ -120,7 +157,6 @@ class TranscriptionServer: self.clients_start_time.pop(websocket) logging.info("Connection Closed.") logging.info(self.clients) - del websocket break @@ -137,11 +173,54 @@ class TranscriptionServer: class ServeClient: + """ + Attributes: + RATE (int): The audio sampling rate (constant) set to 16000. + SERVER_READY (str): A constant message indicating that the server is ready. + DISCONNECT (str): A constant message indicating that the client should disconnect. + client_uid (str): A unique identifier for the client. + data (bytes): Accumulated audio data. + frames (bytes): Accumulated audio frames. + language (str): The language for transcription. + task (str): The task type, e.g., "transcribe." + transcriber (WhisperModel): The Whisper model for speech-to-text. + timestamp_offset (float): The offset in audio timestamps. + frames_np (numpy.ndarray): NumPy array to store audio frames. + frames_offset (float): The offset in audio frames. + text (list): List of transcribed text segments. + current_out (str): The current incomplete transcription. + prev_out (str): The previous incomplete transcription. + t_start (float): Timestamp for the start of transcription. + exit (bool): A flag to exit the transcription thread. + same_output_threshold (int): Threshold for consecutive same output segments. + show_prev_out_thresh (int): Threshold for showing previous output segments. + add_pause_thresh (int): Threshold for adding a pause (blank) segment. + transcript (list): List of transcribed segments. + send_last_n_segments (int): Number of last segments to send to the client. + wrapper (textwrap.TextWrapper): Text wrapper for formatting text. + pick_previous_segments (int): Number of previous segments to include in the output. + websocket: The WebSocket connection for the client. + """ RATE = 16000 SERVER_READY = "SERVER_READY" DISCONNECT = "DISCONNECT" def __init__(self, websocket, task="transcribe", device=None, multilingual=False, language=None, client_uid=None): + """ + Initialize a ServeClient instance. + The Whisper model is initialized based on the client's language and device availability. + The transcription thread is started upon initialization. A "SERVER_READY" message is sent + to the client to indicate that the server is ready. + + Args: + websocket (WebSocket): The WebSocket connection for the client. + task (str, optional): The task type, e.g., "transcribe." Defaults to "transcribe". + device (str, optional): The device type for Whisper, "cuda" or "cpu". Defaults to None. + multilingual (bool, optional): Whether the client supports multilingual transcription. Defaults to False. + language (str, optional): The language for transcription. Defaults to None. + client_uid (str, optional): A unique identifier for the client. Defaults to None. + + """ self.client_uid = client_uid self.data = b"" self.frames = b"" @@ -188,14 +267,24 @@ class ServeClient: def fill_output(self, output): """ - Format output with current and previous complete segments - into two lines of 50 characters. + Format the current incomplete transcription output by combining it with previous complete segments. + The resulting transcription is wrapped into two lines, each containing a maximum of 50 characters. + + Details: + - This method is responsible for combining the current incomplete segment with a history of + previous complete segments to provide a coherent and visually organized transcription. + - It ensures that the combined transcription fits within two lines, with a maximum of 50 characters per line. + - Segments are concatenated in the order they exist in the list of previous segments, with the most + recent complete segment first and older segments appended as needed to maintain the character limit. + - If a 3-second pause is detected in the previous segments, any text preceding it is discarded to ensure + the transcription starts with the most recent complete content. The resulting transcription is returned + as a single string. Args: - output(str): current incomplete segment + output(str): The current incomplete transcription segment. Returns: - transcription wrapped in two lines + str: A formatted transcription wrapped in two lines. """ text = '' pick_prev = min(len(self.text), self.pick_previous_segments) @@ -209,6 +298,26 @@ class ServeClient: return wrapped def add_frames(self, frame_np): + """ + Add audio frames to the ongoing audio stream buffer. + + This method is responsible for maintaining the audio stream buffer, allowing the continuous addition + of audio frames as they are received. It also ensures that the buffer does not exceed a specified size + to prevent excessive memory usage. + + Details: + - The method appends incoming audio frames to the ongoing audio stream buffer. + - If the buffer size exceeds a threshold (45 seconds of audio data), it discards the oldest 30 seconds + of audio data to maintain a reasonable buffer size. + - If the buffer is empty, it initializes it with the provided audio frame. + - The audio stream buffer is used for real-time processing of audio data for transcription. + + Args: + frame_np (numpy.ndarray): The audio frame data as a NumPy array. + + Returns: + None + """ if self.frames_np is not None and self.frames_np.shape[0] > 45*self.RATE: self.frames_offset += 30.0 self.frames_np = self.frames_np[int(30*self.RATE):] @@ -219,7 +328,24 @@ class ServeClient: def speech_to_text(self): """ - Process audio stream in an infinite loop. + Process an audio stream in an infinite loop, continuously transcribing the speech. + + This method continuously receives audio frames, performs real-time transcription, and sends + transcribed segments to the client via a WebSocket connection. + + Details: + - If the client's language is not detected, it waits for 30 seconds of audio input to make a language prediction. + - It utilizes the Whisper ASR model to transcribe the audio, continuously processing and streaming results. + - Segments are sent to the client in real-time, and a history of segments is maintained to provide context. + - Pauses in speech (no output from Whisper) are handled by showing the previous output for a set duration. + - A blank segment is added if there is no speech for a specified duration to indicate a pause. + + Returns: + None + + Raises: + Exception: If there is an issue with audio processing or WebSocket communication. + """ # detect language if self.language is None: @@ -327,12 +453,27 @@ class ServeClient: Processes the segments from whisper. Appends all the segments to the list except for the last segment assuming that it is incomplete. + This method takes segments obtained from the Whisper, processes them, and updates the + ongoing transcript with the transcribed text. It handles complete segments, incomplete + segments, and repeated segments while maintaining chronological order. + + Details: + - The method updates the ongoing transcript with transcribed segments, including their start and end times. + - Complete segments are appended to the transcript in chronological order. + - Incomplete segments (assumed to be the last one) are processed to identify repeated content. If + the same incomplete segment is seen multiple times, it updates the offset and appends the segment + to the transcript. + - A threshold is used to detect repeated content and ensure it is only included once in the transcript. + - The timestamp offset is updated based on the duration of processed segments. + - The method returns the last processed segment, allowing it to be sent to the client for real-time updates. + Args: segments(dict) : dictionary of segments as returned by whisper duration(float): duration of the current chunk Returns: - transcription for the current chunk + dict or None: The last processed segment with its start time, end time, and transcribed text. + Returns None if there are no valid segments to process. """ offset = None self.current_out = '' @@ -391,6 +532,15 @@ class ServeClient: return last_segment def disconnect(self): + """ + Notify the client of disconnection and send a disconnect message. + + This method sends a disconnect message to the client via the WebSocket connection to notify them + that the transcription service is disconnecting gracefully. + + Returns: + None + """ self.websocket.send( json.dumps( { @@ -401,6 +551,16 @@ class ServeClient: ) def cleanup(self): + """ + Perform cleanup tasks before exiting the transcription service. + + This method performs necessary cleanup tasks, including stopping the transcription thread, marking + the exit flag to indicate the transcription thread should exit gracefully, and destroying resources + associated with the transcription process. + + Returns: + None + """ logging.info("Cleaning up.") self.exit = True self.transcriber.destroy() From 5360b8abaa63d05d700becf1410b87a10c4be335 Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 15 Sep 2023 11:35:41 +0530 Subject: [PATCH 02/13] remove unused imports --- whisper_live/server.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/whisper_live/server.py b/whisper_live/server.py index 7a9d4ca..2473b64 100644 --- a/whisper_live/server.py +++ b/whisper_live/server.py @@ -1,16 +1,12 @@ import websockets -import pickle, struct, time, pyaudio +import time import threading -import os, json -import base64 -import wave +import json import textwrap import logging # logging.basicConfig(level = logging.INFO) -from collections import deque -from dataclasses import dataclass from websockets.sync.server import serve import torch @@ -275,7 +271,7 @@ class ServeClient: previous complete segments to provide a coherent and visually organized transcription. - It ensures that the combined transcription fits within two lines, with a maximum of 50 characters per line. - Segments are concatenated in the order they exist in the list of previous segments, with the most - recent complete segment first and older segments appended as needed to maintain the character limit. + recent complete segment first and older segments prepended as needed to maintain the character limit. - If a 3-second pause is detected in the previous segments, any text preceding it is discarded to ensure the transcription starts with the most recent complete content. The resulting transcription is returned as a single string. From 9fbf1d48578afa026ff75ce919ba7bd2b7a10b2a Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 15 Sep 2023 13:26:58 +0530 Subject: [PATCH 03/13] update docstrings --- whisper_live/server.py | 49 ++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/whisper_live/server.py b/whisper_live/server.py index 2473b64..f1f374f 100644 --- a/whisper_live/server.py +++ b/whisper_live/server.py @@ -266,13 +266,10 @@ class ServeClient: Format the current incomplete transcription output by combining it with previous complete segments. The resulting transcription is wrapped into two lines, each containing a maximum of 50 characters. - Details: - - This method is responsible for combining the current incomplete segment with a history of - previous complete segments to provide a coherent and visually organized transcription. - - It ensures that the combined transcription fits within two lines, with a maximum of 50 characters per line. - - Segments are concatenated in the order they exist in the list of previous segments, with the most + It ensures that the combined transcription fits within two lines, with a maximum of 50 characters per line. + Segments are concatenated in the order they exist in the list of previous segments, with the most recent complete segment first and older segments prepended as needed to maintain the character limit. - - If a 3-second pause is detected in the previous segments, any text preceding it is discarded to ensure + If a 3-second pause is detected in the previous segments, any text preceding it is discarded to ensure the transcription starts with the most recent complete content. The resulting transcription is returned as a single string. @@ -301,12 +298,9 @@ class ServeClient: of audio frames as they are received. It also ensures that the buffer does not exceed a specified size to prevent excessive memory usage. - Details: - - The method appends incoming audio frames to the ongoing audio stream buffer. - - If the buffer size exceeds a threshold (45 seconds of audio data), it discards the oldest 30 seconds - of audio data to maintain a reasonable buffer size. - - If the buffer is empty, it initializes it with the provided audio frame. - - The audio stream buffer is used for real-time processing of audio data for transcription. + If the buffer size exceeds a threshold (45 seconds of audio data), it discards the oldest 30 seconds + of audio data to maintain a reasonable buffer size. If the buffer is empty, it initializes it with the provided + audio frame. The audio stream buffer is used for real-time processing of audio data for transcription. Args: frame_np (numpy.ndarray): The audio frame data as a NumPy array. @@ -329,12 +323,11 @@ class ServeClient: This method continuously receives audio frames, performs real-time transcription, and sends transcribed segments to the client via a WebSocket connection. - Details: - - If the client's language is not detected, it waits for 30 seconds of audio input to make a language prediction. - - It utilizes the Whisper ASR model to transcribe the audio, continuously processing and streaming results. - - Segments are sent to the client in real-time, and a history of segments is maintained to provide context. - - Pauses in speech (no output from Whisper) are handled by showing the previous output for a set duration. - - A blank segment is added if there is no speech for a specified duration to indicate a pause. + If the client's language is not detected, it waits for 30 seconds of audio input to make a language prediction. + It utilizes the Whisper ASR model to transcribe the audio, continuously processing and streaming results. Segments + are sent to the client in real-time, and a history of segments is maintained to provide context.Pauses in speech + (no output from Whisper) are handled by showing the previous output for a set duration. A blank segment is added if + there is no speech for a specified duration to indicate a pause. Returns: None @@ -449,19 +442,13 @@ class ServeClient: Processes the segments from whisper. Appends all the segments to the list except for the last segment assuming that it is incomplete. - This method takes segments obtained from the Whisper, processes them, and updates the - ongoing transcript with the transcribed text. It handles complete segments, incomplete - segments, and repeated segments while maintaining chronological order. - - Details: - - The method updates the ongoing transcript with transcribed segments, including their start and end times. - - Complete segments are appended to the transcript in chronological order. - - Incomplete segments (assumed to be the last one) are processed to identify repeated content. If - the same incomplete segment is seen multiple times, it updates the offset and appends the segment - to the transcript. - - A threshold is used to detect repeated content and ensure it is only included once in the transcript. - - The timestamp offset is updated based on the duration of processed segments. - - The method returns the last processed segment, allowing it to be sent to the client for real-time updates. + Updates the ongoing transcript with transcribed segments, including their start and end times. + Complete segments are appended to the transcript in chronological order. Incomplete segments + (assumed to be the last one) are processed to identify repeated content. If the same incomplete + segment is seen multiple times, it updates the offset and appends the segment to the transcript. + A threshold is used to detect repeated content and ensure it is only included once in the transcript. + The timestamp offset is updated based on the duration of processed segments. The method returns the + last processed segment, allowing it to be sent to the client for real-time updates. Args: segments(dict) : dictionary of segments as returned by whisper From 3514b5f0dc7da7f0c46842f4a93acd4b6f81c545 Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 15 Sep 2023 13:27:09 +0530 Subject: [PATCH 04/13] add docstrings --- whisper_live/client.py | 197 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 190 insertions(+), 7 deletions(-) diff --git a/whisper_live/client.py b/whisper_live/client.py index 663f579..76363a5 100644 --- a/whisper_live/client.py +++ b/whisper_live/client.py @@ -44,6 +44,29 @@ def resample(file: str, sr: int = 16000): class Client: + """ + Represents a client for audio recording and streaming to a server using WebSocket communication. + + This class allows audio recording from the microphone or playing audio from a file while streaming it to + a server for transcription or translation. It uses PyAudio for audio recording and playback and WebSocket + for communication with the server. + + Attributes: + CHUNK (int): The size of audio chunks for recording and playback. + FORMAT: The audio format used by PyAudio (paInt16 for 16-bit PCM). + CHANNELS (int): The number of audio channels (1 for mono). + RATE (int): The audio sampling rate in Hz (samples per second). + RECORD_SECONDS (int): The maximum duration for audio recording in seconds. + RECORDING (bool): Indicates whether recording is currently active. + multilingual (bool): Indicates if multilingual transcription is enabled. + language (str): The selected language for transcription. + task (str): The transcription or translation task to be performed. + uid (str): A unique identifier for the client. + WAITING (bool): Indicates if the client is waiting for server availability. + LAST_RESPONSE_RECIEVED (float): Timestamp of the last response received from the server. + DISCONNECT_IF_NO_RESPONSE_FOR (int): Maximum time without server response before disconnection. + + """ CHUNK = 1024 FORMAT = pyaudio.paInt16 CHANNELS = 1 @@ -59,6 +82,30 @@ class Client: DISCONNECT_IF_NO_RESPONSE_FOR = 15 def __init__(self, host=None, port=None, is_multilingual=False, lang=None, translate=False): + """ + Initializes a Client instance for audio recording and streaming to a server. + + If host and port are not provided, the WebSocket connection will not be established. + When translate is True, the task will be set to "translate" instead of "transcribe". + he audio recording starts immediately upon initialization. + + Args: + host (str): The hostname or IP address of the server. + port (int): The port number for the WebSocket server. + is_multilingual (bool, optional): Specifies if multilingual transcription is enabled. Default is False. + lang (str, optional): The selected language for transcription when multilingual is disabled. Default is None. + translate (bool, optional): Specifies if the task is translation. Default is False. + + Attributes: + timestamp_offset (float): A timestamp offset for tracking audio timing. + audio_bytes (bytes): A buffer for storing audio data. + p (pyaudio.PyAudio): An instance of PyAudio for audio streaming. + stream (pyaudio.Stream): The audio stream for recording. + client_socket (websocket.WebSocketApp): The WebSocket client for server communication. + ws_thread (threading.Thread): A thread for running the WebSocket client. + frames (bytes): A buffer for accumulating audio frames. + + """ Client.multilingual = is_multilingual Client.language = lang if is_multilingual else "en" if translate: @@ -73,7 +120,6 @@ class Client: input=True, frames_per_buffer=self.CHUNK) - # create websocket connection if host is not None and port is not None: socket_url = f"ws://{host}:{port}" self.client_socket = websocket.WebSocketApp(socket_url, @@ -85,7 +131,6 @@ class Client: print("[ERROR]: No host or port specified.") return - # start websocket client in a thread self.ws_thread = threading.Thread(target=self.client_socket.run_forever) self.ws_thread.setDaemon(True) self.ws_thread.start() @@ -95,6 +140,18 @@ class Client: @staticmethod def on_message(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. + + Args: + ws (websocket.WebSocketApp): The WebSocket client instance. + message (str): The received message from the server. + + """ Client.LAST_RESPONSE_RECIEVED = time.time() message = json.loads(message) if message.get('uid')!=Client.uid: @@ -153,6 +210,16 @@ class Client: @staticmethod def on_open(ws): + """ + Callback function called when the WebSocket connection is successfully opened. + + Sends an initial configuration message to the server, including client UID, multilingual mode, + language selection, and task type. + + Args: + ws (websocket.WebSocketApp): The WebSocket client instance. + + """ print(Client.multilingual, Client.language, Client.task) print("[INFO]: Opened connection") @@ -165,19 +232,50 @@ class Client: @staticmethod 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 + have values between -1 and 1. + + Args: + audio_bytes (bytes): Audio data in bytes. + + Returns: + np.ndarray: A NumPy array containing the audio data as float values normalized between -1 and 1. + """ raw_data = np.frombuffer( buffer=audio_bytes, dtype=np.int16 ) return raw_data.astype(np.float32) / 32768.0 def send_packet_to_server(self, message): + """ + Send an audio packet to the server using WebSocket. + + Args: + message (bytes): The audio data packet in bytes to be sent to the server. + + """ try: self.client_socket.send(message, websocket.ABNF.OPCODE_BINARY) except Exception as e: print(e) def play_file(self, filename): - # read audio and create pyaudio stream + """ + 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 + 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. + + Args: + filename (str): The path to the audio file to be played and sent to the server. + """ self.wf = wave.open(filename, 'rb') self.stream = self.p.open(format=self.p.get_format_from_width(self.wf.getsampwidth()), channels=self.wf.getnchannels(), @@ -210,20 +308,44 @@ class Client: print("[INFO]: Keyboard interrupt.") def close_websocket(self): + """ + Close the WebSocket connection and join the WebSocket thread. + + 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. + + """ try: - self.client_socket.close() # Close the WebSocket connection + self.client_socket.close() except Exception as e: print("[ERROR]: Error closing WebSocket:", e) try: - self.ws_thread.join() # Wait for the WebSocket thread to finish + self.ws_thread.join() except Exception as e: print("[ERROR:] Error joining WebSocket thread:", e) def get_client_socket(self): + """ + Get the WebSocket client socket instance. + + Returns: + WebSocketApp: The WebSocket client socket instance currently in use by the client. + """ return self.client_socket def write_audio_frames_to_file(self, frames, file_name): + """ + Write audio frames to a WAV file. + + 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: + frames (bytes): The audio frames to be written to the file. + file_name (str): The name of the WAV file to which the frames will be written. + + """ wf = wave.open(file_name, 'wb') wf.setnchannels(self.CHANNELS) wf.setsampwidth(2) @@ -232,8 +354,23 @@ class Client: wf.close() def record(self, out_file="output_recording.wav"): + """ + Record audio data from the input stream and save it to a WAV file. + + Continuously records audio data from the input stream, sends it to the server via a WebSocket + connection, and simultaneously saves it to multiple WAV files in chunks. It stops recording when + the `RECORD_SECONDS` duration is reached or when the `RECORDING` flag is set to `False`. + + 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 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". + + """ n_audio_file = 0 - # create dir for saving audio chunks if not os.path.exists("chunks"): os.makedirs("chunks", exist_ok=True) try: @@ -266,10 +403,22 @@ class Client: self.p.terminate() self.close_websocket() - # combine all the audio files self.write_output_recording(n_audio_file, out_file) 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 + 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`. + + + Args: + n_audio_file (int): The number of audio chunk files to combine. + out_file (str): The name of the output WAV file to save the final recording. + + """ input_files = [f"chunks/{i}.wav" for i in range(n_audio_file) if os.path.exists(f"chunks/{i}.wav")] wf = wave.open(out_file, 'wb') wf.setnchannels(self.CHANNELS) @@ -288,10 +437,44 @@ class Client: class TranscriptionClient: + """ + Client for handling audio transcription tasks via a WebSocket connection. + + Acts as a high-level client for audio transcription tasks using a WebSocket connection. It can be used + to send audio data for transcription to a server and receive transcribed text segments. + + Args: + host (str): The hostname or IP address of the server. + port (int): The port number to connect to on the server. + is_multilingual (bool, optional): Indicates whether the transcription should support multiple languages (default is False). + lang (str, optional): The primary language for transcription (used if `is_multilingual` is False). Default is None, which defaults to English ('en'). + translate (bool, optional): Indicates whether translation tasks are required (default is False). + + Attributes: + client (Client): An instance of the underlying Client class responsible for handling the WebSocket connection. + + Example: + To create a TranscriptionClient and start transcription on microphone audio: + ```python + transcription_client = TranscriptionClient(host="localhost", port=9090, is_multilingual=True) + transcription_client() + ``` + """ def __init__(self, host, port, is_multilingual=False, lang=None, translate=False): self.client = Client(host, port, is_multilingual, lang, translate) def __call__(self, audio=None): + """ + 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 + 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 Client.RECORDING: if Client.WAITING: From b617b03077899c2f23ed25148c1126cb697a3dba Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 15 Sep 2023 13:30:35 +0530 Subject: [PATCH 05/13] remove none returns from docstrings --- whisper_live/server.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/whisper_live/server.py b/whisper_live/server.py index f1f374f..50b6381 100644 --- a/whisper_live/server.py +++ b/whisper_live/server.py @@ -305,8 +305,6 @@ class ServeClient: Args: frame_np (numpy.ndarray): The audio frame data as a NumPy array. - Returns: - None """ if self.frames_np is not None and self.frames_np.shape[0] > 45*self.RATE: self.frames_offset += 30.0 @@ -329,9 +327,6 @@ class ServeClient: (no output from Whisper) are handled by showing the previous output for a set duration. A blank segment is added if there is no speech for a specified duration to indicate a pause. - Returns: - None - Raises: Exception: If there is an issue with audio processing or WebSocket communication. @@ -521,8 +516,6 @@ class ServeClient: This method sends a disconnect message to the client via the WebSocket connection to notify them that the transcription service is disconnecting gracefully. - Returns: - None """ self.websocket.send( json.dumps( @@ -541,8 +534,6 @@ class ServeClient: the exit flag to indicate the transcription thread should exit gracefully, and destroying resources associated with the transcription process. - Returns: - None """ logging.info("Cleaning up.") self.exit = True From 96e86f2c7a9034b783fc1e9f1743d097cdb5c7d6 Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 22 Sep 2023 12:28:31 +0530 Subject: [PATCH 06/13] add sphinx docs --- docs/Makefile | 20 + docs/build/doctrees/environment.pickle | Bin 0 -> 78578 bytes docs/build/doctrees/index.doctree | Bin 0 -> 48761 bytes docs/build/html/.buildinfo | 4 + docs/build/html/_sources/index.rst.txt | 25 + docs/build/html/_static/alabaster.css | 703 +++++++++++++ docs/build/html/_static/basic.css | 925 ++++++++++++++++++ docs/build/html/_static/custom.css | 1 + docs/build/html/_static/doctools.js | 156 +++ .../html/_static/documentation_options.js | 13 + docs/build/html/_static/file.png | Bin 0 -> 286 bytes docs/build/html/_static/language_data.js | 199 ++++ docs/build/html/_static/minus.png | Bin 0 -> 90 bytes docs/build/html/_static/plus.png | Bin 0 -> 90 bytes docs/build/html/_static/pygments.css | 84 ++ docs/build/html/_static/searchtools.js | 574 +++++++++++ docs/build/html/_static/sphinx_highlight.js | 154 +++ docs/build/html/genindex.html | 226 +++++ docs/build/html/index.html | 295 ++++++ docs/build/html/objects.inv | 7 + docs/build/html/py-modindex.html | 123 +++ docs/build/html/search.html | 117 +++ docs/build/html/searchindex.js | 1 + docs/make.bat | 35 + docs/source/conf.py | 36 + docs/source/index.rst | 25 + 26 files changed, 3723 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/build/doctrees/environment.pickle create mode 100644 docs/build/doctrees/index.doctree create mode 100644 docs/build/html/.buildinfo create mode 100644 docs/build/html/_sources/index.rst.txt create mode 100644 docs/build/html/_static/alabaster.css create mode 100644 docs/build/html/_static/basic.css create mode 100644 docs/build/html/_static/custom.css create mode 100644 docs/build/html/_static/doctools.js create mode 100644 docs/build/html/_static/documentation_options.js create mode 100644 docs/build/html/_static/file.png create mode 100644 docs/build/html/_static/language_data.js create mode 100644 docs/build/html/_static/minus.png create mode 100644 docs/build/html/_static/plus.png create mode 100644 docs/build/html/_static/pygments.css create mode 100644 docs/build/html/_static/searchtools.js create mode 100644 docs/build/html/_static/sphinx_highlight.js create mode 100644 docs/build/html/genindex.html create mode 100644 docs/build/html/index.html create mode 100644 docs/build/html/objects.inv create mode 100644 docs/build/html/py-modindex.html create mode 100644 docs/build/html/search.html create mode 100644 docs/build/html/searchindex.js create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..b0c06917e3ee28c8299b0c173e54ef7a10172d79 GIT binary patch literal 78578 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 literal 0 HcmV?d00001 diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7151b12e7159ddd82ae1cff24663f5ca89d8e8af GIT binary patch literal 48761 zcmeHwdypK*c^_W)TD*9G2oj=b4#5ZZK->ZZMM^vpBu@}&(U2e-fJw*+w4B?WyP3i6 z&T?lKz|m2OR+$V@)k$P?Em?{rJ5@Z(QT30J> zz21uIUfdJY^{Ccudab1QR&Va-d!Oi)#Z1S&5=Y%m&FhIRc%$YAje5sx^)BM!9(q3s z8gWH6Fjb41nfm^O_{J4Kh}&ML+6XRtj_$!tf~e&*-8k_&&T`Q3jykoD=O$j=Sz0a0 zka@WTs_JihSAap%PG6OU9%AB(z9&22eu-EAjM zg8sNk2cu~>yu@p)I!WZL0t}}STnbt%N1df^;sl8k`%$-1cihBjco<8m7PS&LXrT?y z=|oZDyqrX}q~m!nJN2OB)e^u^>G@L^d;W~rao)R1sQOU^@%0|jUre@L5IgDlyW%BR zl-wlg1V9~_g_vV|P>;a~Vyf0~;~0hbKGAZU^4oO1+crOz+}fp%x6JR%Tx~WM;&-m-Md=p#7R%=Xap^~EJ3DM0{VmgVFq>vz%S-8 zB=#!!=C5?zHWr?qn@8HyS)>8UK#PJExk9z1eS-O-t%KZ!Y}xT#l_L)xjo=x z64jOV>Hq#6fUosw=Rvm}HTZpv4@ zr8ufx@{*%Y;>MRw9!@%LE3RS9^bQ|&>fYs`=AAqfwIJ=9-3BBlhSzl)C!cd0G0GY) z{an$n)RL*4qnh1HWtbiE z(4gy|4M$g5>;wbOLtJu?VmA$d3C6!Ydy&&f`kFzK|5f659{LOqJwNW979(p?X3ESc z|4q>-r)iXOh||>3@X>u^;OO@8=%9B1uTTyf0fe@N-itSMj-^vf&iHf`_6TOu5lA#` zk))vIp_)f({@qz&l^BG6e>g(L)>i*4`Jcetf6EKLK@9Lcy!c;1^gq@TM9lUrObX^d zNnhDPuV@yKKhmLwrL9}G3?Hrk|N4Y2#u`qd60_bc6PGX*K{C5Z)Uh$U^`!0<-%OY9 zF6>fljrtH2-bq!`ej%2E7OQ<0Bki&mXWehM&>{nG&TY#~rXNV$kg-KFp# z{-g{ugl^{w+YcYW<2<)$!l_=4*uCr^!KtBioUYsq;?@+b^V!>Lo*+!fEt-#eJ=VHgX8-q0Qc${@X{L zl6gVX$1XAy`lCXqhEV7aMyJr97PenVA=S(16xy<_NdAWFppYEG$P_AmQ5@uj4bHxn zS54I?{Dc-qGSC0Rex=W9@l!52IpA4>iqI1!QYE9M5I&k-M!yx4(vt3p{R6aZIH9B= z<4=m|0P;ZszKs4t=2Ale`3p)#njEVP&&JK{#mCSw34|6 z=RCZUZnqvpPVB<7NwzI?C=WJ$)PkE7?>Mms2WaFx{Mhm1$1A0C&z^np*|XKN&n|xO zV@?_7FdA8eTU5U7z)=^&+=Mfr9@Jn-qP@h2FOWN zv!~9F*^->4+SOVQV)WkvNh{W5HBEL~!7E)134Wp^SPsZL9Cdg^vYkq)?k28NURs43 zMc~xnvhFt6b1ZQ~ν6vLE82_v9Oul3wHW7zoR08F#LXcy?!1N~9gz@AB_twG9K_ ztE^OxI){waUpZ7VKRQnNCFu+Q2>k60MNn1bfTO(z0}F4tSM%qSXr2stbQl1V1Qf4E z%gaRI@^T|`HAAQ}ICLcV>D#eJLbcU)%J8qXS1YZ$+v&Kg==w~z`NFEhzldRCsN$Nr zG-Bl6kvi2?XuwXHW;?q0F#eG9#bBWB@C&iGLc0VQV6EFB|8g~gJ6`c8ptI-#HkA@4sz>8&catiWVZ#CY4e5N=1;l1FEj`*#!8Qq1b7VElM2#&}zEahY ztL6r`MQ_q{PEpd=7IuBG5_V&+upPWb&A1M~3h*&8F3~E(Ljr52Q=^dT76@LeUd3)f zQf*arsRjWo2^TPN485Q`iJ3i7js6m%^*W&3yH1&Sihfki= zb})FE!?Q}MjqN4ICvi=|a8fL$BgRma2OEERR1(3JQtBL#%k@j%(mD1B5_xIWqiru< zR4hnJO0~h35DkY{`VD>CfEhZ7?y;u!)h4HptsQAEwu}HDY-n`(v!aM>8Fe4Rd0I4vxzX=IIZI zbBW}pg(*->XI1)S5avShP3sCF@?R5=nwH9QPI-X0)eetLyKtnp@Mgm&mt zXb`5NpWf2@jM~%sE4n}+9yEVS3}hVBYJ6Wjqakn_kI;&mel%Ov=C73y;tncMfi(JjcPE*oa7h09$|_V7ZSJOrVW-F9((bc2o|S+arP;~ z-d4LaHdc>1ZlfW+vFyFX22#-KlA9X*z?(%Scsw(ta^dszJ@U>Y3f-%D@Cer{&hrUc zis5l?#fU+qSJYOGq#h!WQHYR0xTI?y;^H+faRb}1?zh&#y5Ya0N^_^T4`-wcPhgAqd2EG%M-;h;B#KmE1w7Gv z0p<5T3g0)}#r2qc;gU1R!#8)7jx5jyn%luMY71&KwuT&>gitjMFR@P$B-bp8J}}o2 zRP7S;T7v**Wd;U@_nPfwm3!m;Ot*otVp|37^dRxM<8~*yj6J$~$zt4!y+LVD0h&43 zX{hs*!5|v*v`GbKjB40{q`8xUQ~Jl#odtNzHDhj~af4_yjQ*VsF#1%M(H}-rSz#1D zW)+F&7CS5Pg4~fExEFaoT{&4+In26P1YLWVQu07Ke9~@uf}7Twocd*e{J&!XDYr!Q zY~BeVe}MXkq56o`rIfZ=Vf&0$jrzA)#{kWr6&r8Jz`kp{)E34O!W#BnfPDt_i_|pQ zL{dhSM3Ygn1{_q;MwD7A3Aum9$StYBYOv#4_~6D>u(FPRGJ#ae-}lyRDk1XyK)%>>Gm;XWtlOdJRf{ zo0<5tg)_0(lzcI*dux!Cvm>{`GU3yYLywB7;~5C^3eDIL0y7Ao2#TF04~3-oLG|Z& zt9;~043`Fu32Bu_Y`7i6h@Vo!A4?>hrAi6oKV}_%aw5 z84N(J%#<#y=AZi%#AjWklNgdR;h33-F(0~F5wVsR0}wiELC)~Ei08ZCC-l_KTpvo zEelzh8_FrNAvdcDiyTYzuIXkq?GBf|H_0+Zo;FvFzqJdzJF1suB8105`# zF8$Y`CBvA1tUwq`GXEx3l9Ktm1_c?(`~_Pw6HH`O8)!99+BvK+aycM>l;;c#UW7{t zPDX{pCgig6LS_Y}(qhQ-W4a@0YXotM$#?c34UztQ14JTuXNWYCywhwAd8e6u9NSgO zyS+QKyt|FQvApAN8m@CNGG&I1=0J$`3ZVzmx}tmYY~~DXc_}n0Q6hmh8;hA@v#ppZo)!A1!Ok-& zgo8x%kS)b0QK`sRr8j4Bf;N2r2=8XM6b;hD_tEI`qSHa)brGF+4B}7jp@%K>@r5PWe^GNu?vd#&gfuQS zJ>Q{2>no-6kUX%-C_e@n$o4(}i4z&oUq`=?M9Gy1DV6& z*i6cD#&VE=Hv%O8!g&nC6>P^3X9Wk|3T7ajl~c!Tc-IwtgadKNv_T2U^a+nn9WO&K zS|vvWcHLAtopvG~OL#0){Kx`jJ9&I_nK#UPY5;8nq)7QBCx;Vs^FRQs9| zHO0o73J$e8ctavIvju+gEU#zS1;-l3$4BK^L|Rfa3Xaikj1-d#UT)`mTDPdNYYGgm z{>fOl%JRk@@WyXQP}bW2e($=0!k+i~DjOKz9t*}SKkNmJ|B_)Gi6107#^DD-BJqP% zu#0P|y>6`!!R(hzHR@isR^(Cfebi;nOc~8T{=ZRE`p17j-`HQk-!|YcQ0De}Ly#Yr z=i!m1p-S4xSnh(#TJC~u+Z*s0yvH`WvaJsASntWcq33YE*l_u6I0&^~r@^kYuDE*h zoCbLa^L~SE<7_1x5W=4zXmY$APNDF+nBeaoPDW*gA469;D?Cp&ZHO21g!W`>fUs1| zC`ygu$v*pn44;GHkPSP9S%RXV#kok3$|8!|NUofQol-JnC30YtQ^pUZ8{mu+TCo;4 zqG`j*?^i&MWux9&#sz5}HKei4RPlM+!=Ayiirj^EYK37RC5uz;kDEY9O%L9^miyD} ztG7Xr#iq_?+*0Tr{sJm57Ds2Pp*S4!sn=xjJzE}pbI7PBgg<(xxIm8&1rfU z7`GHLt&L+2dA;J2D*-gJ!fH)`YS2O|4{3#8LcF(W3Z@vc=umzK*x-pndC@s)mgd(> zGVP1Ac-E z8pt_zp!gVX11-V(r*o0es8X+kv?dtKFz=Dmrrt#=BSN0D=x~-1JvMScAW|M}13L!Zv%Tc7VOGkREsb<6P8hb1ZToe-xzpdP2PZ(4R(HREbJ&#Q(G8O#?NSovCl0;7d+)(r??8$ThwJL-SZcvt=#Mk!)Vc)BdgMy?tY$>r7@vU%6rY zuYySHwLX6{tt(b}mauTrtfqcJ2WV$EI%@q!fTiy8vc<&{wS9J>&o)yGoYmPE5neraOLoV z8jZV8INqz#kT$-4jZ6=y;FEAzpGP(4>*lnzVR_ zjK=Ws0T62VVwKMWA*J-KUYfuG`_-_)0k(tT!WUtw{0xgGb>veW;MV3N$R~rW&^N%|GN->JW0C?KIag?sI=pt&|oqIbd;f>P~dRXE5=a z^cwUHly~ywF1eaDbdeEEQNApxj;qXSlya9RoWZG#dr6bG!Z_ztuhbnAAEvZRM`#hB zN;6fj-!kxk&(Y$cM!MWySI5@nR=NZd$!@VJZxF63SZCBR&jA|fG{Bia%S7r;P~%FY zX*L;klTk^5eg`wEkr5B9Lq>%6QzaP{U@g}DHhwNDAdyi4dTy4*p5 z_QWCJMF+o-_e{z2lAiEsqDx&sm1#xLIg@LCw{>YffdmU<;Y8W~3MaJB`55YtVEZ?3 zDk8M6<0kiNIPLUUSXDj)g;nwye2B3+lFvXAV7Pvb4JUmJ+alW|l5Cl9g)i&i0-VjL zA-JvM(A_ICrRZP@B6SdQoJ$RZ1Q}v7m5w1j&sx|mk)-DY%#Qx2`r~fM&kKeo4Fvz; zWX7dBfst(_o|FKSI%v+zW#SV<>Szez7RXmBb0f7AB#L7H81_JO(o217d1 zk|U8a&XPm3=hYE9P;SaABfP4jwd9-}cPzC0jS-!r1wXu>at$5TZK@4?RMckw_-@m; zF<9v~{a5sj-KP9)!*0`Jw8?B5@-h^CIZ9^qvGl^oR2u5`m26fuK7IQi(((Vtbo~5n zJ>$o{x18=m9Q3Xt(N$8#{h^Q=u&(OWrB!*M3$8G}W8l-EjvI{q*ncD5Kyt(r=k3@% z_W2;YE#Eyu2Ht?!BVixZPI8*Y@Ieex9zA|PeWRam$mEOsT?b)l+yG;LsyJs9SM2Gt9zq(( zRYF1NiM<@afr8wmHzA`^Qa_5gu5B7>oh+o*5Vz=vRkjptC-3NlBxL>+|`5|z14jRmm{QRad3<* zxEv&_$^%Ge#<@zRM752tFtyds!#*EcHGN-Sh=p6LNcHAxhUg30Cno{y9(hX>{7iI{ z46G>4I_B1ywd&gO0{T80x`&CQg>VNBxreKqop0C}z~r?{`s}&Xf0(Q9-#weo@*(Mo5VhOV}m;es0x)8 zFR49KfhfO-!VQ@#(oW&T^7pPuRTYu) zkI|_ZXrqZ7IdxphNDQM*A`K{(oVOGqM=^;WKjk)Rd?71ugm?JhBkxclEhPh^>3SeYb>NHCHd=ANuJU8xA?lYk}Qsl2@2fi<5V$B^1ZR(QKDGEL!$UQ49`fS zSb{#1D7FyxjS)IfqFD0`9L%zTQ9aXoa-*W2@_8p)QI2GpXKLTH_Dt`mZ|s@oZ|m{w zO5=Z{xr|TZ0M_LyJ6hHy&QFXg&P_jApKa>H&})!ikZyQx(5i1*E?@DMkeu|Am-Gkp zQeqJWs9r+tBE#-SK^ogln`PW$Szb=-iobI6H9Z?b>A^H<5NA@5j9u)tso5ZXJpfW)^*o6eA7_F z)e}EE(T4b1Z~e3aq633ydt3X$Q}D5#vd<7>M$)}B+0(5lmUC{tQbeBcA;(l5oR-uP zi84x2JI8U~6h&LIzq~Zme_DQV@q=^bSS3YecA)7X#**VmDE$~G1ykn*OVBuR<}S9Wou zJZaRWF0K}8&{@a^&a~3FFS|j5u6t2gq2wTN{#ChQ$&o5A3zB%(!`9wf8N>rs{R*-* zmJkzaQhCz&DT3C<2q06Lw7CKWk#gp5DE!^Fo?aI|w8=>sy3)bZ8^TMtgq64nx3At< ze&Um}H-e+M;bNXLOYYCDW62F)rApE?u@=65lPed|sV=lh^$y%<>n)v=+L<)K(%<$} z)2A%tG+bA*r%#Usr82-2l$7j7p#BI3*wY&XO{<|BZpYB~Z;b`HGSw`|zr>Ii3t|;W zB#|_dsb+O>O`gc6e?6Kq@PBoZT>5cBmhsri?%PKlF{QeExRpto+A1-Bx+X&9nn7 z4dbmJm`Qp-jS4#0fL&i6(q!33dQ4=4i8{&2$B#dLyx*+f&K=809R6;&djE`Mn!)x( zu_3=Vt$U*rzYc+12S*tTes2yJEI#amxI5?tDO8LNZxzl8`NLr7NDAc`mivRKG^*u( zh~Kpi-_E3@RKvGMN{_Tm+`&K0NI$Tikv_$}hX)Z&kcOCTJ*m6EH`D$Lh3$tApox62 zU3e!7u{Nn*j@Z5IAZFDa$seY#j~Dm#SiUc@Kkl{(AJrK5{3Ne*BeVZN zRfoD!W^YN7;&-#o6f+mXduiyoMKLU)@On8@?ie;5WOUjm(bW(y3QvCRJ~=p~8Ov{? zK>d3+tgM0ok8fDV1|#&=|R_SeV4ozfO9-2HvV-ALM^MDy^)$~vrN zb@15{I#ByTnwg>S$p%K%6!n^cih3m(e?RD37^u_~|2=(UO_9HCSW_%UnY#B=$M6CM z9oahshT(@5zLSFR-4q1=wjL1lVTE;{Hh4wcg&0#rqCSy5S0J4UJ@Ib&bUw3br5^fr zgh}os!RuOokQdwf!!C9d6G4nFdT;ul4u1fAP?W$gAnfPjIyQrp1EjsWP>*UdsXLuw z9XND|WdDF1vrR!9qwpHYr0>lqk%oD`0f+Q_JdZ}ZO@w4~CafOIZxS~GduUvR{Hk_e zaENBA+>1IUmdZbZ{QatbtjD2uhSe_R)ceb1AdmK@`>^pp2B*6PxrM`rQa zTPjlOa{|*pM$$d4!2`P;1Dg>$mvGA=0;4FuNAInk9+xC<886JxlWLH7P3y7PyMW^} z3ooyDxXT{**1pVR&SatpAEfTc9@!#xQECXdC8}3Z%W5WlC$yEuv=qzvwC=%DQIDr^ z3{uw1Vh8skw#P}mC#LW@PrYW2JkWBfRK|IhI9ObBr7@^8A$_>{{#H3u5dt&Pmj?cXt-B?S6UdU|;R~Gv)Wl56ELz2uZNy4(% z({a4%E4HvH6kfN7e}LmnpXi0(mVZc;hd;yz&OsK9UvBL6{IYmgLZ_S>EL^MDw_PDy z2Zt%kVj_tWx6wPjMaaa(c*m>bXBT+(w~9R~Lq%1!bw=#cC3J;T?+j&8Dt2?4 zJ(TPr1*TnvE_Vy|@_{tG%O23TO_Y4M zk^YuzqPmOw_%l?h4x5Ljs9pG1~RqCQ4>NqMAoQK~C-AjKx))izOMb z(i1b?WO@Gm^KpRh@5w%_va`M_Ppfr+aT;n30&cI&*K4&h!L9Vf-MJ^ZCc;0bDZNLw zYsR2!zbE@3S6SRfFo4%$q+e<`08>xw);W5C@j8BQ^IAQzAJGU%K~=5G+i+U7fH6IZ zUCCvD35*1F@T#{QT!kxF>~XueOu!V0Z8#1rcGtVjHqt*T76f9s07sF$PRnf&>GpC7 zPXHpnDQd@Fw;olmHi@NfgTfHSUe&J2T!J&omxDNvEV{Rf-E>k90f-vql%5)xX1f3k z;$?9I6`U|=bhmn~ip%%_9MstZ^bMWBwLtHHHq;t4bSN$EmGmcAbe859IH$D&M&xUv zdLOy%u-Mb~+UC$S0Ry$J4SvViX>y1={Um9}3&)P3IIgr;6Casnqt42)p17Uf&S%Qa6WruheT^AtltEPDw#!~!o_=030h9P^WA1D(xa z@xv^tCw}krw745Q*{r71lN2;Q^<9nI#39$jZSg9U|EpC$Sn(V95B}ZJh=}!fv751~ z{ETnH{b<~d)kcI&W)ih`rvOCGboWX;CnRS1|taDkAIGj zu9%@WNWI~92aXJgcRWlu=iqQ8~Bv}x$rfFi-fP!#|zu>@gjZDUBw|?XdKej ziy__5$TvWQbWKD^Ng+eZ9La}>`2ciC@%bSI@rM*27g7)%J9k6!05c41Oz{}`TUYG1 zIgC-U8#aIxdq9IMp@l@hCvj_+5chK_II_7EHBzSK!gu8g)3;|bEzam(xzfH&`+WS8 zgwXBzw$u}dVk~pD2$4bH2eGpj!8T;xfvB50rOMv|p%DXTtp7{`H-yx=laZJ{eb7+v~Li$S&$xQEbjhs&H zP*Ex?bhV|OP+OLYy=`n*9SEy .section { + text-align: left; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +p.caption { + font-family: inherit; + font-size: inherit; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Georgia, serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: Georgia, serif; + font-size: 1em; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +div.sphinxsidebar .badge { + border-bottom: none; +} + +div.sphinxsidebar .badge:hover { + border-bottom: none; +} + +/* To address an issue with donation coming after search */ +div.sphinxsidebar h3.donation { + margin-top: 10px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Georgia, serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #EEE; + border: 1px solid #CCC; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fafafa; +} + +div.admonition p.admonition-title { + font-family: Georgia, serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: #fff; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.warning { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.danger { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.error { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.caution { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.attention { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.important { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.tip { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.hint { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #EEE; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +/* Cloned from + * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 + */ +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +table.footnote td.label { + width: .1px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + /* Matches the 30px from the narrow-screen "li > ul" selector below */ + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +div.viewcode-block:target { + background: #ffd; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fff; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +/* Don't put an underline on images */ +a.image-reference, a.image-reference:hover { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + li > ul { + /* Matches the 30px from the "ul, ol" selector above */ + margin-left: 30px; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: #fff; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: #fff; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Make nested-list/multi-paragraph items look better in Releases changelog + * pages. Without this, docutils' magical list fuckery causes inconsistent + * formatting between different release sub-lists. + */ +div#changelog > div.section > ul > li > p:only-child { + margin-bottom: 0; +} + +/* Hide fugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + + +/* relbar */ + +.related { + line-height: 30px; + width: 100%; + font-size: 0.9rem; +} + +.related.top { + border-bottom: 1px solid #EEE; + margin-bottom: 20px; +} + +.related.bottom { + border-top: 1px solid #EEE; +} + +.related ul { + padding: 0; + margin: 0; + list-style: none; +} + +.related li { + display: inline; +} + +nav#rellinks { + float: right; +} + +nav#rellinks li+li:before { + content: "|"; +} + +nav#breadcrumbs li+li:before { + content: "\00BB"; +} + +/* Hide certain items when printing */ +@media print { + div.related { + display: none; + } +} \ No newline at end of file diff --git a/docs/build/html/_static/basic.css b/docs/build/html/_static/basic.css new file mode 100644 index 0000000..30fee9d --- /dev/null +++ b/docs/build/html/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/build/html/_static/custom.css b/docs/build/html/_static/custom.css new file mode 100644 index 0000000..2a924f1 --- /dev/null +++ b/docs/build/html/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/docs/build/html/_static/doctools.js b/docs/build/html/_static/doctools.js new file mode 100644 index 0000000..d06a71d --- /dev/null +++ b/docs/build/html/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js new file mode 100644 index 0000000..7e4c114 --- /dev/null +++ b/docs/build/html/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/build/html/_static/file.png b/docs/build/html/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHDfiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/docs/build/html/_static/language_data.js b/docs/build/html/_static/language_data.js new file mode 100644 index 0000000..250f566 --- /dev/null +++ b/docs/build/html/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/docs/build/html/_static/minus.png b/docs/build/html/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/docs/build/html/_static/plus.png b/docs/build/html/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/docs/build/html/_static/pygments.css b/docs/build/html/_static/pygments.css new file mode 100644 index 0000000..57c7df3 --- /dev/null +++ b/docs/build/html/_static/pygments.css @@ -0,0 +1,84 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #8f5902; font-style: italic } /* Comment */ +.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ +.highlight .g { color: #000000 } /* Generic */ +.highlight .k { color: #004461; font-weight: bold } /* Keyword */ +.highlight .l { color: #000000 } /* Literal */ +.highlight .n { color: #000000 } /* Name */ +.highlight .o { color: #582800 } /* Operator */ +.highlight .x { color: #000000 } /* Other */ +.highlight .p { color: #000000; font-weight: bold } /* Punctuation */ +.highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #8f5902 } /* Comment.Preproc */ +.highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #a40000 } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .ges { color: #000000 } /* Generic.EmphStrong */ +.highlight .gr { color: #ef2929 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #745334 } /* Generic.Prompt */ +.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ +.highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #000000 } /* Literal.Date */ +.highlight .m { color: #990000 } /* Literal.Number */ +.highlight .s { color: #4e9a06 } /* Literal.String */ +.highlight .na { color: #c4a000 } /* Name.Attribute */ +.highlight .nb { color: #004461 } /* Name.Builtin */ +.highlight .nc { color: #000000 } /* Name.Class */ +.highlight .no { color: #000000 } /* Name.Constant */ +.highlight .nd { color: #888888 } /* Name.Decorator */ +.highlight .ni { color: #ce5c00 } /* Name.Entity */ +.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #000000 } /* Name.Function */ +.highlight .nl { color: #f57900 } /* Name.Label */ +.highlight .nn { color: #000000 } /* Name.Namespace */ +.highlight .nx { color: #000000 } /* Name.Other */ +.highlight .py { color: #000000 } /* Name.Property */ +.highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #000000 } /* Name.Variable */ +.highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ +.highlight .pm { color: #000000; font-weight: bold } /* Punctuation.Marker */ +.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ +.highlight .mb { color: #990000 } /* Literal.Number.Bin */ +.highlight .mf { color: #990000 } /* Literal.Number.Float */ +.highlight .mh { color: #990000 } /* Literal.Number.Hex */ +.highlight .mi { color: #990000 } /* Literal.Number.Integer */ +.highlight .mo { color: #990000 } /* Literal.Number.Oct */ +.highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ +.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ +.highlight .sc { color: #4e9a06 } /* Literal.String.Char */ +.highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ +.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ +.highlight .se { color: #4e9a06 } /* Literal.String.Escape */ +.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ +.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ +.highlight .sx { color: #4e9a06 } /* Literal.String.Other */ +.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ +.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ +.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ +.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #000000 } /* Name.Function.Magic */ +.highlight .vc { color: #000000 } /* Name.Variable.Class */ +.highlight .vg { color: #000000 } /* Name.Variable.Global */ +.highlight .vi { color: #000000 } /* Name.Variable.Instance */ +.highlight .vm { color: #000000 } /* Name.Variable.Magic */ +.highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/build/html/_static/searchtools.js b/docs/build/html/_static/searchtools.js new file mode 100644 index 0000000..7918c3f --- /dev/null +++ b/docs/build/html/_static/searchtools.js @@ -0,0 +1,574 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/docs/build/html/_static/sphinx_highlight.js b/docs/build/html/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/docs/build/html/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html new file mode 100644 index 0000000..548b525 --- /dev/null +++ b/docs/build/html/genindex.html @@ -0,0 +1,226 @@ + + + + + + + Index — whisper_live documentation + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/docs/build/html/index.html b/docs/build/html/index.html new file mode 100644 index 0000000..cd207e0 --- /dev/null +++ b/docs/build/html/index.html @@ -0,0 +1,295 @@ + + + + + + + + Welcome to whisper_live’s documentation! — whisper_live documentation + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

Welcome to whisper_live’s documentation!

+
+
+
+
+class whisper_live.server.ServeClient(websocket, task='transcribe', device=None, multilingual=False, language=None, client_uid=None)
+
+
Attributes:

RATE (int): The audio sampling rate (constant) set to 16000. +SERVER_READY (str): A constant message indicating that the server is ready. +DISCONNECT (str): A constant message indicating that the client should disconnect. +client_uid (str): A unique identifier for the client. +data (bytes): Accumulated audio data. +frames (bytes): Accumulated audio frames. +language (str): The language for transcription. +task (str): The task type, e.g., “transcribe.” +transcriber (WhisperModel): The Whisper model for speech-to-text. +timestamp_offset (float): The offset in audio timestamps. +frames_np (numpy.ndarray): NumPy array to store audio frames. +frames_offset (float): The offset in audio frames. +text (list): List of transcribed text segments. +current_out (str): The current incomplete transcription. +prev_out (str): The previous incomplete transcription. +t_start (float): Timestamp for the start of transcription. +exit (bool): A flag to exit the transcription thread. +same_output_threshold (int): Threshold for consecutive same output segments. +show_prev_out_thresh (int): Threshold for showing previous output segments. +add_pause_thresh (int): Threshold for adding a pause (blank) segment. +transcript (list): List of transcribed segments. +send_last_n_segments (int): Number of last segments to send to the client. +wrapper (textwrap.TextWrapper): Text wrapper for formatting text. +pick_previous_segments (int): Number of previous segments to include in the output. +websocket: The WebSocket connection for the client.

+
+
+
+
+add_frames(frame_np)
+

Add audio frames to the ongoing audio stream buffer.

+

This method is responsible for maintaining the audio stream buffer, allowing the continuous addition +of audio frames as they are received. It also ensures that the buffer does not exceed a specified size +to prevent excessive memory usage.

+

If the buffer size exceeds a threshold (45 seconds of audio data), it discards the oldest 30 seconds +of audio data to maintain a reasonable buffer size. If the buffer is empty, it initializes it with the provided +audio frame. The audio stream buffer is used for real-time processing of audio data for transcription.

+
+
Args:

frame_np (numpy.ndarray): The audio frame data as a NumPy array.

+
+
+
+ +
+
+cleanup()
+

Perform cleanup tasks before exiting the transcription service.

+

This method performs necessary cleanup tasks, including stopping the transcription thread, marking +the exit flag to indicate the transcription thread should exit gracefully, and destroying resources +associated with the transcription process.

+
+ +
+
+disconnect()
+

Notify the client of disconnection and send a disconnect message.

+

This method sends a disconnect message to the client via the WebSocket connection to notify them +that the transcription service is disconnecting gracefully.

+
+ +
+
+fill_output(output)
+

Format the current incomplete transcription output by combining it with previous complete segments. +The resulting transcription is wrapped into two lines, each containing a maximum of 50 characters.

+

It ensures that the combined transcription fits within two lines, with a maximum of 50 characters per line. +Segments are concatenated in the order they exist in the list of previous segments, with the most +recent complete segment first and older segments prepended as needed to maintain the character limit. +If a 3-second pause is detected in the previous segments, any text preceding it is discarded to ensure +the transcription starts with the most recent complete content. The resulting transcription is returned +as a single string.

+
+
Args:

output(str): The current incomplete transcription segment.

+
+
Returns:

str: A formatted transcription wrapped in two lines.

+
+
+
+ +
+
+speech_to_text()
+

Process an audio stream in an infinite loop, continuously transcribing the speech.

+

This method continuously receives audio frames, performs real-time transcription, and sends +transcribed segments to the client via a WebSocket connection.

+

If the client’s language is not detected, it waits for 30 seconds of audio input to make a language prediction. +It utilizes the Whisper ASR model to transcribe the audio, continuously processing and streaming results. Segments +are sent to the client in real-time, and a history of segments is maintained to provide context.Pauses in speech +(no output from Whisper) are handled by showing the previous output for a set duration. A blank segment is added if +there is no speech for a specified duration to indicate a pause.

+
+
Raises:

Exception: If there is an issue with audio processing or WebSocket communication.

+
+
+
+ +
+
+update_segments(segments, duration)
+

Processes the segments from whisper. Appends all the segments to the list +except for the last segment assuming that it is incomplete.

+

Updates the ongoing transcript with transcribed segments, including their start and end times. +Complete segments are appended to the transcript in chronological order. Incomplete segments +(assumed to be the last one) are processed to identify repeated content. If the same incomplete +segment is seen multiple times, it updates the offset and appends the segment to the transcript. +A threshold is used to detect repeated content and ensure it is only included once in the transcript. +The timestamp offset is updated based on the duration of processed segments. The method returns the +last processed segment, allowing it to be sent to the client for real-time updates.

+
+
Args:

segments(dict) : dictionary of segments as returned by whisper +duration(float): duration of the current chunk

+
+
Returns:
+
dict or None: The last processed segment with its start time, end time, and transcribed text.

Returns None if there are no valid segments to process.

+
+
+
+
+
+ +
+ +
+
+class whisper_live.server.TranscriptionServer
+

Represents a transcription server that handles incoming audio from clients.

+
+
Attributes:

RATE (int): The audio sampling rate (constant) set to 16000. +vad_model (torch.Module): The voice activity detection model. +vad_threshold (float): The voice activity detection threshold. +clients (dict): A dictionary to store connected clients. +websockets (dict): A dictionary to store WebSocket connections. +clients_start_time (dict): A dictionary to track client start times. +max_clients (int): Maximum allowed connected clients. +max_connection_time (int): Maximum allowed connection time in seconds.

+
+
+
+
+get_wait_time()
+

Calculate and return the estimated wait time for clients.

+
+
Returns:

float: The estimated wait time in minutes.

+
+
+
+ +
+
+recv_audio(websocket)
+

Receive audio chunks from a client in an infinite loop.

+

Continuously receives audio frames from a connected client +over a WebSocket connection. It processes the audio frames using a +voice activity detection (VAD) model to determine if they contain speech +or not. If the audio frame contains speech, it is added to the client’s +audio data for ASR. +If the maximum number of clients is reached, the method sends a +“WAIT” status to the client, indicating that they should wait +until a slot is available. +If a client’s connection exceeds the maximum allowed time, it will +be disconnected, and the client’s resources will be cleaned up.

+
+
Args:

websocket (WebSocket): The WebSocket connection for the client.

+
+
Raises:

Exception: If there is an error during the audio frame processing.

+
+
+
+ +
+
+run(host, port=9090)
+

Run the transcription server.

+
+
Args:

host (str): The host address to bind the server. +port (int): The port number to bind the server.

+
+
+
+ +
+ +
+
+

Indices and tables

+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv new file mode 100644 index 0000000..00b0d2b --- /dev/null +++ b/docs/build/html/objects.inv @@ -0,0 +1,7 @@ +# Sphinx inventory version 2 +# Project: whisper_live +# Version: +# The remainder of this file is compressed using zlib. +xڭn w?U5kN*ErF€㭯%uL7*8L + 7l[+Hp >Rw8< +C 9Nʥd[S)B7ѭAH5Ftq&%A4 -C8"E'9 Pi ree_!\!C ZDxބO!V7ФܐStґqZ|ˤhx-l=Z2#u񱩻"}+Pp.=b i{\s,{/B{ \ No newline at end of file diff --git a/docs/build/html/py-modindex.html b/docs/build/html/py-modindex.html new file mode 100644 index 0000000..17f51ed --- /dev/null +++ b/docs/build/html/py-modindex.html @@ -0,0 +1,123 @@ + + + + + + + Python Module Index — whisper_live documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ + +

Python Module Index

+ +
+ w +
+ + + + + + + + + + + + + +
 
+ w
+ whisper_live +
    + whisper_live.client +
    + whisper_live.server +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/docs/build/html/search.html b/docs/build/html/search.html new file mode 100644 index 0000000..0ae5276 --- /dev/null +++ b/docs/build/html/search.html @@ -0,0 +1,117 @@ + + + + + + + Search — whisper_live documentation + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + + +
+ + + +
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js new file mode 100644 index 0000000..bc25beb --- /dev/null +++ b/docs/build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Welcome to whisper_live\u2019s documentation!"], "terms": {"class": 0, "server": 0, "servecli": 0, "websocket": 0, "task": 0, "transcrib": 0, "devic": 0, "none": 0, "multilingu": 0, "fals": 0, "languag": 0, "client_uid": 0, "attribut": 0, "rate": 0, "int": 0, "The": 0, "audio": 0, "sampl": 0, "constant": 0, "set": 0, "16000": 0, "server_readi": 0, "str": 0, "A": 0, "messag": 0, "i": 0, "readi": 0, "disconnect": 0, "client": 0, "should": 0, "uniqu": 0, "identifi": 0, "data": 0, "byte": 0, "accumul": 0, "frame": 0, "transcript": 0, "type": 0, "e": 0, "g": 0, "whispermodel": 0, "whisper": 0, "model": 0, "speech": 0, "text": 0, "timestamp_offset": 0, "float": 0, "offset": 0, "timestamp": 0, "frames_np": 0, "numpi": 0, "ndarrai": 0, "arrai": 0, "store": 0, "frames_offset": 0, "list": 0, "segment": 0, "current_out": 0, "current": 0, "incomplet": 0, "prev_out": 0, "previou": 0, "t_start": 0, "start": 0, "exit": 0, "bool": 0, "flag": 0, "thread": 0, "same_output_threshold": 0, "threshold": 0, "consecut": 0, "same": 0, "output": 0, "show_prev_out_thresh": 0, "show": 0, "add_pause_thresh": 0, "ad": 0, "paus": 0, "blank": 0, "send_last_n_seg": 0, "number": 0, "last": 0, "send": 0, "wrapper": 0, "textwrap": 0, "textwrapp": 0, "format": 0, "pick_previous_seg": 0, "includ": 0, "connect": 0, "add_fram": 0, "frame_np": 0, "add": 0, "ongo": 0, "stream": 0, "buffer": 0, "thi": 0, "method": 0, "respons": 0, "maintain": 0, "allow": 0, "continu": 0, "addit": 0, "thei": 0, "ar": 0, "receiv": 0, "It": 0, "also": 0, "ensur": 0, "doe": 0, "exce": 0, "specifi": 0, "size": 0, "prevent": 0, "excess": 0, "memori": 0, "usag": 0, "If": 0, "45": 0, "second": 0, "discard": 0, "oldest": 0, "30": 0, "reason": 0, "empti": 0, "initi": 0, "provid": 0, "us": 0, "real": 0, "time": 0, "process": 0, "arg": 0, "cleanup": 0, "perform": 0, "befor": 0, "servic": 0, "necessari": 0, "stop": 0, "mark": 0, "gracefulli": 0, "destroi": 0, "resourc": 0, "associ": 0, "notifi": 0, "via": 0, "them": 0, "fill_output": 0, "combin": 0, "complet": 0, "result": 0, "wrap": 0, "two": 0, "line": 0, "each": 0, "contain": 0, "maximum": 0, "50": 0, "charact": 0, "fit": 0, "within": 0, "per": 0, "concaten": 0, "order": 0, "exist": 0, "most": 0, "recent": 0, "first": 0, "older": 0, "prepend": 0, "need": 0, "limit": 0, "3": 0, "detect": 0, "ani": 0, "preced": 0, "content": 0, "return": 0, "singl": 0, "string": 0, "speech_to_text": 0, "an": 0, "infinit": 0, "loop": 0, "wait": 0, "input": 0, "make": 0, "predict": 0, "util": 0, "asr": 0, "sent": 0, "histori": 0, "context": 0, "from": 0, "handl": 0, "durat": 0, "rais": 0, "except": 0, "issu": 0, "commun": 0, "update_seg": 0, "append": 0, "all": 0, "assum": 0, "updat": 0, "end": 0, "chronolog": 0, "one": 0, "repeat": 0, "seen": 0, "multipl": 0, "onli": 0, "onc": 0, "base": 0, "dict": 0, "dictionari": 0, "chunk": 0, "its": 0, "valid": 0, "transcriptionserv": 0, "repres": 0, "incom": 0, "vad_model": 0, "torch": 0, "modul": 0, "voic": 0, "activ": 0, "vad_threshold": 0, "clients_start_tim": 0, "track": 0, "max_client": 0, "max_connection_tim": 0, "get_wait_tim": 0, "calcul": 0, "estim": 0, "minut": 0, "recv_audio": 0, "over": 0, "vad": 0, "determin": 0, "reach": 0, "statu": 0, "until": 0, "slot": 0, "avail": 0, "clean": 0, "up": 0, "error": 0, "dure": 0, "run": 0, "host": 0, "port": 0, "9090": 0, "address": 0, "bind": 0, "index": 0, "search": 0, "page": 0}, "objects": {"whisper_live": [[0, 0, 0, "-", "client"], [0, 0, 0, "-", "server"]], "whisper_live.server": [[0, 1, 1, "", "ServeClient"], [0, 1, 1, "", "TranscriptionServer"]], "whisper_live.server.ServeClient": [[0, 2, 1, "", "add_frames"], [0, 2, 1, "", "cleanup"], [0, 2, 1, "", "disconnect"], [0, 2, 1, "", "fill_output"], [0, 2, 1, "", "speech_to_text"], [0, 2, 1, "", "update_segments"]], "whisper_live.server.TranscriptionServer": [[0, 2, 1, "", "get_wait_time"], [0, 2, 1, "", "recv_audio"], [0, 2, 1, "", "run"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"]}, "titleterms": {"welcom": 0, "whisper_l": 0, "": 0, "document": 0, "indic": 0, "tabl": 0}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"Welcome to whisper_live\u2019s documentation!": [[0, "welcome-to-whisper-live-s-documentation"]], "Indices and tables": [[0, "indices-and-tables"]]}, "indexentries": {"serveclient (class in whisper_live.server)": [[0, "whisper_live.server.ServeClient"]], "transcriptionserver (class in whisper_live.server)": [[0, "whisper_live.server.TranscriptionServer"]], "add_frames() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.add_frames"]], "cleanup() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.cleanup"]], "disconnect() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.disconnect"]], "fill_output() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.fill_output"]], "get_wait_time() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.get_wait_time"]], "module": [[0, "module-whisper_live.client"], [0, "module-whisper_live.server"]], "recv_audio() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.recv_audio"]], "run() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.run"]], "speech_to_text() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.speech_to_text"]], "update_segments() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.update_segments"]], "whisper_live.client": [[0, "module-whisper_live.client"]], "whisper_live.server": [[0, "module-whisper_live.server"]]}}) \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..f944f34 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,36 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + +project = 'whisper_live' +copyright = '2023, Collabora' +author = 'Collabora' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'alabaster' +html_static_path = ['_static'] + +extensions = [ + 'sphinx.ext.duration', + 'sphinx.ext.doctest', + 'sphinx.ext.autodoc', +] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..e57b0ad --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,25 @@ +.. whisper_live documentation master file, created by + sphinx-quickstart on Fri Sep 22 11:39:30 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to whisper_live's documentation! +======================================== + +.. toctree:: + :maxdepth: 2 + + +.. automodule:: whisper_live.server + :members: + +.. automodule:: whisper_live.client + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` From 587ae49ef7d5444a2ecfbd2b9ed22bbdd8610858 Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 22 Sep 2023 12:34:03 +0530 Subject: [PATCH 07/13] mv index.html --- docs/{build => }/html/.buildinfo | 0 docs/{build => }/html/_sources/index.rst.txt | 0 docs/{build => }/html/_static/alabaster.css | 0 docs/{build => }/html/_static/basic.css | 0 docs/{build => }/html/_static/custom.css | 0 docs/{build => }/html/_static/doctools.js | 0 .../html/_static/documentation_options.js | 0 docs/{build => }/html/_static/file.png | Bin docs/{build => }/html/_static/language_data.js | 0 docs/{build => }/html/_static/minus.png | Bin docs/{build => }/html/_static/plus.png | Bin docs/{build => }/html/_static/pygments.css | 0 docs/{build => }/html/_static/searchtools.js | 0 docs/{build => }/html/_static/sphinx_highlight.js | 0 docs/{build => }/html/genindex.html | 0 docs/{build => }/html/index.html | 0 docs/{build => }/html/objects.inv | 0 docs/{build => }/html/py-modindex.html | 0 docs/{build => }/html/search.html | 0 docs/{build => }/html/searchindex.js | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename docs/{build => }/html/.buildinfo (100%) rename docs/{build => }/html/_sources/index.rst.txt (100%) rename docs/{build => }/html/_static/alabaster.css (100%) rename docs/{build => }/html/_static/basic.css (100%) rename docs/{build => }/html/_static/custom.css (100%) rename docs/{build => }/html/_static/doctools.js (100%) rename docs/{build => }/html/_static/documentation_options.js (100%) rename docs/{build => }/html/_static/file.png (100%) rename docs/{build => }/html/_static/language_data.js (100%) rename docs/{build => }/html/_static/minus.png (100%) rename docs/{build => }/html/_static/plus.png (100%) rename docs/{build => }/html/_static/pygments.css (100%) rename docs/{build => }/html/_static/searchtools.js (100%) rename docs/{build => }/html/_static/sphinx_highlight.js (100%) rename docs/{build => }/html/genindex.html (100%) rename docs/{build => }/html/index.html (100%) rename docs/{build => }/html/objects.inv (100%) rename docs/{build => }/html/py-modindex.html (100%) rename docs/{build => }/html/search.html (100%) rename docs/{build => }/html/searchindex.js (100%) diff --git a/docs/build/html/.buildinfo b/docs/html/.buildinfo similarity index 100% rename from docs/build/html/.buildinfo rename to docs/html/.buildinfo diff --git a/docs/build/html/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt similarity index 100% rename from docs/build/html/_sources/index.rst.txt rename to docs/html/_sources/index.rst.txt diff --git a/docs/build/html/_static/alabaster.css b/docs/html/_static/alabaster.css similarity index 100% rename from docs/build/html/_static/alabaster.css rename to docs/html/_static/alabaster.css diff --git a/docs/build/html/_static/basic.css b/docs/html/_static/basic.css similarity index 100% rename from docs/build/html/_static/basic.css rename to docs/html/_static/basic.css diff --git a/docs/build/html/_static/custom.css b/docs/html/_static/custom.css similarity index 100% rename from docs/build/html/_static/custom.css rename to docs/html/_static/custom.css diff --git a/docs/build/html/_static/doctools.js b/docs/html/_static/doctools.js similarity index 100% rename from docs/build/html/_static/doctools.js rename to docs/html/_static/doctools.js diff --git a/docs/build/html/_static/documentation_options.js b/docs/html/_static/documentation_options.js similarity index 100% rename from docs/build/html/_static/documentation_options.js rename to docs/html/_static/documentation_options.js diff --git a/docs/build/html/_static/file.png b/docs/html/_static/file.png similarity index 100% rename from docs/build/html/_static/file.png rename to docs/html/_static/file.png diff --git a/docs/build/html/_static/language_data.js b/docs/html/_static/language_data.js similarity index 100% rename from docs/build/html/_static/language_data.js rename to docs/html/_static/language_data.js diff --git a/docs/build/html/_static/minus.png b/docs/html/_static/minus.png similarity index 100% rename from docs/build/html/_static/minus.png rename to docs/html/_static/minus.png diff --git a/docs/build/html/_static/plus.png b/docs/html/_static/plus.png similarity index 100% rename from docs/build/html/_static/plus.png rename to docs/html/_static/plus.png diff --git a/docs/build/html/_static/pygments.css b/docs/html/_static/pygments.css similarity index 100% rename from docs/build/html/_static/pygments.css rename to docs/html/_static/pygments.css diff --git a/docs/build/html/_static/searchtools.js b/docs/html/_static/searchtools.js similarity index 100% rename from docs/build/html/_static/searchtools.js rename to docs/html/_static/searchtools.js diff --git a/docs/build/html/_static/sphinx_highlight.js b/docs/html/_static/sphinx_highlight.js similarity index 100% rename from docs/build/html/_static/sphinx_highlight.js rename to docs/html/_static/sphinx_highlight.js diff --git a/docs/build/html/genindex.html b/docs/html/genindex.html similarity index 100% rename from docs/build/html/genindex.html rename to docs/html/genindex.html diff --git a/docs/build/html/index.html b/docs/html/index.html similarity index 100% rename from docs/build/html/index.html rename to docs/html/index.html diff --git a/docs/build/html/objects.inv b/docs/html/objects.inv similarity index 100% rename from docs/build/html/objects.inv rename to docs/html/objects.inv diff --git a/docs/build/html/py-modindex.html b/docs/html/py-modindex.html similarity index 100% rename from docs/build/html/py-modindex.html rename to docs/html/py-modindex.html diff --git a/docs/build/html/search.html b/docs/html/search.html similarity index 100% rename from docs/build/html/search.html rename to docs/html/search.html diff --git a/docs/build/html/searchindex.js b/docs/html/searchindex.js similarity index 100% rename from docs/build/html/searchindex.js rename to docs/html/searchindex.js From 489153df226c27fe76ec62b58de4c91cf3aa90ff Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 22 Sep 2023 12:35:11 +0530 Subject: [PATCH 08/13] mv index html to top level in docs --- docs/{html => }/_sources/index.rst.txt | 0 docs/{html => }/_static/alabaster.css | 0 docs/{html => }/_static/basic.css | 0 docs/{html => }/_static/custom.css | 0 docs/{html => }/_static/doctools.js | 0 .../{html => }/_static/documentation_options.js | 0 docs/{html => }/_static/file.png | Bin docs/{html => }/_static/language_data.js | 0 docs/{html => }/_static/minus.png | Bin docs/{html => }/_static/plus.png | Bin docs/{html => }/_static/pygments.css | 0 docs/{html => }/_static/searchtools.js | 0 docs/{html => }/_static/sphinx_highlight.js | 0 docs/build/doctrees/environment.pickle | Bin 78578 -> 0 bytes docs/build/doctrees/index.doctree | Bin 48761 -> 0 bytes docs/{html => }/genindex.html | 0 docs/html/.buildinfo | 4 ---- docs/{html => }/index.html | 0 docs/{html => }/objects.inv | 0 docs/{html => }/py-modindex.html | 0 docs/{html => }/search.html | 0 docs/{html => }/searchindex.js | 0 22 files changed, 4 deletions(-) rename docs/{html => }/_sources/index.rst.txt (100%) rename docs/{html => }/_static/alabaster.css (100%) rename docs/{html => }/_static/basic.css (100%) rename docs/{html => }/_static/custom.css (100%) rename docs/{html => }/_static/doctools.js (100%) rename docs/{html => }/_static/documentation_options.js (100%) rename docs/{html => }/_static/file.png (100%) rename docs/{html => }/_static/language_data.js (100%) rename docs/{html => }/_static/minus.png (100%) rename docs/{html => }/_static/plus.png (100%) rename docs/{html => }/_static/pygments.css (100%) rename docs/{html => }/_static/searchtools.js (100%) rename docs/{html => }/_static/sphinx_highlight.js (100%) delete mode 100644 docs/build/doctrees/environment.pickle delete mode 100644 docs/build/doctrees/index.doctree rename docs/{html => }/genindex.html (100%) delete mode 100644 docs/html/.buildinfo rename docs/{html => }/index.html (100%) rename docs/{html => }/objects.inv (100%) rename docs/{html => }/py-modindex.html (100%) rename docs/{html => }/search.html (100%) rename docs/{html => }/searchindex.js (100%) diff --git a/docs/html/_sources/index.rst.txt b/docs/_sources/index.rst.txt similarity index 100% rename from docs/html/_sources/index.rst.txt rename to docs/_sources/index.rst.txt diff --git a/docs/html/_static/alabaster.css b/docs/_static/alabaster.css similarity index 100% rename from docs/html/_static/alabaster.css rename to docs/_static/alabaster.css diff --git a/docs/html/_static/basic.css b/docs/_static/basic.css similarity index 100% rename from docs/html/_static/basic.css rename to docs/_static/basic.css diff --git a/docs/html/_static/custom.css b/docs/_static/custom.css similarity index 100% rename from docs/html/_static/custom.css rename to docs/_static/custom.css diff --git a/docs/html/_static/doctools.js b/docs/_static/doctools.js similarity index 100% rename from docs/html/_static/doctools.js rename to docs/_static/doctools.js diff --git a/docs/html/_static/documentation_options.js b/docs/_static/documentation_options.js similarity index 100% rename from docs/html/_static/documentation_options.js rename to docs/_static/documentation_options.js diff --git a/docs/html/_static/file.png b/docs/_static/file.png similarity index 100% rename from docs/html/_static/file.png rename to docs/_static/file.png diff --git a/docs/html/_static/language_data.js b/docs/_static/language_data.js similarity index 100% rename from docs/html/_static/language_data.js rename to docs/_static/language_data.js diff --git a/docs/html/_static/minus.png b/docs/_static/minus.png similarity index 100% rename from docs/html/_static/minus.png rename to docs/_static/minus.png diff --git a/docs/html/_static/plus.png b/docs/_static/plus.png similarity index 100% rename from docs/html/_static/plus.png rename to docs/_static/plus.png diff --git a/docs/html/_static/pygments.css b/docs/_static/pygments.css similarity index 100% rename from docs/html/_static/pygments.css rename to docs/_static/pygments.css diff --git a/docs/html/_static/searchtools.js b/docs/_static/searchtools.js similarity index 100% rename from docs/html/_static/searchtools.js rename to docs/_static/searchtools.js diff --git a/docs/html/_static/sphinx_highlight.js b/docs/_static/sphinx_highlight.js similarity index 100% rename from docs/html/_static/sphinx_highlight.js rename to docs/_static/sphinx_highlight.js diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle deleted file mode 100644 index b0c06917e3ee28c8299b0c173e54ef7a10172d79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78578 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 diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree deleted file mode 100644 index 7151b12e7159ddd82ae1cff24663f5ca89d8e8af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48761 zcmeHwdypK*c^_W)TD*9G2oj=b4#5ZZK->ZZMM^vpBu@}&(U2e-fJw*+w4B?WyP3i6 z&T?lKz|m2OR+$V@)k$P?Em?{rJ5@Z(QT30J> zz21uIUfdJY^{Ccudab1QR&Va-d!Oi)#Z1S&5=Y%m&FhIRc%$YAje5sx^)BM!9(q3s z8gWH6Fjb41nfm^O_{J4Kh}&ML+6XRtj_$!tf~e&*-8k_&&T`Q3jykoD=O$j=Sz0a0 zka@WTs_JihSAap%PG6OU9%AB(z9&22eu-EAjM zg8sNk2cu~>yu@p)I!WZL0t}}STnbt%N1df^;sl8k`%$-1cihBjco<8m7PS&LXrT?y z=|oZDyqrX}q~m!nJN2OB)e^u^>G@L^d;W~rao)R1sQOU^@%0|jUre@L5IgDlyW%BR zl-wlg1V9~_g_vV|P>;a~Vyf0~;~0hbKGAZU^4oO1+crOz+}fp%x6JR%Tx~WM;&-m-Md=p#7R%=Xap^~EJ3DM0{VmgVFq>vz%S-8 zB=#!!=C5?zHWr?qn@8HyS)>8UK#PJExk9z1eS-O-t%KZ!Y}xT#l_L)xjo=x z64jOV>Hq#6fUosw=Rvm}HTZpv4@ zr8ufx@{*%Y;>MRw9!@%LE3RS9^bQ|&>fYs`=AAqfwIJ=9-3BBlhSzl)C!cd0G0GY) z{an$n)RL*4qnh1HWtbiE z(4gy|4M$g5>;wbOLtJu?VmA$d3C6!Ydy&&f`kFzK|5f659{LOqJwNW979(p?X3ESc z|4q>-r)iXOh||>3@X>u^;OO@8=%9B1uTTyf0fe@N-itSMj-^vf&iHf`_6TOu5lA#` zk))vIp_)f({@qz&l^BG6e>g(L)>i*4`Jcetf6EKLK@9Lcy!c;1^gq@TM9lUrObX^d zNnhDPuV@yKKhmLwrL9}G3?Hrk|N4Y2#u`qd60_bc6PGX*K{C5Z)Uh$U^`!0<-%OY9 zF6>fljrtH2-bq!`ej%2E7OQ<0Bki&mXWehM&>{nG&TY#~rXNV$kg-KFp# z{-g{ugl^{w+YcYW<2<)$!l_=4*uCr^!KtBioUYsq;?@+b^V!>Lo*+!fEt-#eJ=VHgX8-q0Qc${@X{L zl6gVX$1XAy`lCXqhEV7aMyJr97PenVA=S(16xy<_NdAWFppYEG$P_AmQ5@uj4bHxn zS54I?{Dc-qGSC0Rex=W9@l!52IpA4>iqI1!QYE9M5I&k-M!yx4(vt3p{R6aZIH9B= z<4=m|0P;ZszKs4t=2Ale`3p)#njEVP&&JK{#mCSw34|6 z=RCZUZnqvpPVB<7NwzI?C=WJ$)PkE7?>Mms2WaFx{Mhm1$1A0C&z^np*|XKN&n|xO zV@?_7FdA8eTU5U7z)=^&+=Mfr9@Jn-qP@h2FOWN zv!~9F*^->4+SOVQV)WkvNh{W5HBEL~!7E)134Wp^SPsZL9Cdg^vYkq)?k28NURs43 zMc~xnvhFt6b1ZQ~ν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 Date: Fri, 22 Sep 2023 12:45:41 +0530 Subject: [PATCH 09/13] add nojekyll to resolve theme --- docs/.nojekyll | 0 docs/Makefile | 20 -- docs/doctrees/environment.pickle | Bin 0 -> 78215 bytes docs/doctrees/index.doctree | Bin 0 -> 48767 bytes docs/html/.buildinfo | 4 + docs/{ => html}/_sources/index.rst.txt | 0 docs/{ => html}/_static/alabaster.css | 0 docs/{ => html}/_static/basic.css | 0 docs/{ => html}/_static/custom.css | 0 docs/{ => html}/_static/doctools.js | 0 .../_static/documentation_options.js | 0 docs/{ => html}/_static/file.png | Bin docs/{ => html}/_static/language_data.js | 0 docs/{ => html}/_static/minus.png | Bin docs/{ => html}/_static/plus.png | Bin docs/{ => html}/_static/pygments.css | 0 docs/{ => html}/_static/searchtools.js | 0 docs/{ => html}/_static/sphinx_highlight.js | 0 docs/{ => html}/genindex.html | 0 docs/html/index.html | 295 +++++++++++++++++ docs/{ => html}/objects.inv | 0 docs/{ => html}/py-modindex.html | 0 docs/{ => html}/search.html | 0 docs/{ => html}/searchindex.js | 0 docs/index.html | 296 +----------------- docs/make.bat | 35 --- docs/source/conf.py | 36 --- docs/source/index.rst | 25 -- 28 files changed, 300 insertions(+), 411 deletions(-) create mode 100644 docs/.nojekyll delete mode 100644 docs/Makefile create mode 100644 docs/doctrees/environment.pickle create mode 100644 docs/doctrees/index.doctree create mode 100644 docs/html/.buildinfo rename docs/{ => html}/_sources/index.rst.txt (100%) rename docs/{ => html}/_static/alabaster.css (100%) rename docs/{ => html}/_static/basic.css (100%) rename docs/{ => html}/_static/custom.css (100%) rename docs/{ => html}/_static/doctools.js (100%) rename docs/{ => html}/_static/documentation_options.js (100%) rename docs/{ => html}/_static/file.png (100%) rename docs/{ => html}/_static/language_data.js (100%) rename docs/{ => html}/_static/minus.png (100%) rename docs/{ => html}/_static/plus.png (100%) rename docs/{ => html}/_static/pygments.css (100%) rename docs/{ => html}/_static/searchtools.js (100%) rename docs/{ => html}/_static/sphinx_highlight.js (100%) rename docs/{ => html}/genindex.html (100%) create mode 100644 docs/html/index.html rename docs/{ => html}/objects.inv (100%) rename docs/{ => html}/py-modindex.html (100%) rename docs/{ => html}/search.html (100%) rename docs/{ => html}/searchindex.js (100%) delete mode 100644 docs/make.bat delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.rst diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d0c3cbf..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..20a7ea1c42d90270957e08b2087b93984d7fa334 GIT binary patch literal 78215 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⁢O7WjwCn~{VXDD&)jMU!%UlS? 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 W+7OG{3JIoCousi6Tyk%_^8W$NVS)ny literal 0 HcmV?d00001 diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo new file mode 100644 index 0000000..7598238 --- /dev/null +++ b/docs/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 7b818b47e6f359b937e5a2517f120d43 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt similarity index 100% rename from docs/_sources/index.rst.txt rename to docs/html/_sources/index.rst.txt diff --git a/docs/_static/alabaster.css b/docs/html/_static/alabaster.css similarity index 100% rename from docs/_static/alabaster.css rename to docs/html/_static/alabaster.css diff --git a/docs/_static/basic.css b/docs/html/_static/basic.css similarity index 100% rename from docs/_static/basic.css rename to docs/html/_static/basic.css diff --git a/docs/_static/custom.css b/docs/html/_static/custom.css similarity index 100% rename from docs/_static/custom.css rename to docs/html/_static/custom.css diff --git a/docs/_static/doctools.js b/docs/html/_static/doctools.js similarity index 100% rename from docs/_static/doctools.js rename to docs/html/_static/doctools.js diff --git a/docs/_static/documentation_options.js b/docs/html/_static/documentation_options.js similarity index 100% rename from docs/_static/documentation_options.js rename to docs/html/_static/documentation_options.js diff --git a/docs/_static/file.png b/docs/html/_static/file.png similarity index 100% rename from docs/_static/file.png rename to docs/html/_static/file.png diff --git a/docs/_static/language_data.js b/docs/html/_static/language_data.js similarity index 100% rename from docs/_static/language_data.js rename to docs/html/_static/language_data.js diff --git a/docs/_static/minus.png b/docs/html/_static/minus.png similarity index 100% rename from docs/_static/minus.png rename to docs/html/_static/minus.png diff --git a/docs/_static/plus.png b/docs/html/_static/plus.png similarity index 100% rename from docs/_static/plus.png rename to docs/html/_static/plus.png diff --git a/docs/_static/pygments.css b/docs/html/_static/pygments.css similarity index 100% rename from docs/_static/pygments.css rename to docs/html/_static/pygments.css diff --git a/docs/_static/searchtools.js b/docs/html/_static/searchtools.js similarity index 100% rename from docs/_static/searchtools.js rename to docs/html/_static/searchtools.js diff --git a/docs/_static/sphinx_highlight.js b/docs/html/_static/sphinx_highlight.js similarity index 100% rename from docs/_static/sphinx_highlight.js rename to docs/html/_static/sphinx_highlight.js diff --git a/docs/genindex.html b/docs/html/genindex.html similarity index 100% rename from docs/genindex.html rename to docs/html/genindex.html diff --git a/docs/html/index.html b/docs/html/index.html new file mode 100644 index 0000000..cd207e0 --- /dev/null +++ b/docs/html/index.html @@ -0,0 +1,295 @@ + + + + + + + + Welcome to whisper_live’s documentation! — whisper_live documentation + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

Welcome to whisper_live’s documentation!

+
+
+
+
+class whisper_live.server.ServeClient(websocket, task='transcribe', device=None, multilingual=False, language=None, client_uid=None)
+
+
Attributes:

RATE (int): The audio sampling rate (constant) set to 16000. +SERVER_READY (str): A constant message indicating that the server is ready. +DISCONNECT (str): A constant message indicating that the client should disconnect. +client_uid (str): A unique identifier for the client. +data (bytes): Accumulated audio data. +frames (bytes): Accumulated audio frames. +language (str): The language for transcription. +task (str): The task type, e.g., “transcribe.” +transcriber (WhisperModel): The Whisper model for speech-to-text. +timestamp_offset (float): The offset in audio timestamps. +frames_np (numpy.ndarray): NumPy array to store audio frames. +frames_offset (float): The offset in audio frames. +text (list): List of transcribed text segments. +current_out (str): The current incomplete transcription. +prev_out (str): The previous incomplete transcription. +t_start (float): Timestamp for the start of transcription. +exit (bool): A flag to exit the transcription thread. +same_output_threshold (int): Threshold for consecutive same output segments. +show_prev_out_thresh (int): Threshold for showing previous output segments. +add_pause_thresh (int): Threshold for adding a pause (blank) segment. +transcript (list): List of transcribed segments. +send_last_n_segments (int): Number of last segments to send to the client. +wrapper (textwrap.TextWrapper): Text wrapper for formatting text. +pick_previous_segments (int): Number of previous segments to include in the output. +websocket: The WebSocket connection for the client.

+
+
+
+
+add_frames(frame_np)
+

Add audio frames to the ongoing audio stream buffer.

+

This method is responsible for maintaining the audio stream buffer, allowing the continuous addition +of audio frames as they are received. It also ensures that the buffer does not exceed a specified size +to prevent excessive memory usage.

+

If the buffer size exceeds a threshold (45 seconds of audio data), it discards the oldest 30 seconds +of audio data to maintain a reasonable buffer size. If the buffer is empty, it initializes it with the provided +audio frame. The audio stream buffer is used for real-time processing of audio data for transcription.

+
+
Args:

frame_np (numpy.ndarray): The audio frame data as a NumPy array.

+
+
+
+ +
+
+cleanup()
+

Perform cleanup tasks before exiting the transcription service.

+

This method performs necessary cleanup tasks, including stopping the transcription thread, marking +the exit flag to indicate the transcription thread should exit gracefully, and destroying resources +associated with the transcription process.

+
+ +
+
+disconnect()
+

Notify the client of disconnection and send a disconnect message.

+

This method sends a disconnect message to the client via the WebSocket connection to notify them +that the transcription service is disconnecting gracefully.

+
+ +
+
+fill_output(output)
+

Format the current incomplete transcription output by combining it with previous complete segments. +The resulting transcription is wrapped into two lines, each containing a maximum of 50 characters.

+

It ensures that the combined transcription fits within two lines, with a maximum of 50 characters per line. +Segments are concatenated in the order they exist in the list of previous segments, with the most +recent complete segment first and older segments prepended as needed to maintain the character limit. +If a 3-second pause is detected in the previous segments, any text preceding it is discarded to ensure +the transcription starts with the most recent complete content. The resulting transcription is returned +as a single string.

+
+
Args:

output(str): The current incomplete transcription segment.

+
+
Returns:

str: A formatted transcription wrapped in two lines.

+
+
+
+ +
+
+speech_to_text()
+

Process an audio stream in an infinite loop, continuously transcribing the speech.

+

This method continuously receives audio frames, performs real-time transcription, and sends +transcribed segments to the client via a WebSocket connection.

+

If the client’s language is not detected, it waits for 30 seconds of audio input to make a language prediction. +It utilizes the Whisper ASR model to transcribe the audio, continuously processing and streaming results. Segments +are sent to the client in real-time, and a history of segments is maintained to provide context.Pauses in speech +(no output from Whisper) are handled by showing the previous output for a set duration. A blank segment is added if +there is no speech for a specified duration to indicate a pause.

+
+
Raises:

Exception: If there is an issue with audio processing or WebSocket communication.

+
+
+
+ +
+
+update_segments(segments, duration)
+

Processes the segments from whisper. Appends all the segments to the list +except for the last segment assuming that it is incomplete.

+

Updates the ongoing transcript with transcribed segments, including their start and end times. +Complete segments are appended to the transcript in chronological order. Incomplete segments +(assumed to be the last one) are processed to identify repeated content. If the same incomplete +segment is seen multiple times, it updates the offset and appends the segment to the transcript. +A threshold is used to detect repeated content and ensure it is only included once in the transcript. +The timestamp offset is updated based on the duration of processed segments. The method returns the +last processed segment, allowing it to be sent to the client for real-time updates.

+
+
Args:

segments(dict) : dictionary of segments as returned by whisper +duration(float): duration of the current chunk

+
+
Returns:
+
dict or None: The last processed segment with its start time, end time, and transcribed text.

Returns None if there are no valid segments to process.

+
+
+
+
+
+ +
+ +
+
+class whisper_live.server.TranscriptionServer
+

Represents a transcription server that handles incoming audio from clients.

+
+
Attributes:

RATE (int): The audio sampling rate (constant) set to 16000. +vad_model (torch.Module): The voice activity detection model. +vad_threshold (float): The voice activity detection threshold. +clients (dict): A dictionary to store connected clients. +websockets (dict): A dictionary to store WebSocket connections. +clients_start_time (dict): A dictionary to track client start times. +max_clients (int): Maximum allowed connected clients. +max_connection_time (int): Maximum allowed connection time in seconds.

+
+
+
+
+get_wait_time()
+

Calculate and return the estimated wait time for clients.

+
+
Returns:

float: The estimated wait time in minutes.

+
+
+
+ +
+
+recv_audio(websocket)
+

Receive audio chunks from a client in an infinite loop.

+

Continuously receives audio frames from a connected client +over a WebSocket connection. It processes the audio frames using a +voice activity detection (VAD) model to determine if they contain speech +or not. If the audio frame contains speech, it is added to the client’s +audio data for ASR. +If the maximum number of clients is reached, the method sends a +“WAIT” status to the client, indicating that they should wait +until a slot is available. +If a client’s connection exceeds the maximum allowed time, it will +be disconnected, and the client’s resources will be cleaned up.

+
+
Args:

websocket (WebSocket): The WebSocket connection for the client.

+
+
Raises:

Exception: If there is an error during the audio frame processing.

+
+
+
+ +
+
+run(host, port=9090)
+

Run the transcription server.

+
+
Args:

host (str): The host address to bind the server. +port (int): The port number to bind the server.

+
+
+
+ +
+ +
+
+

Indices and tables

+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/docs/objects.inv b/docs/html/objects.inv similarity index 100% rename from docs/objects.inv rename to docs/html/objects.inv diff --git a/docs/py-modindex.html b/docs/html/py-modindex.html similarity index 100% rename from docs/py-modindex.html rename to docs/html/py-modindex.html diff --git a/docs/search.html b/docs/html/search.html similarity index 100% rename from docs/search.html rename to docs/html/search.html diff --git a/docs/searchindex.js b/docs/html/searchindex.js similarity index 100% rename from docs/searchindex.js rename to docs/html/searchindex.js diff --git a/docs/index.html b/docs/index.html index cd207e0..5896b1d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,295 +1 @@ - - - - - - - - Welcome to whisper_live’s documentation! — whisper_live documentation - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

Welcome to whisper_live’s documentation!

-
-
-
-
-class whisper_live.server.ServeClient(websocket, task='transcribe', device=None, multilingual=False, language=None, client_uid=None)
-
-
Attributes:

RATE (int): The audio sampling rate (constant) set to 16000. -SERVER_READY (str): A constant message indicating that the server is ready. -DISCONNECT (str): A constant message indicating that the client should disconnect. -client_uid (str): A unique identifier for the client. -data (bytes): Accumulated audio data. -frames (bytes): Accumulated audio frames. -language (str): The language for transcription. -task (str): The task type, e.g., “transcribe.” -transcriber (WhisperModel): The Whisper model for speech-to-text. -timestamp_offset (float): The offset in audio timestamps. -frames_np (numpy.ndarray): NumPy array to store audio frames. -frames_offset (float): The offset in audio frames. -text (list): List of transcribed text segments. -current_out (str): The current incomplete transcription. -prev_out (str): The previous incomplete transcription. -t_start (float): Timestamp for the start of transcription. -exit (bool): A flag to exit the transcription thread. -same_output_threshold (int): Threshold for consecutive same output segments. -show_prev_out_thresh (int): Threshold for showing previous output segments. -add_pause_thresh (int): Threshold for adding a pause (blank) segment. -transcript (list): List of transcribed segments. -send_last_n_segments (int): Number of last segments to send to the client. -wrapper (textwrap.TextWrapper): Text wrapper for formatting text. -pick_previous_segments (int): Number of previous segments to include in the output. -websocket: The WebSocket connection for the client.

-
-
-
-
-add_frames(frame_np)
-

Add audio frames to the ongoing audio stream buffer.

-

This method is responsible for maintaining the audio stream buffer, allowing the continuous addition -of audio frames as they are received. It also ensures that the buffer does not exceed a specified size -to prevent excessive memory usage.

-

If the buffer size exceeds a threshold (45 seconds of audio data), it discards the oldest 30 seconds -of audio data to maintain a reasonable buffer size. If the buffer is empty, it initializes it with the provided -audio frame. The audio stream buffer is used for real-time processing of audio data for transcription.

-
-
Args:

frame_np (numpy.ndarray): The audio frame data as a NumPy array.

-
-
-
- -
-
-cleanup()
-

Perform cleanup tasks before exiting the transcription service.

-

This method performs necessary cleanup tasks, including stopping the transcription thread, marking -the exit flag to indicate the transcription thread should exit gracefully, and destroying resources -associated with the transcription process.

-
- -
-
-disconnect()
-

Notify the client of disconnection and send a disconnect message.

-

This method sends a disconnect message to the client via the WebSocket connection to notify them -that the transcription service is disconnecting gracefully.

-
- -
-
-fill_output(output)
-

Format the current incomplete transcription output by combining it with previous complete segments. -The resulting transcription is wrapped into two lines, each containing a maximum of 50 characters.

-

It ensures that the combined transcription fits within two lines, with a maximum of 50 characters per line. -Segments are concatenated in the order they exist in the list of previous segments, with the most -recent complete segment first and older segments prepended as needed to maintain the character limit. -If a 3-second pause is detected in the previous segments, any text preceding it is discarded to ensure -the transcription starts with the most recent complete content. The resulting transcription is returned -as a single string.

-
-
Args:

output(str): The current incomplete transcription segment.

-
-
Returns:

str: A formatted transcription wrapped in two lines.

-
-
-
- -
-
-speech_to_text()
-

Process an audio stream in an infinite loop, continuously transcribing the speech.

-

This method continuously receives audio frames, performs real-time transcription, and sends -transcribed segments to the client via a WebSocket connection.

-

If the client’s language is not detected, it waits for 30 seconds of audio input to make a language prediction. -It utilizes the Whisper ASR model to transcribe the audio, continuously processing and streaming results. Segments -are sent to the client in real-time, and a history of segments is maintained to provide context.Pauses in speech -(no output from Whisper) are handled by showing the previous output for a set duration. A blank segment is added if -there is no speech for a specified duration to indicate a pause.

-
-
Raises:

Exception: If there is an issue with audio processing or WebSocket communication.

-
-
-
- -
-
-update_segments(segments, duration)
-

Processes the segments from whisper. Appends all the segments to the list -except for the last segment assuming that it is incomplete.

-

Updates the ongoing transcript with transcribed segments, including their start and end times. -Complete segments are appended to the transcript in chronological order. Incomplete segments -(assumed to be the last one) are processed to identify repeated content. If the same incomplete -segment is seen multiple times, it updates the offset and appends the segment to the transcript. -A threshold is used to detect repeated content and ensure it is only included once in the transcript. -The timestamp offset is updated based on the duration of processed segments. The method returns the -last processed segment, allowing it to be sent to the client for real-time updates.

-
-
Args:

segments(dict) : dictionary of segments as returned by whisper -duration(float): duration of the current chunk

-
-
Returns:
-
dict or None: The last processed segment with its start time, end time, and transcribed text.

Returns None if there are no valid segments to process.

-
-
-
-
-
- -
- -
-
-class whisper_live.server.TranscriptionServer
-

Represents a transcription server that handles incoming audio from clients.

-
-
Attributes:

RATE (int): The audio sampling rate (constant) set to 16000. -vad_model (torch.Module): The voice activity detection model. -vad_threshold (float): The voice activity detection threshold. -clients (dict): A dictionary to store connected clients. -websockets (dict): A dictionary to store WebSocket connections. -clients_start_time (dict): A dictionary to track client start times. -max_clients (int): Maximum allowed connected clients. -max_connection_time (int): Maximum allowed connection time in seconds.

-
-
-
-
-get_wait_time()
-

Calculate and return the estimated wait time for clients.

-
-
Returns:

float: The estimated wait time in minutes.

-
-
-
- -
-
-recv_audio(websocket)
-

Receive audio chunks from a client in an infinite loop.

-

Continuously receives audio frames from a connected client -over a WebSocket connection. It processes the audio frames using a -voice activity detection (VAD) model to determine if they contain speech -or not. If the audio frame contains speech, it is added to the client’s -audio data for ASR. -If the maximum number of clients is reached, the method sends a -“WAIT” status to the client, indicating that they should wait -until a slot is available. -If a client’s connection exceeds the maximum allowed time, it will -be disconnected, and the client’s resources will be cleaned up.

-
-
Args:

websocket (WebSocket): The WebSocket connection for the client.

-
-
Raises:

Exception: If there is an error during the audio frame processing.

-
-
-
- -
-
-run(host, port=9090)
-

Run the transcription server.

-
-
Args:

host (str): The host address to bind the server. -port (int): The port number to bind the server.

-
-
-
- -
- -
-
-

Indices and tables

- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 747ffb7..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index f944f34..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,36 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -import os -import sys -sys.path.insert(0, os.path.abspath('../..')) - -project = 'whisper_live' -copyright = '2023, Collabora' -author = 'Collabora' - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = [] - -templates_path = ['_templates'] -exclude_patterns = [] - - - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = 'alabaster' -html_static_path = ['_static'] - -extensions = [ - 'sphinx.ext.duration', - 'sphinx.ext.doctest', - 'sphinx.ext.autodoc', -] diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index e57b0ad..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. whisper_live documentation master file, created by - sphinx-quickstart on Fri Sep 22 11:39:30 2023. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to whisper_live's documentation! -======================================== - -.. toctree:: - :maxdepth: 2 - - -.. automodule:: whisper_live.server - :members: - -.. automodule:: whisper_live.client - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` From 2f2233a9afd12b17d229f1efa302c309c491e274 Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 22 Sep 2023 12:51:21 +0530 Subject: [PATCH 10/13] update resmaple_file docstring --- whisper_live/client.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/whisper_live/client.py b/whisper_live/client.py index 76363a5..4d44438 100644 --- a/whisper_live/client.py +++ b/whisper_live/client.py @@ -19,12 +19,13 @@ 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 - Parameters - ---------- - file: str - The audio file to open - sr: int - The sample rate to resample the audio if necessary + + 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. From 92887919434f774715e2e83200f76005ff9a4739 Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Fri, 22 Sep 2023 12:52:16 +0530 Subject: [PATCH 11/13] update resmapled file documentation --- docs/doctrees/environment.pickle | Bin 78215 -> 150721 bytes docs/doctrees/index.doctree | Bin 48767 -> 99902 bytes docs/html/_sources/index.rst.txt | 3 +- docs/html/genindex.html | 61 +++++++++- docs/html/index.html | 200 ++++++++++++++++++++++++++++++- docs/html/objects.inv | 4 +- docs/html/searchindex.js | 2 +- 7 files changed, 258 insertions(+), 12 deletions(-) diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle index 20a7ea1c42d90270957e08b2087b93984d7fa334..62e3147cab2380a62a65ce07f6663c4718a3bdc8 100644 GIT binary patch literal 150721 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 literal 78215 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⁢O7WjwCn~{VXDD&)jMU!%UlS? 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 diff --git a/docs/html/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt index e57b0ad..acc105e 100644 --- a/docs/html/_sources/index.rst.txt +++ b/docs/html/_sources/index.rst.txt @@ -3,7 +3,7 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to whisper_live's documentation! +Welcome to Whisper Live documentation! ======================================== .. toctree:: @@ -14,6 +14,7 @@ Welcome to whisper_live's documentation! :members: .. automodule:: whisper_live.client + :members: diff --git a/docs/html/genindex.html b/docs/html/genindex.html index 548b525..7b9a20f 100644 --- a/docs/html/genindex.html +++ b/docs/html/genindex.html @@ -33,11 +33,14 @@
A + | B | C | D | F | G | M + | O + | P | R | S | T @@ -53,10 +56,24 @@ +

B

+ + +
+

C

+
@@ -79,6 +96,10 @@

G

+
+

O

+ + + +
+ +

P

+ + +
+

R

@@ -115,10 +160,12 @@

S

@@ -126,6 +173,10 @@

T

+ - +
-
  • whisper_live.server @@ -160,6 +209,12 @@
diff --git a/docs/html/index.html b/docs/html/index.html index cd207e0..60f8c65 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -5,7 +5,7 @@ - Welcome to whisper_live’s documentation! — whisper_live documentation + Welcome to Whisper Live documentation! — whisper_live documentation @@ -29,8 +29,8 @@
-
-

Welcome to whisper_live’s documentation!

+
+

Welcome to Whisper Live documentation!

@@ -219,9 +219,201 @@ port (int): The port number to bind the server.

+
+
+class whisper_live.client.Client(host=None, port=None, is_multilingual=False, lang=None, translate=False)
+

Represents a client for audio recording and streaming to a server using WebSocket communication.

+

This class allows audio recording from the microphone or playing audio from a file while streaming it to +a server for transcription or translation. It uses PyAudio for audio recording and playback and WebSocket +for communication with the server.

+
+
Attributes:

CHUNK (int): The size of audio chunks for recording and playback. +FORMAT: The audio format used by PyAudio (paInt16 for 16-bit PCM). +CHANNELS (int): The number of audio channels (1 for mono). +RATE (int): The audio sampling rate in Hz (samples per second). +RECORD_SECONDS (int): The maximum duration for audio recording in seconds. +RECORDING (bool): Indicates whether recording is currently active. +multilingual (bool): Indicates if multilingual transcription is enabled. +language (str): The selected language for transcription. +task (str): The transcription or translation task to be performed. +uid (str): A unique identifier for the client. +WAITING (bool): Indicates if the client is waiting for server availability. +LAST_RESPONSE_RECIEVED (float): Timestamp of the last response received from the server. +DISCONNECT_IF_NO_RESPONSE_FOR (int): Maximum time without server response before disconnection.

+
+
+
+
+static 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 +have values between -1 and 1.

+
+
Args:

audio_bytes (bytes): Audio data in bytes.

+
+
Returns:

np.ndarray: A NumPy array containing the audio data as float values normalized between -1 and 1.

+
+
+
+ +
+
+close_websocket()
+

Close the WebSocket connection and join the WebSocket thread.

+

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.

+
+ +
+
+get_client_socket()
+

Get the WebSocket client socket instance.

+
+
Returns:

WebSocketApp: The WebSocket client socket instance currently in use by the client.

+
+
+
+ +
+
+static on_message(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.

+
+
Args:

ws (websocket.WebSocketApp): The WebSocket client instance. +message (str): The received message from the server.

+
+
+
+ +
+
+static on_open(ws)
+

Callback function called when the WebSocket connection is successfully opened.

+

Sends an initial configuration message to the server, including client UID, multilingual mode, +language selection, and task type.

+
+
Args:

ws (websocket.WebSocketApp): The WebSocket client instance.

+
+
+
+ +
+
+play_file(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 +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.

+
+
Args:

filename (str): The path to the audio file to be played and sent to the server.

+
+
+
+ +
+
+record(out_file='output_recording.wav')
+

Record audio data from the input stream and save it to a WAV file.

+

Continuously records audio data from the input stream, sends it to the server via a WebSocket +connection, and simultaneously saves it to multiple WAV files in chunks. It stops recording when +the RECORD_SECONDS duration is reached or when the RECORDING flag is set to False.

+

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 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”.

+
+
+
+ +
+
+send_packet_to_server(message)
+

Send an audio packet to the server using WebSocket.

+
+
Args:

message (bytes): The audio data packet in bytes to be sent to the server.

+
+
+
+ +
+
+write_audio_frames_to_file(frames, file_name)
+

Write audio frames to a WAV file.

+

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:

frames (bytes): The audio frames to be written to the file. +file_name (str): The name of the WAV file to which the frames will be written.

+
+
+
+ +
+
+write_output_recording(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 +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.

+
+
Args:

n_audio_file (int): The number of audio chunk files to combine. +out_file (str): The name of the output WAV file to save the final recording.

+
+
+
+ +
+ +
+
+class whisper_live.client.TranscriptionClient(host, port, is_multilingual=False, lang=None, translate=False)
+

Client for handling audio transcription tasks via a WebSocket connection.

+

Acts as a high-level client for audio transcription tasks using a WebSocket connection. It can be used +to send audio data for transcription to a server and receive transcribed text segments.

+
+
Args:

host (str): The hostname or IP address of the server. +port (int): The port number to connect to on the server. +is_multilingual (bool, optional): Indicates whether the transcription should support multiple languages (default is False). +lang (str, optional): The primary language for transcription (used if is_multilingual is False). Default is None, which defaults to English (‘en’). +translate (bool, optional): Indicates whether translation tasks are required (default is False).

+
+
Attributes:

client (Client): An instance of the underlying Client class responsible for handling the WebSocket connection.

+
+
Example:

To create a TranscriptionClient and start transcription on microphone audio: +`python +transcription_client = TranscriptionClient(host="localhost", port=9090, is_multilingual=True) +transcription_client() +`

+
+
+
+ +
+
+whisper_live.client.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

+
+
+
+
-

Indices and tables

+

Indices and tables

  • Index

  • Module Index

  • diff --git a/docs/html/objects.inv b/docs/html/objects.inv index 00b0d2b..ca8d141 100644 --- a/docs/html/objects.inv +++ b/docs/html/objects.inv @@ -2,6 +2,4 @@ # Project: whisper_live # Version: # The remainder of this file is compressed using zlib. -xڭn w?U5kN*ErF€㭯%uL7*8L - 7l[+Hp >Rw8< -C 9Nʥd[S)B7ѭAH5Ftq&%A4 -C8"E'9 Pi ree_!\!C ZDxބO!V7ФܐStґqZ|ˤhx-l=Z2#u񱩻"}+Pp.=b i{\s,{/B{ \ No newline at end of file +xڭUn0+\DʵJTJ^mA@A hNIvwuF8JI[̓DJ⸭l+_HYtf#SmP=:5]K @h>IvrRB'hE[tjO h#k!ǞniG*N :#:+cOw`_},Qe902f_OT \ No newline at end of file diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index bc25beb..5b26292 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Welcome to whisper_live\u2019s documentation!"], "terms": {"class": 0, "server": 0, "servecli": 0, "websocket": 0, "task": 0, "transcrib": 0, "devic": 0, "none": 0, "multilingu": 0, "fals": 0, "languag": 0, "client_uid": 0, "attribut": 0, "rate": 0, "int": 0, "The": 0, "audio": 0, "sampl": 0, "constant": 0, "set": 0, "16000": 0, "server_readi": 0, "str": 0, "A": 0, "messag": 0, "i": 0, "readi": 0, "disconnect": 0, "client": 0, "should": 0, "uniqu": 0, "identifi": 0, "data": 0, "byte": 0, "accumul": 0, "frame": 0, "transcript": 0, "type": 0, "e": 0, "g": 0, "whispermodel": 0, "whisper": 0, "model": 0, "speech": 0, "text": 0, "timestamp_offset": 0, "float": 0, "offset": 0, "timestamp": 0, "frames_np": 0, "numpi": 0, "ndarrai": 0, "arrai": 0, "store": 0, "frames_offset": 0, "list": 0, "segment": 0, "current_out": 0, "current": 0, "incomplet": 0, "prev_out": 0, "previou": 0, "t_start": 0, "start": 0, "exit": 0, "bool": 0, "flag": 0, "thread": 0, "same_output_threshold": 0, "threshold": 0, "consecut": 0, "same": 0, "output": 0, "show_prev_out_thresh": 0, "show": 0, "add_pause_thresh": 0, "ad": 0, "paus": 0, "blank": 0, "send_last_n_seg": 0, "number": 0, "last": 0, "send": 0, "wrapper": 0, "textwrap": 0, "textwrapp": 0, "format": 0, "pick_previous_seg": 0, "includ": 0, "connect": 0, "add_fram": 0, "frame_np": 0, "add": 0, "ongo": 0, "stream": 0, "buffer": 0, "thi": 0, "method": 0, "respons": 0, "maintain": 0, "allow": 0, "continu": 0, "addit": 0, "thei": 0, "ar": 0, "receiv": 0, "It": 0, "also": 0, "ensur": 0, "doe": 0, "exce": 0, "specifi": 0, "size": 0, "prevent": 0, "excess": 0, "memori": 0, "usag": 0, "If": 0, "45": 0, "second": 0, "discard": 0, "oldest": 0, "30": 0, "reason": 0, "empti": 0, "initi": 0, "provid": 0, "us": 0, "real": 0, "time": 0, "process": 0, "arg": 0, "cleanup": 0, "perform": 0, "befor": 0, "servic": 0, "necessari": 0, "stop": 0, "mark": 0, "gracefulli": 0, "destroi": 0, "resourc": 0, "associ": 0, "notifi": 0, "via": 0, "them": 0, "fill_output": 0, "combin": 0, "complet": 0, "result": 0, "wrap": 0, "two": 0, "line": 0, "each": 0, "contain": 0, "maximum": 0, "50": 0, "charact": 0, "fit": 0, "within": 0, "per": 0, "concaten": 0, "order": 0, "exist": 0, "most": 0, "recent": 0, "first": 0, "older": 0, "prepend": 0, "need": 0, "limit": 0, "3": 0, "detect": 0, "ani": 0, "preced": 0, "content": 0, "return": 0, "singl": 0, "string": 0, "speech_to_text": 0, "an": 0, "infinit": 0, "loop": 0, "wait": 0, "input": 0, "make": 0, "predict": 0, "util": 0, "asr": 0, "sent": 0, "histori": 0, "context": 0, "from": 0, "handl": 0, "durat": 0, "rais": 0, "except": 0, "issu": 0, "commun": 0, "update_seg": 0, "append": 0, "all": 0, "assum": 0, "updat": 0, "end": 0, "chronolog": 0, "one": 0, "repeat": 0, "seen": 0, "multipl": 0, "onli": 0, "onc": 0, "base": 0, "dict": 0, "dictionari": 0, "chunk": 0, "its": 0, "valid": 0, "transcriptionserv": 0, "repres": 0, "incom": 0, "vad_model": 0, "torch": 0, "modul": 0, "voic": 0, "activ": 0, "vad_threshold": 0, "clients_start_tim": 0, "track": 0, "max_client": 0, "max_connection_tim": 0, "get_wait_tim": 0, "calcul": 0, "estim": 0, "minut": 0, "recv_audio": 0, "over": 0, "vad": 0, "determin": 0, "reach": 0, "statu": 0, "until": 0, "slot": 0, "avail": 0, "clean": 0, "up": 0, "error": 0, "dure": 0, "run": 0, "host": 0, "port": 0, "9090": 0, "address": 0, "bind": 0, "index": 0, "search": 0, "page": 0}, "objects": {"whisper_live": [[0, 0, 0, "-", "client"], [0, 0, 0, "-", "server"]], "whisper_live.server": [[0, 1, 1, "", "ServeClient"], [0, 1, 1, "", "TranscriptionServer"]], "whisper_live.server.ServeClient": [[0, 2, 1, "", "add_frames"], [0, 2, 1, "", "cleanup"], [0, 2, 1, "", "disconnect"], [0, 2, 1, "", "fill_output"], [0, 2, 1, "", "speech_to_text"], [0, 2, 1, "", "update_segments"]], "whisper_live.server.TranscriptionServer": [[0, 2, 1, "", "get_wait_time"], [0, 2, 1, "", "recv_audio"], [0, 2, 1, "", "run"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"]}, "titleterms": {"welcom": 0, "whisper_l": 0, "": 0, "document": 0, "indic": 0, "tabl": 0}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"Welcome to whisper_live\u2019s documentation!": [[0, "welcome-to-whisper-live-s-documentation"]], "Indices and tables": [[0, "indices-and-tables"]]}, "indexentries": {"serveclient (class in whisper_live.server)": [[0, "whisper_live.server.ServeClient"]], "transcriptionserver (class in whisper_live.server)": [[0, "whisper_live.server.TranscriptionServer"]], "add_frames() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.add_frames"]], "cleanup() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.cleanup"]], "disconnect() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.disconnect"]], "fill_output() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.fill_output"]], "get_wait_time() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.get_wait_time"]], "module": [[0, "module-whisper_live.client"], [0, "module-whisper_live.server"]], "recv_audio() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.recv_audio"]], "run() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.run"]], "speech_to_text() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.speech_to_text"]], "update_segments() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.update_segments"]], "whisper_live.client": [[0, "module-whisper_live.client"]], "whisper_live.server": [[0, "module-whisper_live.server"]]}}) \ No newline at end of file +Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Welcome to Whisper Live documentation!"], "terms": {"class": 0, "server": 0, "servecli": 0, "websocket": 0, "task": 0, "transcrib": 0, "devic": 0, "none": 0, "multilingu": 0, "fals": 0, "languag": 0, "client_uid": 0, "attribut": 0, "rate": 0, "int": 0, "The": 0, "audio": 0, "sampl": 0, "constant": 0, "set": 0, "16000": 0, "server_readi": 0, "str": 0, "A": 0, "messag": 0, "i": 0, "readi": 0, "disconnect": 0, "client": 0, "should": 0, "uniqu": 0, "identifi": 0, "data": 0, "byte": 0, "accumul": 0, "frame": 0, "transcript": 0, "type": 0, "e": 0, "g": 0, "whispermodel": 0, "whisper": [], "model": 0, "speech": 0, "text": 0, "timestamp_offset": 0, "float": 0, "offset": 0, "timestamp": 0, "frames_np": 0, "numpi": 0, "ndarrai": 0, "arrai": 0, "store": 0, "frames_offset": 0, "list": 0, "segment": 0, "current_out": 0, "current": 0, "incomplet": 0, "prev_out": 0, "previou": 0, "t_start": 0, "start": 0, "exit": 0, "bool": 0, "flag": 0, "thread": 0, "same_output_threshold": 0, "threshold": 0, "consecut": 0, "same": 0, "output": 0, "show_prev_out_thresh": 0, "show": 0, "add_pause_thresh": 0, "ad": 0, "paus": 0, "blank": 0, "send_last_n_seg": 0, "number": 0, "last": 0, "send": 0, "wrapper": 0, "textwrap": 0, "textwrapp": 0, "format": 0, "pick_previous_seg": 0, "includ": 0, "connect": 0, "add_fram": 0, "frame_np": 0, "add": 0, "ongo": 0, "stream": 0, "buffer": 0, "thi": 0, "method": 0, "respons": 0, "maintain": 0, "allow": 0, "continu": 0, "addit": 0, "thei": 0, "ar": 0, "receiv": 0, "It": 0, "also": 0, "ensur": 0, "doe": 0, "exce": 0, "specifi": 0, "size": 0, "prevent": 0, "excess": 0, "memori": 0, "usag": 0, "If": 0, "45": 0, "second": 0, "discard": 0, "oldest": 0, "30": 0, "reason": 0, "empti": 0, "initi": 0, "provid": 0, "us": 0, "real": 0, "time": 0, "process": 0, "arg": 0, "cleanup": 0, "perform": 0, "befor": 0, "servic": 0, "necessari": 0, "stop": 0, "mark": 0, "gracefulli": 0, "destroi": 0, "resourc": 0, "associ": 0, "notifi": 0, "via": 0, "them": 0, "fill_output": 0, "combin": 0, "complet": 0, "result": 0, "wrap": 0, "two": 0, "line": 0, "each": 0, "contain": 0, "maximum": 0, "50": 0, "charact": 0, "fit": 0, "within": 0, "per": 0, "concaten": 0, "order": 0, "exist": 0, "most": 0, "recent": 0, "first": 0, "older": 0, "prepend": 0, "need": 0, "limit": 0, "3": 0, "detect": 0, "ani": 0, "preced": 0, "content": 0, "return": 0, "singl": 0, "string": 0, "speech_to_text": 0, "an": 0, "infinit": 0, "loop": 0, "wait": 0, "input": 0, "make": 0, "predict": 0, "util": 0, "asr": 0, "sent": 0, "histori": 0, "context": 0, "from": 0, "handl": 0, "durat": 0, "rais": 0, "except": 0, "issu": 0, "commun": 0, "update_seg": 0, "append": 0, "all": 0, "assum": 0, "updat": 0, "end": 0, "chronolog": 0, "one": 0, "repeat": 0, "seen": 0, "multipl": 0, "onli": 0, "onc": 0, "base": 0, "dict": 0, "dictionari": 0, "chunk": 0, "its": 0, "valid": 0, "transcriptionserv": 0, "repres": 0, "incom": 0, "vad_model": 0, "torch": 0, "modul": 0, "voic": 0, "activ": 0, "vad_threshold": 0, "clients_start_tim": 0, "track": 0, "max_client": 0, "max_connection_tim": 0, "get_wait_tim": 0, "calcul": 0, "estim": 0, "minut": 0, "recv_audio": 0, "over": 0, "vad": 0, "determin": 0, "reach": 0, "statu": 0, "until": 0, "slot": 0, "avail": 0, "clean": 0, "up": 0, "error": 0, "dure": 0, "run": 0, "host": 0, "port": 0, "9090": 0, "address": 0, "bind": 0, "index": 0, "search": 0, "page": 0, "whisper_l": 0, "": 0, "is_multilingu": 0, "lang": 0, "translat": 0, "record": 0, "microphon": 0, "plai": 0, "file": 0, "while": 0, "pyaudio": 0, "playback": 0, "paint16": 0, "16": 0, "bit": 0, "pcm": 0, "channel": 0, "1": 0, "mono": 0, "hz": 0, "record_second": 0, "whether": 0, "enabl": 0, "select": 0, "uid": 0, "last_response_reciev": 0, "disconnect_if_no_response_for": 0, "without": 0, "static": 0, "bytes_to_float_arrai": 0, "audio_byt": 0, "convert": 0, "normal": 0, "have": 0, "valu": 0, "between": 0, "np": 0, "close_websocket": 0, "close": 0, "join": 0, "attempt": 0, "self": 0, "client_socket": 0, "after": 0, "proper": 0, "termin": 0, "get_client_socket": 0, "get": 0, "socket": 0, "instanc": 0, "websocketapp": 0, "on_messag": 0, "w": 0, "callback": 0, "function": 0, "call": 0, "when": 0, "variou": 0, "on_open": 0, "successfulli": 0, "open": 0, "configur": 0, "mode": 0, "play_fil": 0, "filenam": 0, "read": 0, "through": 0, "simultan": 0, "creat": 0, "point": 0, "typic": 0, "you": 0, "want": 0, "pre": 0, "path": 0, "out_fil": 0, "output_record": 0, "wav": 0, "save": 0, "directori": 0, "separ": 0, "can": 0, "interrupt": 0, "keyboardinterrupt": 0, "press": 0, "ctrl": 0, "c": 0, "option": 0, "name": 0, "entir": 0, "default": 0, "send_packet_to_serv": 0, "packet": 0, "write_audio_frames_to_fil": 0, "file_nam": 0, "write": 0, "overwritten": 0, "correct": 0, "match": 0, "width": 0, "written": 0, "which": 0, "write_output_record": 0, "n_audio_fil": 0, "individu": 0, "expect": 0, "locat": 0, "final": 0, "delet": 0, "transcriptioncli": 0, "act": 0, "high": 0, "level": 0, "hostnam": 0, "ip": 0, "support": 0, "primari": 0, "english": 0, "en": 0, "requir": 0, "underli": 0, "exampl": 0, "To": 0, "python": 0, "transcription_cli": 0, "localhost": 0, "true": 0, "resampl": 0, "sr": 0, "http": 0, "github": 0, "com": 0, "openai": 0, "blob": 0, "7858aa9c08d98f75575035ecd6481f462d66ca27": 0, "py": 0, "l22": 0, "waveform": 0, "paramet": [], "resampled_fil": 0}, "objects": {"whisper_live": [[0, 0, 0, "-", "client"], [0, 0, 0, "-", "server"]], "whisper_live.client": [[0, 1, 1, "", "Client"], [0, 1, 1, "", "TranscriptionClient"], [0, 3, 1, "", "resample"]], "whisper_live.client.Client": [[0, 2, 1, "", "bytes_to_float_array"], [0, 2, 1, "", "close_websocket"], [0, 2, 1, "", "get_client_socket"], [0, 2, 1, "", "on_message"], [0, 2, 1, "", "on_open"], [0, 2, 1, "", "play_file"], [0, 2, 1, "", "record"], [0, 2, 1, "", "send_packet_to_server"], [0, 2, 1, "", "write_audio_frames_to_file"], [0, 2, 1, "", "write_output_recording"]], "whisper_live.server": [[0, 1, 1, "", "ServeClient"], [0, 1, 1, "", "TranscriptionServer"]], "whisper_live.server.ServeClient": [[0, 2, 1, "", "add_frames"], [0, 2, 1, "", "cleanup"], [0, 2, 1, "", "disconnect"], [0, 2, 1, "", "fill_output"], [0, 2, 1, "", "speech_to_text"], [0, 2, 1, "", "update_segments"]], "whisper_live.server.TranscriptionServer": [[0, 2, 1, "", "get_wait_time"], [0, 2, 1, "", "recv_audio"], [0, 2, 1, "", "run"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "function", "Python function"]}, "titleterms": {"welcom": 0, "whisper_l": [], "": [], "document": 0, "indic": 0, "tabl": 0, "whisper": 0, "live": 0}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"Welcome to Whisper Live documentation!": [[0, "welcome-to-whisper-live-documentation"]], "Indices and tables": [[0, "indices-and-tables"]]}, "indexentries": {"client (class in whisper_live.client)": [[0, "whisper_live.client.Client"]], "serveclient (class in whisper_live.server)": [[0, "whisper_live.server.ServeClient"]], "transcriptionclient (class in whisper_live.client)": [[0, "whisper_live.client.TranscriptionClient"]], "transcriptionserver (class in whisper_live.server)": [[0, "whisper_live.server.TranscriptionServer"]], "add_frames() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.add_frames"]], "bytes_to_float_array() (whisper_live.client.client static method)": [[0, "whisper_live.client.Client.bytes_to_float_array"]], "cleanup() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.cleanup"]], "close_websocket() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.close_websocket"]], "disconnect() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.disconnect"]], "fill_output() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.fill_output"]], "get_client_socket() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.get_client_socket"]], "get_wait_time() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.get_wait_time"]], "module": [[0, "module-whisper_live.client"], [0, "module-whisper_live.server"]], "on_message() (whisper_live.client.client static method)": [[0, "whisper_live.client.Client.on_message"]], "on_open() (whisper_live.client.client static method)": [[0, "whisper_live.client.Client.on_open"]], "play_file() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.play_file"]], "record() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.record"]], "recv_audio() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.recv_audio"]], "resample() (in module whisper_live.client)": [[0, "whisper_live.client.resample"]], "run() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.run"]], "send_packet_to_server() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.send_packet_to_server"]], "speech_to_text() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.speech_to_text"]], "update_segments() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.update_segments"]], "whisper_live.client": [[0, "module-whisper_live.client"]], "whisper_live.server": [[0, "module-whisper_live.server"]], "write_audio_frames_to_file() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.write_audio_frames_to_file"]], "write_output_recording() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.write_output_recording"]]}}) \ No newline at end of file From 241851f6533cbfcf217c9488022d45ef933e9d9f Mon Sep 17 00:00:00 2001 From: makaveli10 Date: Mon, 25 Sep 2023 16:16:06 +0530 Subject: [PATCH 12/13] update docs with cleaned client --- docs/doctrees/environment.pickle | Bin 150721 -> 96085 bytes docs/doctrees/index.doctree | Bin 99902 -> 61568 bytes docs/html/genindex.html | 55 +---------- docs/html/index.html | 153 ------------------------------- docs/html/objects.inv | Bin 544 -> 443 bytes docs/html/searchindex.js | 2 +- 6 files changed, 4 insertions(+), 206 deletions(-) diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle index 62e3147cab2380a62a65ce07f6663c4718a3bdc8..c828d9d8aa78b7b1874dacb41da9c717ee2200bd 100644 GIT binary patch delta 5087 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 literal 150721 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 diff --git a/docs/doctrees/index.doctree b/docs/doctrees/index.doctree index a59dbcc8ee8f777b99ee43ce5e308f1dd997a2cd..b4c99e483b7c960e3530d88370daf24949a61840 100644 GIT binary patch delta 2303 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 A - | B | C | D | F | G | M - | O - | P | R | S | T @@ -56,24 +53,10 @@
-

B

- - -
-

C

-
@@ -96,10 +79,6 @@

G

-
-

O

- - - -
- -

P

- - -
-

R

@@ -160,12 +117,10 @@

S

@@ -201,6 +156,8 @@
  • module
  • + + -
    • whisper_live.server @@ -209,12 +166,6 @@
    diff --git a/docs/html/index.html b/docs/html/index.html index 60f8c65..1539dd3 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -220,159 +220,6 @@ port (int): The port number to bind the server.

    -
    -class whisper_live.client.Client(host=None, port=None, is_multilingual=False, lang=None, translate=False)
    -

    Represents a client for audio recording and streaming to a server using WebSocket communication.

    -

    This class allows audio recording from the microphone or playing audio from a file while streaming it to -a server for transcription or translation. It uses PyAudio for audio recording and playback and WebSocket -for communication with the server.

    -
    -
    Attributes:

    CHUNK (int): The size of audio chunks for recording and playback. -FORMAT: The audio format used by PyAudio (paInt16 for 16-bit PCM). -CHANNELS (int): The number of audio channels (1 for mono). -RATE (int): The audio sampling rate in Hz (samples per second). -RECORD_SECONDS (int): The maximum duration for audio recording in seconds. -RECORDING (bool): Indicates whether recording is currently active. -multilingual (bool): Indicates if multilingual transcription is enabled. -language (str): The selected language for transcription. -task (str): The transcription or translation task to be performed. -uid (str): A unique identifier for the client. -WAITING (bool): Indicates if the client is waiting for server availability. -LAST_RESPONSE_RECIEVED (float): Timestamp of the last response received from the server. -DISCONNECT_IF_NO_RESPONSE_FOR (int): Maximum time without server response before disconnection.

    -
    -
    -
    -
    -static 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 -have values between -1 and 1.

    -
    -
    Args:

    audio_bytes (bytes): Audio data in bytes.

    -
    -
    Returns:

    np.ndarray: A NumPy array containing the audio data as float values normalized between -1 and 1.

    -
    -
    -
    - -
    -
    -close_websocket()
    -

    Close the WebSocket connection and join the WebSocket thread.

    -

    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.

    -
    - -
    -
    -get_client_socket()
    -

    Get the WebSocket client socket instance.

    -
    -
    Returns:

    WebSocketApp: The WebSocket client socket instance currently in use by the client.

    -
    -
    -
    - -
    -
    -static on_message(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.

    -
    -
    Args:

    ws (websocket.WebSocketApp): The WebSocket client instance. -message (str): The received message from the server.

    -
    -
    -
    - -
    -
    -static on_open(ws)
    -

    Callback function called when the WebSocket connection is successfully opened.

    -

    Sends an initial configuration message to the server, including client UID, multilingual mode, -language selection, and task type.

    -
    -
    Args:

    ws (websocket.WebSocketApp): The WebSocket client instance.

    -
    -
    -
    - -
    -
    -play_file(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 -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.

    -
    -
    Args:

    filename (str): The path to the audio file to be played and sent to the server.

    -
    -
    -
    - -
    -
    -record(out_file='output_recording.wav')
    -

    Record audio data from the input stream and save it to a WAV file.

    -

    Continuously records audio data from the input stream, sends it to the server via a WebSocket -connection, and simultaneously saves it to multiple WAV files in chunks. It stops recording when -the RECORD_SECONDS duration is reached or when the RECORDING flag is set to False.

    -

    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 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”.

    -
    -
    -
    - -
    -
    -send_packet_to_server(message)
    -

    Send an audio packet to the server using WebSocket.

    -
    -
    Args:

    message (bytes): The audio data packet in bytes to be sent to the server.

    -
    -
    -
    - -
    -
    -write_audio_frames_to_file(frames, file_name)
    -

    Write audio frames to a WAV file.

    -

    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:

    frames (bytes): The audio frames to be written to the file. -file_name (str): The name of the WAV file to which the frames will be written.

    -
    -
    -
    - -
    -
    -write_output_recording(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 -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.

    -
    -
    Args:

    n_audio_file (int): The number of audio chunk files to combine. -out_file (str): The name of the output WAV file to save the final recording.

    -
    -
    -
    - -
    - -
    class whisper_live.client.TranscriptionClient(host, port, is_multilingual=False, lang=None, translate=False)

    Client for handling audio transcription tasks via a WebSocket connection.

    diff --git a/docs/html/objects.inv b/docs/html/objects.inv index ca8d1416a8743e6170079b79f18d1fe9e3f446bf..eac02a78c6d0227c0fc91fbb116e91215e0b8bd2 100644 GIT binary patch delta 329 zcmV-P0k;021iJ%}dw-L`Zo?o9hVOX_rtO+&z4p4(q)n=nNj)MDoCJvg0+Y6HKT^~# zRl8MDdJ3`qfBpfp>r`;)xmE*KLMt@H++p2Y88xz3ri{dW)lsjXbF6$}WwEx@Et+G51(ItG->6x+VyEb_j=hh| z;ieh;z}ThNGN1 delta 431 zcmV;g0Z{(C1E2(udw*5SZo)7Syyq)iwbw-D%C)CTtyGCh9IeG(6044F`9a9v*Tz6W zAZQX!Np^R}`g@<- z5}YHmQ|xU}wdy@O>NQ##2n1bOD<>|0)WzLIy#Jot13~5x55_&4)5?(pX^eE-Qk=Z`;{^ny(ymw$weU-3^=`~vIN Date: Mon, 25 Sep 2023 16:25:20 +0530 Subject: [PATCH 13/13] add client docs --- docs/doctrees/environment.pickle | Bin 96085 -> 145782 bytes docs/doctrees/index.doctree | Bin 61568 -> 95455 bytes docs/html/genindex.html | 55 ++++++++++++- docs/html/index.html | 134 +++++++++++++++++++++++++++++++ docs/html/objects.inv | Bin 443 -> 544 bytes docs/html/searchindex.js | 2 +- whisper_live/client.py | 3 + 7 files changed, 190 insertions(+), 4 deletions(-) diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle index c828d9d8aa78b7b1874dacb41da9c717ee2200bd..0923b6676c7aa4e664777ed3ecfe37edc4b5f21c 100644 GIT binary patch delta 27181 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 diff --git a/docs/doctrees/index.doctree b/docs/doctrees/index.doctree index b4c99e483b7c960e3530d88370daf24949a61840..be33d87a80c885feea401b79367a6d4040d7ba3c 100644 GIT binary patch literal 95455 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 a){rT-7=@PXR^ delta 2294 zcmah}Z%k8H6z{o(N_p=UD_T)02veX?8Bh=e3@}B9Y`O~KpViF*)yESMDcv-43vN?~ z!8mujT(@lcVMen_6j0Zx+l2bJ=j1em_`>`^#qw;5hD6XvH)K*&9#C_odLO-^RTYn8E%nh7!1>f-b6{Ybxj> zf)Z)!87mBhffply8{sz<6XX~|1bXQ2MG)kf$W(#4k3C0%5z+DV^k+vQpRPJz$5PAT z1T3VRE}+`(u;Qdx*`hcE=1vAn;J{)y1f*J^=7SDtsi4!zwpzXB&)*2PKu-nCC1is@ z#|y)L0sRdE%`ci4NO%gK)RV0O6|OH}u4*VEJ|1c}dO4Lv*($Ir)$l1suZod05U8D`ksMOAa{A3-T0HHVVD!9)49$mp(@RYysc$Jf{1LK z63f5gh6R#?qcdc^oT)4oX#W~J%gBQj7K^CUDP?+GX7XLRwxeUWirE)=;9|e5g5ODj zNTDi)jW{5ghD;O)2zYxUW&!j2||#8aw4n5gXDeRSifFH;nNgebFuK~TUqkJ4g4B~wp0c(@k%@^Sf@=+9_5g|u4!p}Fs zc5*g~Fdn@F8X*Qn5ONq2s)MnNm!=IytHcLG%Hcmlvs8*S?QJXD)CkrY!x*TI$_U@7 zqcTE&yj8*{C63)}gdy@XuP%q)(8uVN`w|;#g0|ck6Vhp&q|=2OObJ`FAk(nlz=7HsAwY@UsEc~HKD8#@+_=8{&!ZxDX=ID58i}Elm zkq4~A5djms=Yia3OCDQW*cw$^$SRwA A + | B | C | D | F | G | M + | O + | P | R | S | T @@ -53,10 +56,24 @@ +

    B

    + + +
    +

    C

    +
    @@ -79,6 +96,10 @@

    G

    +
    +

    O

    + + + +
    + +

    P

    + + +
    +

    R

    @@ -117,10 +160,12 @@

    S

    @@ -156,8 +201,6 @@
  • module
  • - - +
    • whisper_live.server @@ -166,6 +209,12 @@
    diff --git a/docs/html/index.html b/docs/html/index.html index 1539dd3..c8b3e33 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -220,6 +220,140 @@ port (int): The port number to bind the server.

    +
    +class whisper_live.client.Client(host=None, port=None, is_multilingual=False, lang=None, translate=False)
    +

    Handles audio recording, streaming, and communication with a server using WebSocket.

    +
    +
    +static 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 +have values between -1 and 1.

    +
    +
    Args:

    audio_bytes (bytes): Audio data in bytes.

    +
    +
    Returns:

    np.ndarray: A NumPy array containing the audio data as float values normalized between -1 and 1.

    +
    +
    +
    + +
    +
    +close_websocket()
    +

    Close the WebSocket connection and join the WebSocket thread.

    +

    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.

    +
    + +
    +
    +get_client_socket()
    +

    Get the WebSocket client socket instance.

    +
    +
    Returns:

    WebSocketApp: The WebSocket client socket instance currently in use by the client.

    +
    +
    +
    + +
    +
    +on_message(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.

    +
    +
    Args:

    ws (websocket.WebSocketApp): The WebSocket client instance. +message (str): The received message from the server.

    +
    +
    +
    + +
    +
    +on_open(ws)
    +

    Callback function called when the WebSocket connection is successfully opened.

    +

    Sends an initial configuration message to the server, including client UID, multilingual mode, +language selection, and task type.

    +
    +
    Args:

    ws (websocket.WebSocketApp): The WebSocket client instance.

    +
    +
    +
    + +
    +
    +play_file(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 +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.

    +
    +
    Args:

    filename (str): The path to the audio file to be played and sent to the server.

    +
    +
    +
    + +
    +
    +record(out_file='output_recording.wav')
    +

    Record audio data from the input stream and save it to a WAV file.

    +

    Continuously records audio data from the input stream, sends it to the server via a WebSocket +connection, and simultaneously saves it to multiple WAV files in chunks. It stops recording when +the RECORD_SECONDS duration is reached or when the RECORDING flag is set to False.

    +

    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 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”.

    +
    +
    +
    + +
    +
    +send_packet_to_server(message)
    +

    Send an audio packet to the server using WebSocket.

    +
    +
    Args:

    message (bytes): The audio data packet in bytes to be sent to the server.

    +
    +
    +
    + +
    +
    +write_audio_frames_to_file(frames, file_name)
    +

    Write audio frames to a WAV file.

    +

    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:

    frames (bytes): The audio frames to be written to the file. +file_name (str): The name of the WAV file to which the frames will be written.

    +
    +
    +
    + +
    +
    +write_output_recording(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 +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.

    +
    +
    Args:

    n_audio_file (int): The number of audio chunk files to combine. +out_file (str): The name of the output WAV file to save the final recording.

    +
    +
    +
    + +
    + +
    class whisper_live.client.TranscriptionClient(host, port, is_multilingual=False, lang=None, translate=False)

    Client for handling audio transcription tasks via a WebSocket connection.

    diff --git a/docs/html/objects.inv b/docs/html/objects.inv index eac02a78c6d0227c0fc91fbb116e91215e0b8bd2..ca8d1416a8743e6170079b79f18d1fe9e3f446bf 100644 GIT binary patch delta 431 zcmV;g0Z{(C1E2(udw*5SZo)7Syyq)iwbw-D%C)CTtyGCh9IeG(6044F`9a9v*Tz6W zAZQX!Np^R}`g@<- z5}YHmQ|xU}wdy@O>NQ##2n1bOD<>|0)WzLIy#Jot13~5x55_&4)5?(pX^eE-Qk=Z`;{^ny(ymw$weU-3^=`~vINr`;)xmE*KLMt@H++p2Y88xz3ri{dW)lsjXbF6$}WwEx@Et+G51(ItG->6x+VyEb_j=hh| z;ieh;z}ThNGN1 diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index b1d0043..d75d57a 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Welcome to Whisper Live documentation!"], "terms": {"class": 0, "server": 0, "servecli": 0, "websocket": 0, "task": 0, "transcrib": 0, "devic": 0, "none": 0, "multilingu": 0, "fals": 0, "languag": 0, "client_uid": 0, "attribut": 0, "rate": 0, "int": 0, "The": 0, "audio": 0, "sampl": 0, "constant": 0, "set": 0, "16000": 0, "server_readi": 0, "str": 0, "A": 0, "messag": 0, "i": 0, "readi": 0, "disconnect": 0, "client": 0, "should": 0, "uniqu": 0, "identifi": 0, "data": 0, "byte": 0, "accumul": 0, "frame": 0, "transcript": 0, "type": 0, "e": 0, "g": 0, "whispermodel": 0, "whisper": [], "model": 0, "speech": 0, "text": 0, "timestamp_offset": 0, "float": 0, "offset": 0, "timestamp": 0, "frames_np": 0, "numpi": 0, "ndarrai": 0, "arrai": 0, "store": 0, "frames_offset": 0, "list": 0, "segment": 0, "current_out": 0, "current": 0, "incomplet": 0, "prev_out": 0, "previou": 0, "t_start": 0, "start": 0, "exit": 0, "bool": 0, "flag": 0, "thread": 0, "same_output_threshold": 0, "threshold": 0, "consecut": 0, "same": 0, "output": 0, "show_prev_out_thresh": 0, "show": 0, "add_pause_thresh": 0, "ad": 0, "paus": 0, "blank": 0, "send_last_n_seg": 0, "number": 0, "last": 0, "send": 0, "wrapper": 0, "textwrap": 0, "textwrapp": 0, "format": 0, "pick_previous_seg": 0, "includ": 0, "connect": 0, "add_fram": 0, "frame_np": 0, "add": 0, "ongo": 0, "stream": 0, "buffer": 0, "thi": 0, "method": 0, "respons": 0, "maintain": 0, "allow": 0, "continu": 0, "addit": 0, "thei": 0, "ar": 0, "receiv": 0, "It": 0, "also": 0, "ensur": 0, "doe": 0, "exce": 0, "specifi": 0, "size": 0, "prevent": 0, "excess": 0, "memori": 0, "usag": 0, "If": 0, "45": 0, "second": 0, "discard": 0, "oldest": 0, "30": 0, "reason": 0, "empti": 0, "initi": 0, "provid": 0, "us": 0, "real": 0, "time": 0, "process": 0, "arg": 0, "cleanup": 0, "perform": 0, "befor": 0, "servic": 0, "necessari": 0, "stop": 0, "mark": 0, "gracefulli": 0, "destroi": 0, "resourc": 0, "associ": 0, "notifi": 0, "via": 0, "them": 0, "fill_output": 0, "combin": 0, "complet": 0, "result": 0, "wrap": 0, "two": 0, "line": 0, "each": 0, "contain": 0, "maximum": 0, "50": 0, "charact": 0, "fit": 0, "within": 0, "per": 0, "concaten": 0, "order": 0, "exist": 0, "most": 0, "recent": 0, "first": 0, "older": 0, "prepend": 0, "need": 0, "limit": 0, "3": 0, "detect": 0, "ani": 0, "preced": 0, "content": 0, "return": 0, "singl": 0, "string": 0, "speech_to_text": 0, "an": 0, "infinit": 0, "loop": 0, "wait": 0, "input": 0, "make": 0, "predict": 0, "util": 0, "asr": 0, "sent": 0, "histori": 0, "context": 0, "from": 0, "handl": 0, "durat": 0, "rais": 0, "except": 0, "issu": 0, "commun": 0, "update_seg": 0, "append": 0, "all": 0, "assum": 0, "updat": 0, "end": 0, "chronolog": 0, "one": 0, "repeat": 0, "seen": 0, "multipl": 0, "onli": 0, "onc": 0, "base": 0, "dict": 0, "dictionari": 0, "chunk": 0, "its": 0, "valid": 0, "transcriptionserv": 0, "repres": 0, "incom": 0, "vad_model": 0, "torch": 0, "modul": 0, "voic": 0, "activ": 0, "vad_threshold": 0, "clients_start_tim": 0, "track": 0, "max_client": 0, "max_connection_tim": 0, "get_wait_tim": 0, "calcul": 0, "estim": 0, "minut": 0, "recv_audio": 0, "over": 0, "vad": 0, "determin": 0, "reach": 0, "statu": 0, "until": 0, "slot": 0, "avail": 0, "clean": 0, "up": 0, "error": 0, "dure": 0, "run": 0, "host": 0, "port": 0, "9090": 0, "address": 0, "bind": 0, "index": 0, "search": 0, "page": 0, "whisper_l": 0, "": 0, "is_multilingu": 0, "lang": 0, "translat": 0, "record": [], "microphon": 0, "plai": [], "file": 0, "while": [], "pyaudio": [], "playback": [], "paint16": [], "16": [], "bit": [], "pcm": [], "channel": [], "1": [], "mono": 0, "hz": [], "record_second": [], "whether": 0, "enabl": [], "select": [], "uid": [], "last_response_reciev": [], "disconnect_if_no_response_for": [], "without": [], "static": [], "bytes_to_float_arrai": [], "audio_byt": [], "convert": [], "normal": [], "have": [], "valu": [], "between": [], "np": [], "close_websocket": [], "close": [], "join": [], "attempt": [], "self": [], "client_socket": [], "after": [], "proper": [], "termin": [], "get_client_socket": [], "get": [], "socket": [], "instanc": 0, "websocketapp": [], "on_messag": [], "w": [], "callback": [], "function": [], "call": [], "when": [], "variou": [], "on_open": [], "successfulli": [], "open": 0, "configur": [], "mode": [], "play_fil": [], "filenam": [], "read": 0, "through": [], "simultan": [], "creat": 0, "point": [], "typic": [], "you": [], "want": [], "pre": [], "path": [], "out_fil": [], "output_record": [], "wav": [], "save": 0, "directori": [], "separ": [], "can": 0, "interrupt": [], "keyboardinterrupt": [], "press": [], "ctrl": [], "c": [], "option": 0, "name": [], "entir": [], "default": 0, "send_packet_to_serv": [], "packet": [], "write_audio_frames_to_fil": [], "file_nam": [], "write": [], "overwritten": [], "correct": [], "match": [], "width": [], "written": [], "which": 0, "write_output_record": [], "n_audio_fil": [], "individu": [], "expect": [], "locat": [], "final": [], "delet": [], "transcriptioncli": 0, "act": 0, "high": 0, "level": 0, "hostnam": 0, "ip": 0, "support": 0, "primari": 0, "english": 0, "en": 0, "requir": 0, "underli": 0, "exampl": 0, "To": 0, "python": 0, "transcription_cli": 0, "localhost": 0, "true": 0, "resampl": 0, "sr": 0, "http": 0, "github": 0, "com": 0, "openai": 0, "blob": 0, "7858aa9c08d98f75575035ecd6481f462d66ca27": 0, "py": 0, "l22": 0, "waveform": 0, "paramet": [], "resampled_fil": 0}, "objects": {"whisper_live": [[0, 0, 0, "-", "client"], [0, 0, 0, "-", "server"]], "whisper_live.client": [[0, 1, 1, "", "TranscriptionClient"], [0, 2, 1, "", "resample"]], "whisper_live.server": [[0, 1, 1, "", "ServeClient"], [0, 1, 1, "", "TranscriptionServer"]], "whisper_live.server.ServeClient": [[0, 3, 1, "", "add_frames"], [0, 3, 1, "", "cleanup"], [0, 3, 1, "", "disconnect"], [0, 3, 1, "", "fill_output"], [0, 3, 1, "", "speech_to_text"], [0, 3, 1, "", "update_segments"]], "whisper_live.server.TranscriptionServer": [[0, 3, 1, "", "get_wait_time"], [0, 3, 1, "", "recv_audio"], [0, 3, 1, "", "run"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:function", "3": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "function", "Python function"], "3": ["py", "method", "Python method"]}, "titleterms": {"welcom": 0, "whisper_l": [], "": [], "document": 0, "indic": 0, "tabl": 0, "whisper": 0, "live": 0}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"Welcome to Whisper Live documentation!": [[0, "welcome-to-whisper-live-documentation"]], "Indices and tables": [[0, "indices-and-tables"]]}, "indexentries": {"serveclient (class in whisper_live.server)": [[0, "whisper_live.server.ServeClient"]], "transcriptionclient (class in whisper_live.client)": [[0, "whisper_live.client.TranscriptionClient"]], "transcriptionserver (class in whisper_live.server)": [[0, "whisper_live.server.TranscriptionServer"]], "add_frames() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.add_frames"]], "cleanup() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.cleanup"]], "disconnect() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.disconnect"]], "fill_output() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.fill_output"]], "get_wait_time() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.get_wait_time"]], "module": [[0, "module-whisper_live.client"], [0, "module-whisper_live.server"]], "recv_audio() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.recv_audio"]], "resample() (in module whisper_live.client)": [[0, "whisper_live.client.resample"]], "run() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.run"]], "speech_to_text() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.speech_to_text"]], "update_segments() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.update_segments"]], "whisper_live.client": [[0, "module-whisper_live.client"]], "whisper_live.server": [[0, "module-whisper_live.server"]]}}) \ No newline at end of file +Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Welcome to Whisper Live documentation!"], "terms": {"class": 0, "whisper_l": 0, "server": 0, "servecli": 0, "websocket": 0, "task": 0, "transcrib": 0, "devic": 0, "none": 0, "multilingu": 0, "fals": 0, "languag": 0, "client_uid": 0, "attribut": 0, "rate": 0, "int": 0, "The": 0, "audio": 0, "sampl": 0, "constant": 0, "set": 0, "16000": 0, "server_readi": 0, "str": 0, "A": 0, "messag": 0, "i": 0, "readi": 0, "disconnect": 0, "client": 0, "should": 0, "uniqu": 0, "identifi": 0, "data": 0, "byte": 0, "accumul": 0, "frame": 0, "transcript": 0, "type": 0, "e": 0, "g": 0, "whispermodel": 0, "model": 0, "speech": 0, "text": 0, "timestamp_offset": 0, "float": 0, "offset": 0, "timestamp": 0, "frames_np": 0, "numpi": 0, "ndarrai": 0, "arrai": 0, "store": 0, "frames_offset": 0, "list": 0, "segment": 0, "current_out": 0, "current": 0, "incomplet": 0, "prev_out": 0, "previou": 0, "t_start": 0, "start": 0, "exit": 0, "bool": 0, "flag": 0, "thread": 0, "same_output_threshold": 0, "threshold": 0, "consecut": 0, "same": 0, "output": 0, "show_prev_out_thresh": 0, "show": 0, "add_pause_thresh": 0, "ad": 0, "paus": 0, "blank": 0, "send_last_n_seg": 0, "number": 0, "last": 0, "send": 0, "wrapper": 0, "textwrap": 0, "textwrapp": 0, "format": 0, "pick_previous_seg": 0, "includ": 0, "connect": 0, "add_fram": 0, "frame_np": 0, "add": 0, "ongo": 0, "stream": 0, "buffer": 0, "thi": 0, "method": 0, "respons": 0, "maintain": 0, "allow": 0, "continu": 0, "addit": 0, "thei": 0, "ar": 0, "receiv": 0, "It": 0, "also": 0, "ensur": 0, "doe": 0, "exce": 0, "specifi": 0, "size": 0, "prevent": 0, "excess": 0, "memori": 0, "usag": 0, "If": 0, "45": 0, "second": 0, "discard": 0, "oldest": 0, "30": 0, "reason": 0, "empti": 0, "initi": 0, "provid": 0, "us": 0, "real": 0, "time": 0, "process": 0, "arg": 0, "cleanup": 0, "perform": 0, "befor": 0, "servic": 0, "necessari": 0, "stop": 0, "mark": 0, "gracefulli": 0, "destroi": 0, "resourc": 0, "associ": 0, "notifi": 0, "via": 0, "them": 0, "fill_output": 0, "combin": 0, "complet": 0, "result": 0, "wrap": 0, "two": 0, "line": 0, "each": 0, "contain": 0, "maximum": 0, "50": 0, "charact": 0, "fit": 0, "within": 0, "per": 0, "concaten": 0, "order": 0, "exist": 0, "most": 0, "recent": 0, "first": 0, "older": 0, "prepend": 0, "need": 0, "limit": 0, "3": 0, "detect": 0, "ani": 0, "preced": 0, "content": 0, "return": 0, "singl": 0, "string": 0, "speech_to_text": 0, "an": 0, "infinit": 0, "loop": 0, "": 0, "wait": 0, "input": 0, "make": 0, "predict": 0, "util": 0, "asr": 0, "sent": 0, "histori": 0, "context": 0, "from": 0, "handl": 0, "durat": 0, "rais": 0, "except": 0, "issu": 0, "commun": 0, "update_seg": 0, "append": 0, "all": 0, "assum": 0, "updat": 0, "end": 0, "chronolog": 0, "one": 0, "repeat": 0, "seen": 0, "multipl": 0, "onli": 0, "onc": 0, "base": 0, "dict": 0, "dictionari": 0, "chunk": 0, "its": 0, "valid": 0, "transcriptionserv": 0, "repres": 0, "incom": 0, "vad_model": 0, "torch": 0, "modul": 0, "voic": 0, "activ": 0, "vad_threshold": 0, "clients_start_tim": 0, "track": 0, "max_client": 0, "max_connection_tim": 0, "get_wait_tim": 0, "calcul": 0, "estim": 0, "minut": 0, "recv_audio": 0, "over": 0, "vad": 0, "determin": 0, "reach": 0, "statu": 0, "until": 0, "slot": 0, "avail": 0, "clean": 0, "up": 0, "error": 0, "dure": 0, "run": 0, "host": 0, "port": 0, "9090": 0, "address": 0, "bind": 0, "transcriptioncli": 0, "is_multilingu": 0, "lang": 0, "translat": 0, "act": 0, "high": 0, "level": 0, "can": 0, "hostnam": 0, "ip": 0, "option": 0, "whether": 0, "support": 0, "default": 0, "primari": 0, "which": 0, "english": 0, "en": 0, "requir": 0, "instanc": 0, "underli": 0, "exampl": 0, "To": 0, "creat": 0, "microphon": 0, "python": 0, "transcription_cli": 0, "localhost": 0, "true": 0, "resampl": 0, "file": 0, "sr": 0, "http": 0, "github": 0, "com": 0, "openai": 0, "blob": 0, "7858aa9c08d98f75575035ecd6481f462d66ca27": 0, "py": 0, "l22": 0, "open": 0, "read": 0, "mono": 0, "waveform": 0, "save": 0, "resampled_fil": 0, "index": 0, "search": 0, "page": 0, "record": 0, "static": 0, "bytes_to_float_arrai": 0, "audio_byt": 0, "convert": 0, "16": 0, "bit": 0, "pcm": 0, "normal": 0, "have": 0, "valu": 0, "between": 0, "1": 0, "np": 0, "close_websocket": 0, "close": 0, "join": 0, "attempt": 0, "self": 0, "client_socket": 0, "after": 0, "proper": 0, "termin": 0, "get_client_socket": 0, "get": 0, "socket": 0, "websocketapp": 0, "on_messag": 0, "w": 0, "callback": 0, "function": 0, "call": 0, "when": 0, "variou": 0, "on_open": 0, "successfulli": 0, "configur": 0, "uid": 0, "mode": 0, "select": 0, "play_fil": 0, "filenam": 0, "plai": 0, "through": 0, "simultan": 0, "pyaudio": 0, "playback": 0, "point": 0, "typic": 0, "you": 0, "want": 0, "pre": 0, "path": 0, "out_fil": 0, "output_record": 0, "wav": 0, "record_second": 0, "directori": 0, "separ": 0, "interrupt": 0, "keyboardinterrupt": 0, "press": 0, "ctrl": 0, "c": 0, "name": 0, "entir": 0, "send_packet_to_serv": 0, "packet": 0, "write_audio_frames_to_fil": 0, "file_nam": 0, "write": 0, "overwritten": 0, "correct": 0, "match": 0, "channel": 0, "width": 0, "written": 0, "write_output_record": 0, "n_audio_fil": 0, "individu": 0, "expect": 0, "locat": 0, "final": 0, "delet": 0}, "objects": {"whisper_live": [[0, 0, 0, "-", "client"], [0, 0, 0, "-", "server"]], "whisper_live.client": [[0, 1, 1, "", "Client"], [0, 1, 1, "", "TranscriptionClient"], [0, 3, 1, "", "resample"]], "whisper_live.client.Client": [[0, 2, 1, "", "bytes_to_float_array"], [0, 2, 1, "", "close_websocket"], [0, 2, 1, "", "get_client_socket"], [0, 2, 1, "", "on_message"], [0, 2, 1, "", "on_open"], [0, 2, 1, "", "play_file"], [0, 2, 1, "", "record"], [0, 2, 1, "", "send_packet_to_server"], [0, 2, 1, "", "write_audio_frames_to_file"], [0, 2, 1, "", "write_output_recording"]], "whisper_live.server": [[0, 1, 1, "", "ServeClient"], [0, 1, 1, "", "TranscriptionServer"]], "whisper_live.server.ServeClient": [[0, 2, 1, "", "add_frames"], [0, 2, 1, "", "cleanup"], [0, 2, 1, "", "disconnect"], [0, 2, 1, "", "fill_output"], [0, 2, 1, "", "speech_to_text"], [0, 2, 1, "", "update_segments"]], "whisper_live.server.TranscriptionServer": [[0, 2, 1, "", "get_wait_time"], [0, 2, 1, "", "recv_audio"], [0, 2, 1, "", "run"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "function", "Python function"]}, "titleterms": {"welcom": 0, "whisper": 0, "live": 0, "document": 0, "indic": 0, "tabl": 0}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"Welcome to Whisper Live documentation!": [[0, "welcome-to-whisper-live-documentation"]], "Indices and tables": [[0, "indices-and-tables"]]}, "indexentries": {"client (class in whisper_live.client)": [[0, "whisper_live.client.Client"]], "serveclient (class in whisper_live.server)": [[0, "whisper_live.server.ServeClient"]], "transcriptionclient (class in whisper_live.client)": [[0, "whisper_live.client.TranscriptionClient"]], "transcriptionserver (class in whisper_live.server)": [[0, "whisper_live.server.TranscriptionServer"]], "add_frames() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.add_frames"]], "bytes_to_float_array() (whisper_live.client.client static method)": [[0, "whisper_live.client.Client.bytes_to_float_array"]], "cleanup() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.cleanup"]], "close_websocket() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.close_websocket"]], "disconnect() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.disconnect"]], "fill_output() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.fill_output"]], "get_client_socket() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.get_client_socket"]], "get_wait_time() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.get_wait_time"]], "module": [[0, "module-whisper_live.client"], [0, "module-whisper_live.server"]], "on_message() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.on_message"]], "on_open() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.on_open"]], "play_file() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.play_file"]], "record() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.record"]], "recv_audio() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.recv_audio"]], "resample() (in module whisper_live.client)": [[0, "whisper_live.client.resample"]], "run() (whisper_live.server.transcriptionserver method)": [[0, "whisper_live.server.TranscriptionServer.run"]], "send_packet_to_server() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.send_packet_to_server"]], "speech_to_text() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.speech_to_text"]], "update_segments() (whisper_live.server.serveclient method)": [[0, "whisper_live.server.ServeClient.update_segments"]], "whisper_live.client": [[0, "module-whisper_live.client"]], "whisper_live.server": [[0, "module-whisper_live.server"]], "write_audio_frames_to_file() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.write_audio_frames_to_file"]], "write_output_recording() (whisper_live.client.client method)": [[0, "whisper_live.client.Client.write_output_recording"]]}}) \ No newline at end of file diff --git a/whisper_live/client.py b/whisper_live/client.py index 7364c24..ca2300c 100644 --- a/whisper_live/client.py +++ b/whisper_live/client.py @@ -44,6 +44,9 @@ def resample(file: str, sr: int = 16000): class Client: + """ + Handles audio recording, streaming, and communication with a server using WebSocket. + """ INSTANCES = {} def __init__(