25 Commits

Author SHA1 Message Date
makaveli10 7c0b32b85e bump version 0.0.10 2024-01-01 18:12:51 +05:30
makaveli 01665a54c1 Merge pull request #82 from hcljsq/main
Add the `large-v3` model
2024-01-01 18:09:41 +05:30
华晨 02793a93f8 feat: Update transcriber to support large-v3 model with 128 mel filters 2024-01-01 20:22:37 +08:00
makaveli db2e0bbcdd Merge pull request #81 from k0hacuu/main
README Spelling correction
2024-01-01 13:53:38 +05:30
华晨 5918b5ed42 fix: update faster-whisper 2023-12-31 15:45:41 +08:00
华晨 71d207a607 feat: add large-v3 model 2023-12-30 23:19:30 +08:00
Jonny Yang 6ee4cd09f2 Update README.md 2023-12-25 13:12:24 +00:00
Marcus Edel 5de4de4b84 Merge pull request #76 from makaveli10/model_size_option
Model size option.
2023-12-20 09:10:57 -05:00
makaveli10 e006722da7 add model size option to client 2023-12-20 18:06:02 +05:30
makaveli10 a52dc0cbf8 update chrome/firefox plugin readme 2023-12-15 00:09:55 +05:30
makaveli10 048ab0a8f4 remove debugging script 2023-12-15 02:26:01 +08:00
makaveli10 261bb9e961 Merge branch 'model_size_option' of github.com:makaveli10/whisper-live into model_size_option 2023-12-15 02:23:20 +08:00
makaveli10 091f6179d4 add sample audio for testing 2023-12-15 02:23:13 +08:00
makaveli10 1e1349cd80 update firefox plugin with model size dropdown 2023-12-14 23:52:51 +05:30
makaveli10 14beb4f942 update chrome plugin with model size dropdown 2023-12-14 23:52:25 +05:30
makaveli10 7ffcad64ba update readme 2023-12-15 02:21:35 +08:00
makaveli10 402fceb9f3 remove emptyline 2023-12-15 02:21:09 +08:00
makaveli10 09b18e8ab8 add model size option from server 2023-12-14 22:08:12 +08:00
makaveli da72d03073 Merge pull request #73 from jhormigo/hls_support
Support for HLS transcription
2023-12-13 00:05:11 +05:30
Jesús Hormigo a1a8d5f92a Added a HLS stream sample URL 2023-12-12 13:46:52 +01:00
Jesús Hormigo b6dee4e46e Using ffmpeg-python package instead of requiring having ffmpeg installed in system 2023-12-10 19:34:27 +01:00
Jesús Hormigo f3cd20fbf3 Support for HLS transcription (resolves #62) 2023-12-09 21:24:55 +01:00
makaveli10 da86c18205 bump version: 0.0.9 2023-12-06 15:09:50 +05:30
makaveli 8097e9b44a Merge pull request #70 from ethanzrd/main
Update `faster_whisper` version setup.py
2023-12-06 15:04:06 +05:30
Ethan Zerad 222852ff33 Update setup.py
Update faster-whisper version to match requirements/server.txt
2023-12-02 11:07:41 +02:00
17 changed files with 232 additions and 67 deletions
+1
View File
@@ -29,6 +29,7 @@ When using the Audio Transcription extension, you have the following options:
- **Use Multilingual Model**: Enable this option to utilize the multilingual capabilities of OpenAI-whisper.
- **Language**: Select the target language for transcription or translation. You can choose from a variety of languages supported by OpenAI-whisper.
- **Task:** Choose the specific task to perform on the audio. You can select either "transcribe" for transcription or "translate" to translate the audio to English.
- **Model Size**: Select the whisper model size to run the server with.
### Getting Started
- Make sure the transcription server is running properly. To know more about how to start the server, see the [documentation here](https://github.com/collabora/whisper-live).
+2 -1
View File
@@ -156,7 +156,8 @@ async function startCapture(options) {
port: options.port,
multilingual: options.useMultilingual,
language: options.language,
task: options.task
task: options.task,
modelSize: options.modelSize
},
});
} else {
+2 -1
View File
@@ -102,7 +102,8 @@ async function startRecord(option) {
uid: uuid,
multilingual: option.multilingual,
language: option.language,
task: option.task
task: option.task,
model_size: option.modelSize
})
);
};
+13 -1
View File
@@ -125,11 +125,23 @@
</div>
<div class="dropdown-container">
<label for="taskDropdown">Select task:</label>
<select id="taskDropdown" disabled>
<select id="taskDropdown" >
<option value="">Select Task</option>
<option value="transcribe" selected>Transcribe</option>
<option value="translate">Translate</option>
</select>
</div>
<div class="dropdown-container">
<label for="modelSizeDropdown">Select Model Size:</label>
<select id="modelSizeDropdown">
<option value="">Select Task</option>
<option value="tiny">Tiny</option>
<option value="base">Base</option>
<option value="small" selected>Small</option>
<option value="medium">Medium</option>
<option value="large-v2">Large-v2</option>
<option value="large-v3">Large-v3</option>
</select>
</div>
</body>
</html>
+18 -2
View File
@@ -7,8 +7,10 @@ document.addEventListener("DOMContentLoaded", function () {
const useMultilingualCheckbox = document.getElementById('useMultilingualCheckbox');
const languageDropdown = document.getElementById('languageDropdown');
const taskDropdown = document.getElementById('taskDropdown');
const modelSizeDropdown = document.getElementById('modelSizeDropdown');
let selectedLanguage = null;
let selectedTask = taskDropdown.value;
let selectedModelSize = modelSizeDropdown.value;
// Add click event listeners to the buttons
startButton.addEventListener("click", startCapture);
@@ -52,6 +54,13 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
chrome.storage.local.get("selectedModelSize", ({ selectedModelSize: storedModelSize }) => {
if (storedModelSize !== undefined) {
modelSizeDropdown.value = storedModelSize;
selectedModelSize = storedModelSize;
}
});
// Function to handle the start capture button click event
async function startCapture() {
// Ignore click if the button is disabled
@@ -64,7 +73,7 @@ document.addEventListener("DOMContentLoaded", function () {
// Send a message to the background script to start capturing
let host = "localhost";
let port = "9090";
let port = "5901";
const useCollaboraServer = useServerCheckbox.checked;
if (useCollaboraServer){
host = "transcription.kurg.org"
@@ -79,7 +88,8 @@ document.addEventListener("DOMContentLoaded", function () {
port: port,
useMultilingual: useMultilingualCheckbox.checked,
language: selectedLanguage,
task: selectedTask
task: selectedTask,
modelSize: selectedModelSize
}, () => {
// Update capturing state in storage and toggle the buttons
chrome.storage.local.set({ capturingState: { isCapturing: true } }, () => {
@@ -120,6 +130,7 @@ document.addEventListener("DOMContentLoaded", function () {
stopButton.disabled = !isCapturing;
useServerCheckbox.disabled = isCapturing;
useMultilingualCheckbox.disabled = isCapturing;
modelSizeDropdown.disabled = isCapturing;
startButton.classList.toggle("disabled", isCapturing);
stopButton.classList.toggle("disabled", !isCapturing);
@@ -157,6 +168,11 @@ document.addEventListener("DOMContentLoaded", function () {
chrome.storage.local.set({ selectedTask });
});
modelSizeDropdown.addEventListener('change', function() {
selectedModelSize = modelSizeDropdown.value;
chrome.storage.local.set({ selectedModelSize });
});
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
if (request.action === "updateSelectedLanguage") {
const detectedLanguage = request.detectedLanguage;
+1
View File
@@ -27,6 +27,7 @@ When using the Audio Transcription extension, you have the following options:
- **Use Multilingual Model**: Enable this option to utilize the multilingual capabilities of OpenAI-whisper.
- **Language**: Select the target language for transcription or translation. You can choose from a variety of languages supported by OpenAI-whisper.
- **Task:** Choose the specific task to perform on the audio. You can select either "transcribe" for transcription or "translate" to translate the audio to English.
- **Model Size**: Select the whisper model size to run the server with.
### Getting Started
- Make sure the transcription server is running properly. To know more about how to start the server, see the [documentation here](https://github.com/collabora/whisper-live).
+2 -1
View File
@@ -77,7 +77,8 @@ function startRecording(data) {
uid: uuid,
multilingual: data.useMultilingual,
language: data.language,
task: data.task
task: data.task,
model_size: data.modelSize
})
);
};
+12
View File
@@ -133,5 +133,17 @@
<option value="translate">Translate</option>
</select>
</div>
<div class="dropdown-container">
<label for="modelSizeDropdown">Select Model Size:</label>
<select id="modelSizeDropdown">
<option value="">Select Task</option>
<option value="tiny">Tiny</option>
<option value="base">Base</option>
<option value="small" selected>Small</option>
<option value="medium">Medium</option>
<option value="large-v2">Large-v2</option>
<option value="large-v3">Large-v3</option>
</select>
</div>
</body>
</html>
+19 -2
View File
@@ -6,8 +6,11 @@ document.addEventListener("DOMContentLoaded", function() {
const useMultilingualCheckbox = document.getElementById('useMultilingualCheckbox');
const languageDropdown = document.getElementById('languageDropdown');
const taskDropdown = document.getElementById('taskDropdown');
const modelSizeDropdown = document.getElementById('modelSizeDropdown');
let selectedLanguage = null;
let selectedTask = taskDropdown.value;
let selectedModelSize = modelSizeDropdown.value;
browser.storage.local.get("capturingState")
.then(function(result) {
@@ -54,9 +57,16 @@ document.addEventListener("DOMContentLoaded", function() {
}
});
browser.storage.local.get("selectedModelSize", ({ selectedModelSize: storedModelSize }) => {
if (storedModelSize !== undefined) {
modelSizeDropdown.value = storedModelSize;
selectedModelSize = storedModelSize;
}
});
startButton.addEventListener("click", function() {
let host = "localhost";
let port = "9090";
let port = "5901";
const useCollaboraServer = useServerCheckbox.checked;
if (useCollaboraServer){
@@ -75,7 +85,8 @@ document.addEventListener("DOMContentLoaded", function() {
port: port,
useMultilingual: useMultilingualCheckbox.checked,
language: selectedLanguage,
task: selectedTask
task: selectedTask,
modelSize: selectedModelSize
}
});
toggleCaptureButtons(true);
@@ -115,6 +126,7 @@ document.addEventListener("DOMContentLoaded", function() {
stopButton.disabled = !isCapturing;
useServerCheckbox.disabled = isCapturing;
useMultilingualCheckbox.disabled = isCapturing;
modelSizeDropdown.disabled = isCapturing;
startButton.classList.toggle("disabled", isCapturing);
stopButton.classList.toggle("disabled", !isCapturing);
@@ -152,6 +164,11 @@ document.addEventListener("DOMContentLoaded", function() {
browser.storage.local.set({ selectedTask });
});
modelSizeDropdown.addEventListener('change', function() {
selectedModelSize = modelSizeDropdown.value;
browser.storage.local.set({ selectedModelSize });
});
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "updateSelectedLanguage") {
const detectedLanguage = request.data;
+26 -5
View File
@@ -28,19 +28,40 @@ Unlike traditional speech recognition systems that rely on continuous audio stre
- To transcribe an audio file:
```python
from whisper_live.client import TranscriptionClient
client = TranscriptionClient("localhost", 9090, is_multilingual=True, lang="hi", translate=True)
client(audio_file_path)
client = TranscriptionClient(
"localhost",
9090,
is_multilingual=False,
lang="en",
translate=False,
model_size="small"
)
client("tests/jfk.wav")
```
This command transcribes the specified audio file (audio.wav) using the Whisper model. It connects to the server running on localhost at port 9090. It also enables the multilingual feature, allowing transcription in multiple languages. The language option specifies the target language for transcription, in this case, Hindi ("hi"). The translate option should be set to `True` if we want to translate from the source language to English and `False` if we want to transcribe in the source language.
This command transcribes the specified audio file (audio.wav) using the Whisper model. It connects to the server running on localhost at port 9090. It can also enable the multilingual feature, allowing transcription in multiple languages. The language option specifies the target language for transcription, in this case, English ("en"). The translate option should be set to `True` if we want to translate from the source language to English and `False` if we want to transcribe in the source language.
- To transcribe from microphone:
```python
from whisper_live.client import TranscriptionClient
client = TranscriptionClient(host, port, is_multilingual=True, lang="hi", translate=True)
client = TranscriptionClient(
"localhost",
9090,
is_multilingual=True,
lang="hi",
translate=True,
model_size="small"
)
client()
```
This command captures audio from the microphone and sends it to the server for transcription. It uses the same options as the previous command, enabling the multilingual feature and specifying the target language and task.
This command captures audio from the microphone and sends it to the server for transcription. It uses the multilingual option with `hi` as the selected language, enabling the multilingual feature and specifying the target language and task. We use whisper `small` by default but can be changed to any other option based on the requirements and the hardware running the server.
- To transcribe from a HLS stream:
```python
client = TranscriptionClient(host, port, is_multilingual=True, lang="en", translate=False)
client(hls_url="http://as-hls-ww-live.akamaized.net/pool_904/live/ww/bbc_1xtra/bbc_1xtra.isml/bbc_1xtra-audio%3d96000.norewind.m3u8")
```
This command streams audio into the server from a HLS stream. It uses the same options as the previous command, enabling the multilingual feature and specifying the target language and task.
## Transcribe audio from browser
- Run the server
+1 -1
View File
@@ -1,5 +1,5 @@
PyAudio
faster-whisper==0.9.0
faster-whisper==0.10.0
--extra-index-url https://download.pytorch.org/whl/cu111
torch==1.10.1
torchaudio==0.10.1
+2 -2
View File
@@ -41,7 +41,7 @@ setup(name="whisper-live",
),
install_requires=[
"PyAudio",
"faster-whisper==0.6.0",
"faster-whisper==0.10.0",
"torch",
"torchaudio",
"websockets",
@@ -51,4 +51,4 @@ setup(name="whisper-live",
"websocket-client",
],
python_requires=">=3.8"
)
)
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
__version__="0.0.8"
__version__="0.0.10"
+60 -14
View File
@@ -50,7 +50,7 @@ class Client:
INSTANCES = {}
def __init__(
self, host=None, port=None, is_multilingual=False, lang=None, translate=False
self, host=None, port=None, is_multilingual=False, lang=None, translate=False, model_size="small"
):
"""
Initializes a Client instance for audio recording and streaming to a server.
@@ -80,7 +80,9 @@ class Client:
self.last_response_recieved = None
self.disconnect_if_no_response_for = 15
self.multilingual = is_multilingual
self.language = lang if is_multilingual else "en"
self.language = lang
self.model_size = model_size
self.server_error = False
if translate:
self.task = "translate"
@@ -140,11 +142,16 @@ class Client:
print("[ERROR]: invalid client uid")
return
if "status" in message.keys() and message["status"] == "WAIT":
self.waiting = True
print(
f"[INFO]:Server is full. Estimated wait time {round(message['message'])} minutes."
)
if "status" in message.keys():
if message["status"] == "WAIT":
self.waiting = True
print(
f"[INFO]:Server is full. Estimated wait time {round(message['message'])} minutes."
)
elif message["status"] == "ERROR":
print(f"Message from Server: {message['message']}")
self.server_error = True
return
if "message" in message.keys() and message["message"] == "DISCONNECT":
print("[INFO]: Server overtime disconnected.")
@@ -213,6 +220,7 @@ class Client:
"multilingual": self.multilingual,
"language": self.language,
"task": self.task,
"model_size": self.model_size,
}
)
)
@@ -344,6 +352,42 @@ class Client:
wavfile.setframerate(self.rate)
wavfile.writeframes(frames)
def process_hls_stream(self, hls_url):
"""
Connect to an HLS source, process the audio stream, and send it for transcription.
Args:
hls_url (str): The URL of the HLS stream source.
"""
print("[INFO]: Connecting to HLS stream...")
process = None # Initialize process to None
try:
# Connecting to the HLS stream using ffmpeg-python
process = (
ffmpeg
.input(hls_url, threads=0)
.output('-', format='s16le', acodec='pcm_s16le', ac=1, ar=self.rate)
.run_async(pipe_stdout=True, pipe_stderr=True)
)
# Process the stream
while True:
in_bytes = process.stdout.read(self.chunk * 2) # 2 bytes per sample
if not in_bytes:
break
audio_array = self.bytes_to_float_array(in_bytes)
self.send_packet_to_server(audio_array.tobytes())
except Exception as e:
print(f"[ERROR]: Failed to connect to HLS stream: {e}")
finally:
if process:
process.kill()
print("[INFO]: HLS stream processing finished.")
def record(self, out_file="output_recording.wav"):
"""
Record audio data from the input stream and save it to a WAV file.
@@ -461,10 +505,10 @@ class TranscriptionClient:
transcription_client()
```
"""
def __init__(self, host, port, is_multilingual=False, lang=None, translate=False):
self.client = Client(host, port, is_multilingual, lang, translate)
def __init__(self, host, port, is_multilingual=False, lang=None, translate=False, model_size="small"):
self.client = Client(host, port, is_multilingual, lang, translate, model_size)
def __call__(self, audio=None):
def __call__(self, audio=None, hls_url=None):
"""
Start the transcription process.
@@ -478,13 +522,15 @@ class TranscriptionClient:
"""
print("[INFO]: Waiting for server ready ...")
while not self.client.recording:
if self.client.waiting:
if self.client.waiting or self.client.server_error:
self.client.close_websocket()
return
pass
print("[INFO]: Server Ready!")
if audio is not None:
if hls_url is not None:
self.client.process_hls_stream(hls_url)
elif audio is not None:
resampled_file = resample(audio)
self.client.play_file(resampled_file)
else:
self.client.record()
self.client.record()
+50 -33
View File
@@ -101,9 +101,10 @@ class TranscriptionServer:
multilingual=options["multilingual"],
language=options["language"],
task=options["task"],
client_uid=options["uid"]
client_uid=options["uid"],
model_size=options["model_size"]
)
self.clients[websocket] = client
self.clients_start_time[websocket] = time.time()
@@ -127,7 +128,8 @@ class TranscriptionServer:
except Exception as e:
logging.error(e)
self.clients[websocket].cleanup()
if self.clients[websocket].model_size is not None:
self.clients[websocket].cleanup()
self.clients.pop(websocket)
self.clients_start_time.pop(websocket)
logging.info("Connection Closed.")
@@ -180,7 +182,16 @@ class ServeClient:
SERVER_READY = "SERVER_READY"
DISCONNECT = "DISCONNECT"
def __init__(self, websocket, task="transcribe", device=None, multilingual=False, language=None, client_uid=None):
def __init__(
self,
websocket,
task="transcribe",
device=None,
multilingual=False,
language=None,
client_uid=None,
model_size="small"
):
"""
Initialize a ServeClient instance.
The Whisper model is initialized based on the client's language and device availability.
@@ -199,11 +210,22 @@ class ServeClient:
self.client_uid = client_uid
self.data = b""
self.frames = b""
self.language = language if multilingual else "en"
self.model_sizes = [
"tiny", "base", "small", "medium", "large-v2", "large-v3"
]
self.multilingual = multilingual
self.model_size = self.get_model_size(model_size)
self.language = language if self.multilingual else "en"
self.task = task
self.websocket = websocket
device = "cuda" if torch.cuda.is_available() else "cpu"
if self.model_size == None:
return
self.transcriber = WhisperModel(
"small" if multilingual else "small.en",
self.model_size,
device=device,
compute_type="int8" if device=="cpu" else "float16",
local_files_only=False,
@@ -228,7 +250,6 @@ class ServeClient:
self.pick_previous_segments = 2
# threading
self.websocket = websocket
self.trans_thread = threading.Thread(target=self.speech_to_text)
self.trans_thread.start()
self.websocket.send(
@@ -240,34 +261,30 @@ class ServeClient:
)
)
def fill_output(self, output):
def get_model_size(self, model_size):
"""
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 the whisper model size based on multilingual.
"""
if model_size not in self.model_sizes:
self.websocket.send(
json.dumps(
{
"uid": self.client_uid,
"status": "ERROR",
"message": f"Invalid model size {model_size}. Available choices: {self.model_sizes}"
}
)
)
return None
Returns:
str: A formatted transcription wrapped in two lines.
"""
text = ''
pick_prev = min(len(self.text), self.pick_previous_segments)
for seg in self.text[-pick_prev:]:
# discard everything before a 3 second pause
if seg == '':
text = ''
else:
text += seg
wrapped = "".join(text + output)
return wrapped
if model_size in ["large-v2", "large-v3"]:
self.multilingual = True
return model_size
if not self.multilingual:
model_size = model_size + ".en"
return model_size
def add_frames(self, frame_np):
"""
+22 -3
View File
@@ -4,6 +4,8 @@ import itertools
import logging
import os
import zlib
import json
from inspect import signature
from typing import BinaryIO, Iterable, List, NamedTuple, Optional, Tuple, Union
@@ -94,7 +96,7 @@ class WhisperModel:
Args:
model_size_or_path: Size of the model to use (tiny, tiny.en, base, base.en,
small, small.en, medium, medium.en, large-v1, large-v2, or large), a path to a converted
small, small.en, medium, medium.en, large-v1, large-v2, large-v3, or large), a path to a converted
model directory, or a CTranslate2-converted Whisper model ID from the Hugging Face Hub.
When a size or a model ID is configured, the converted model is downloaded
from the Hugging Face Hub.
@@ -144,7 +146,8 @@ class WhisperModel:
"openai/whisper-tiny" + ("" if self.model.is_multilingual else ".en")
)
self.feature_extractor = FeatureExtractor()
self.feat_kwargs = self._get_feature_kwargs(model_path)
self.feature_extractor = FeatureExtractor(**self.feat_kwargs)
self.num_samples_per_token = self.feature_extractor.hop_length * 2
self.frames_per_second = (
self.feature_extractor.sampling_rate // self.feature_extractor.hop_length
@@ -161,6 +164,22 @@ class WhisperModel:
"""The languages supported by the model."""
return list(_LANGUAGE_CODES) if self.model.is_multilingual else ["en"]
def _get_feature_kwargs(self, model_path) -> dict:
preprocessor_config_file = os.path.join(model_path, "preprocessor_config.json")
config = {}
if os.path.isfile(preprocessor_config_file):
try:
with open(preprocessor_config_file, "r", encoding="utf-8") as json_file:
config = json.load(json_file)
valid_keys = signature(FeatureExtractor.__init__).parameters.keys()
config = {k: v for k, v in config.items() if k in valid_keys}
except json.JSONDecodeError as e:
self.logger.warning(
"Could not load preprocessor_config.json: %s", str(e)
)
return config
def transcribe(
self,
audio: Union[str, BinaryIO, np.ndarray],
@@ -914,7 +933,7 @@ class WhisperModel:
words, word_tokens, start_times, end_times, word_probabilities
)
]
def destroy(self):
del self.model