Delete whisper_pipeline
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- whisper_pipeline/HA1.wav +0 -3
- whisper_pipeline/api.py +0 -42
- whisper_pipeline/check.py +0 -6
- whisper_pipeline/dockerfile +0 -51
- whisper_pipeline/faster-whisper-main/.github/workflows/ci.yml +0 -90
- whisper_pipeline/faster-whisper-main/.gitignore +0 -15
- whisper_pipeline/faster-whisper-main/CONTRIBUTING.md +0 -31
- whisper_pipeline/faster-whisper-main/LICENSE +0 -21
- whisper_pipeline/faster-whisper-main/MANIFEST.in +0 -4
- whisper_pipeline/faster-whisper-main/README.md +0 -319
- whisper_pipeline/faster-whisper-main/benchmark/benchmark.m4a +0 -3
- whisper_pipeline/faster-whisper-main/benchmark/memory_benchmark.py +0 -94
- whisper_pipeline/faster-whisper-main/benchmark/normalizer.json +0 -1742
- whisper_pipeline/faster-whisper-main/benchmark/requirements.benchmark.txt +0 -6
- whisper_pipeline/faster-whisper-main/benchmark/speed_benchmark.py +0 -31
- whisper_pipeline/faster-whisper-main/benchmark/utils.py +0 -39
- whisper_pipeline/faster-whisper-main/benchmark/wer_benchmark.py +0 -64
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/__init__.py +0 -14
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/assets/__init__.py +0 -0
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/assets/pyannote_vad_model.bin +0 -3
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/assets/silero_vad.onnx +0 -3
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/audio.py +0 -58
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/feature_extractor.py +0 -114
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/tokenizer.py +0 -314
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/transcribe.py +0 -2170
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/utils.py +0 -157
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/vad.py +0 -596
- whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/version.py +0 -3
- whisper_pipeline/faster-whisper-main/docker/Dockerfile +0 -6
- whisper_pipeline/faster-whisper-main/docker/infer.py +0 -7
- whisper_pipeline/faster-whisper-main/docker/jfk.flac +0 -3
- whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/PKG-INFO +0 -347
- whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/SOURCES.txt +0 -25
- whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/dependency_links.txt +0 -1
- whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/requires.txt +0 -17
- whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/top_level.txt +0 -1
- whisper_pipeline/faster-whisper-main/faster_whisper/__init__.py +0 -14
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/__init__.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/audio.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/feature_extractor.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/tokenizer.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/transcribe.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/utils.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/vad.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/version.cpython-310.pyc +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/assets/__init__.py +0 -0
- whisper_pipeline/faster-whisper-main/faster_whisper/assets/pyannote_vad_model.bin +0 -3
- whisper_pipeline/faster-whisper-main/faster_whisper/assets/silero_vad.onnx +0 -3
- whisper_pipeline/faster-whisper-main/faster_whisper/audio.py +0 -58
- whisper_pipeline/faster-whisper-main/faster_whisper/feature_extractor.py +0 -114
whisper_pipeline/HA1.wav
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:87fd3e947f85de5aeeae4d2f34a4774370541acf92e0f3317686e3c70572aa6a
|
3 |
-
size 1242438
|
|
|
|
|
|
|
|
whisper_pipeline/api.py
DELETED
@@ -1,42 +0,0 @@
|
|
1 |
-
from fastapi import FastAPI, UploadFile, File
|
2 |
-
from fastapi.responses import JSONResponse
|
3 |
-
from pathlib import Path
|
4 |
-
import os
|
5 |
-
from gector import GecBERTModel
|
6 |
-
from faster_whisper import WhisperModel, BatchedInferencePipeline
|
7 |
-
from transformers.models.whisper.english_normalizer import BasicTextNormalizer
|
8 |
-
from text_processing.inverse_normalize import InverseNormalizer
|
9 |
-
import shutil
|
10 |
-
import uvicorn
|
11 |
-
|
12 |
-
# Initialize the FastAPI app
|
13 |
-
app = FastAPI()
|
14 |
-
|
15 |
-
# Initialize models and normalizer
|
16 |
-
current_dir = Path(__file__).parent.as_posix()
|
17 |
-
inverse_normalizer = InverseNormalizer('vi')
|
18 |
-
whisper_model = WhisperModel("pho_distill_q8", device="auto", compute_type="auto")
|
19 |
-
batched_model = BatchedInferencePipeline(model=whisper_model, use_vad_model=True, chunk_length=15)
|
20 |
-
gector_model = GecBERTModel(
|
21 |
-
vocab_path=os.path.join(current_dir, "gector/vocabulary"),
|
22 |
-
model_paths=[os.path.join(current_dir, "gector/Model_GECTOR")],
|
23 |
-
split_chunk=True
|
24 |
-
)
|
25 |
-
normalizer = BasicTextNormalizer()
|
26 |
-
|
27 |
-
@app.post("/transcriptions")
|
28 |
-
async def transcribe_audio(file: UploadFile = File(...)):
|
29 |
-
# Save the uploaded file temporarily
|
30 |
-
temp_file_path = Path(f"temp_{file.filename}")
|
31 |
-
with open(temp_file_path, "wb") as buffer:
|
32 |
-
shutil.copyfileobj(file.file, buffer)
|
33 |
-
segments, info = batched_model.transcribe(str(temp_file_path), language="vi", batch_size=32)
|
34 |
-
os.remove(temp_file_path)
|
35 |
-
transcriptions = [segment.text for segment in segments]
|
36 |
-
normalized_transcriptions = [inverse_normalizer.inverse_normalize(normalizer(text)) for text in transcriptions]
|
37 |
-
corrected_texts = gector_model(normalized_transcriptions)
|
38 |
-
return JSONResponse({"text": ' '.join(corrected_texts)})
|
39 |
-
|
40 |
-
|
41 |
-
if __name__ == "__main__":
|
42 |
-
uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/check.py
DELETED
@@ -1,6 +0,0 @@
|
|
1 |
-
from text_processing.inverse_normalize import InverseNormalizer
|
2 |
-
import time
|
3 |
-
normalizer = InverseNormalizer('vi')
|
4 |
-
start = time.time()
|
5 |
-
print(normalizer.inverse_normalize("mười hai ki lô gram"))
|
6 |
-
print(time.time()- start)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/dockerfile
DELETED
@@ -1,51 +0,0 @@
|
|
1 |
-
# Use Python 3.11-slim-bookworm as base
|
2 |
-
FROM python:3.11-slim-bookworm AS base
|
3 |
-
|
4 |
-
# Use args
|
5 |
-
ARG USE_CUDA
|
6 |
-
ARG USE_CUDA_VER
|
7 |
-
|
8 |
-
## Basis ##
|
9 |
-
ENV ENV=prod \
|
10 |
-
PORT=5056 \
|
11 |
-
# pass build args to the build
|
12 |
-
USE_CUDA_DOCKER=${USE_CUDA} \
|
13 |
-
USE_CUDA_DOCKER_VER=${USE_CUDA_VER}
|
14 |
-
|
15 |
-
# Install GCC and build tools
|
16 |
-
RUN apt-get update && \
|
17 |
-
apt-get install -y gcc build-essential curl git pkg-config libicu-dev && \
|
18 |
-
apt-get clean && \
|
19 |
-
rm -rf /var/lib/apt/lists/*
|
20 |
-
|
21 |
-
# Set working directory
|
22 |
-
WORKDIR /app
|
23 |
-
|
24 |
-
# Install the necessary dependencies from the requirements.txt file
|
25 |
-
COPY ./requirements.txt .
|
26 |
-
RUN pip3 install uv && \
|
27 |
-
if [ "$USE_CUDA" = "true" ]; then \
|
28 |
-
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir; \
|
29 |
-
else \
|
30 |
-
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir; \
|
31 |
-
fi
|
32 |
-
# Copy faster-whisper-main folder (which includes the setup file) and install
|
33 |
-
COPY ./faster-whisper-main ./faster-whisper-main
|
34 |
-
RUN pip3 install ./faster-whisper-main
|
35 |
-
|
36 |
-
RUN uv pip install --system -r requirements.txt --no-cache-dir
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
# Copy the remaining application code
|
41 |
-
COPY . .
|
42 |
-
|
43 |
-
# Expose the API port
|
44 |
-
EXPOSE 5056
|
45 |
-
|
46 |
-
# Set the environment variables
|
47 |
-
ENV HOST="0.0.0.0"
|
48 |
-
ENV PORT="5056"
|
49 |
-
|
50 |
-
# Set entrypoint to run the FastAPI server
|
51 |
-
ENTRYPOINT [ "bash", "start.sh" ]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/.github/workflows/ci.yml
DELETED
@@ -1,90 +0,0 @@
|
|
1 |
-
name: CI
|
2 |
-
|
3 |
-
on:
|
4 |
-
push:
|
5 |
-
branches:
|
6 |
-
- master
|
7 |
-
tags:
|
8 |
-
- v*
|
9 |
-
pull_request:
|
10 |
-
branches:
|
11 |
-
- master
|
12 |
-
|
13 |
-
jobs:
|
14 |
-
check-code-format:
|
15 |
-
runs-on: ubuntu-latest
|
16 |
-
|
17 |
-
steps:
|
18 |
-
- uses: actions/checkout@v3
|
19 |
-
|
20 |
-
- name: Set up Python 3.8
|
21 |
-
uses: actions/setup-python@v4
|
22 |
-
with:
|
23 |
-
python-version: 3.8
|
24 |
-
|
25 |
-
- name: Install module
|
26 |
-
run: |
|
27 |
-
pip install wheel
|
28 |
-
pip install -e .[dev]
|
29 |
-
|
30 |
-
- name: Check code format with Black
|
31 |
-
run: |
|
32 |
-
black --check .
|
33 |
-
|
34 |
-
- name: Check imports order with isort
|
35 |
-
run: |
|
36 |
-
isort --check-only .
|
37 |
-
|
38 |
-
- name: Check code style with Flake8
|
39 |
-
if: ${{ always() }}
|
40 |
-
run: |
|
41 |
-
flake8 .
|
42 |
-
|
43 |
-
|
44 |
-
run-tests:
|
45 |
-
runs-on: ubuntu-latest
|
46 |
-
|
47 |
-
steps:
|
48 |
-
- uses: actions/checkout@v3
|
49 |
-
|
50 |
-
- name: Set up Python 3.8
|
51 |
-
uses: actions/setup-python@v4
|
52 |
-
with:
|
53 |
-
python-version: 3.8
|
54 |
-
|
55 |
-
- name: Install module
|
56 |
-
run: |
|
57 |
-
pip install wheel
|
58 |
-
pip install -e .[dev]
|
59 |
-
|
60 |
-
- name: Run pytest
|
61 |
-
run: |
|
62 |
-
pytest -v tests/
|
63 |
-
|
64 |
-
|
65 |
-
build-and-push-package:
|
66 |
-
runs-on: ubuntu-latest
|
67 |
-
needs: [check-code-format, run-tests]
|
68 |
-
|
69 |
-
steps:
|
70 |
-
- uses: actions/checkout@v3
|
71 |
-
|
72 |
-
- name: Set up Python 3.8
|
73 |
-
uses: actions/setup-python@v4
|
74 |
-
with:
|
75 |
-
python-version: 3.8
|
76 |
-
|
77 |
-
- name: Install dependencies
|
78 |
-
run: |
|
79 |
-
pip install wheel
|
80 |
-
|
81 |
-
- name: Build package
|
82 |
-
run: |
|
83 |
-
python3 setup.py sdist bdist_wheel
|
84 |
-
|
85 |
-
- name: Push package on PyPI
|
86 |
-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
87 |
-
uses: pypa/gh-action-pypi-publish@release/v1
|
88 |
-
with:
|
89 |
-
user: __token__
|
90 |
-
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/.gitignore
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
# Byte-compiled / Optimized / DLL Files
|
2 |
-
*.pyc
|
3 |
-
*.pyo
|
4 |
-
*.pyd
|
5 |
-
__pycache__/
|
6 |
-
|
7 |
-
# Distribution / Packaging
|
8 |
-
venv/
|
9 |
-
|
10 |
-
# Unit Test
|
11 |
-
.pytest_cache/
|
12 |
-
|
13 |
-
# Ignore IDE, Editor Files
|
14 |
-
.idea/
|
15 |
-
.vscode/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/CONTRIBUTING.md
DELETED
@@ -1,31 +0,0 @@
|
|
1 |
-
# Contributing to faster-whisper
|
2 |
-
|
3 |
-
Contributions are welcome! Here are some pointers to help you install the library for development and validate your changes before submitting a pull request.
|
4 |
-
|
5 |
-
## Install the library for development
|
6 |
-
|
7 |
-
We recommend installing the module in editable mode with the `dev` extra requirements:
|
8 |
-
|
9 |
-
```bash
|
10 |
-
git clone https://github.com/SYSTRAN/faster-whisper.git
|
11 |
-
cd faster-whisper/
|
12 |
-
pip install -e .[dev]
|
13 |
-
```
|
14 |
-
|
15 |
-
## Validate the changes before creating a pull request
|
16 |
-
|
17 |
-
1. Make sure the existing tests are still passing (and consider adding new tests as well!):
|
18 |
-
|
19 |
-
```bash
|
20 |
-
pytest tests/
|
21 |
-
```
|
22 |
-
|
23 |
-
2. Reformat and validate the code with the following tools:
|
24 |
-
|
25 |
-
```bash
|
26 |
-
black .
|
27 |
-
isort .
|
28 |
-
flake8 .
|
29 |
-
```
|
30 |
-
|
31 |
-
These steps are also run automatically in the CI when you open the pull request.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/LICENSE
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
MIT License
|
2 |
-
|
3 |
-
Copyright (c) 2023 SYSTRAN
|
4 |
-
|
5 |
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
-
of this software and associated documentation files (the "Software"), to deal
|
7 |
-
in the Software without restriction, including without limitation the rights
|
8 |
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
-
copies of the Software, and to permit persons to whom the Software is
|
10 |
-
furnished to do so, subject to the following conditions:
|
11 |
-
|
12 |
-
The above copyright notice and this permission notice shall be included in all
|
13 |
-
copies or substantial portions of the Software.
|
14 |
-
|
15 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
-
SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/MANIFEST.in
DELETED
@@ -1,4 +0,0 @@
|
|
1 |
-
include faster_whisper/assets/silero_vad.onnx
|
2 |
-
include requirements.txt
|
3 |
-
include requirements.conversion.txt
|
4 |
-
include faster_whisper/assets/pyannote_vad_model.bin
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/README.md
DELETED
@@ -1,319 +0,0 @@
|
|
1 |
-
[](https://github.com/SYSTRAN/faster-whisper/actions?query=workflow%3ACI) [](https://badge.fury.io/py/faster-whisper)
|
2 |
-
|
3 |
-
# Faster Whisper transcription with CTranslate2
|
4 |
-
|
5 |
-
**faster-whisper** is a reimplementation of OpenAI's Whisper model using [CTranslate2](https://github.com/OpenNMT/CTranslate2/), which is a fast inference engine for Transformer models.
|
6 |
-
|
7 |
-
This implementation is up to 4 times faster than [openai/whisper](https://github.com/openai/whisper) for the same accuracy while using less memory. The efficiency can be further improved with 8-bit quantization on both CPU and GPU.
|
8 |
-
|
9 |
-
## Benchmark
|
10 |
-
|
11 |
-
### Whisper
|
12 |
-
|
13 |
-
For reference, here's the time and memory usage that are required to transcribe [**13 minutes**](https://www.youtube.com/watch?v=0u7tTptBo9I) of audio using different implementations:
|
14 |
-
|
15 |
-
* [openai/whisper](https://github.com/openai/whisper)@[6dea21fd](https://github.com/openai/whisper/commit/6dea21fd7f7253bfe450f1e2512a0fe47ee2d258)
|
16 |
-
* [whisper.cpp](https://github.com/ggerganov/whisper.cpp)@[3b010f9](https://github.com/ggerganov/whisper.cpp/commit/3b010f9bed9a6068609e9faf52383aea792b0362)
|
17 |
-
* [faster-whisper](https://github.com/SYSTRAN/faster-whisper)@[cce6b53e](https://github.com/SYSTRAN/faster-whisper/commit/cce6b53e4554f71172dad188c45f10fb100f6e3e)
|
18 |
-
|
19 |
-
### Large-v2 model on GPU
|
20 |
-
|
21 |
-
| Implementation | Precision | Beam size | Time | Max. GPU memory | Max. CPU memory |
|
22 |
-
| --- | --- | --- | --- | --- | --- |
|
23 |
-
| openai/whisper | fp16 | 5 | 4m30s | 11325MB | 9439MB |
|
24 |
-
| faster-whisper | fp16 | 5 | 54s | 4755MB | 3244MB |
|
25 |
-
| faster-whisper | int8 | 5 | 59s | 3091MB | 3117MB |
|
26 |
-
|
27 |
-
*Executed with CUDA 11.7.1 on a NVIDIA Tesla V100S.*
|
28 |
-
|
29 |
-
### Small model on CPU
|
30 |
-
|
31 |
-
| Implementation | Precision | Beam size | Time | Max. memory |
|
32 |
-
| --- | --- | --- | --- | --- |
|
33 |
-
| openai/whisper | fp32 | 5 | 10m31s | 3101MB |
|
34 |
-
| whisper.cpp | fp32 | 5 | 17m42s | 1581MB |
|
35 |
-
| whisper.cpp | fp16 | 5 | 12m39s | 873MB |
|
36 |
-
| faster-whisper | fp32 | 5 | 2m44s | 1675MB |
|
37 |
-
| faster-whisper | int8 | 5 | 2m04s | 995MB |
|
38 |
-
|
39 |
-
*Executed with 8 threads on a Intel(R) Xeon(R) Gold 6226R.*
|
40 |
-
|
41 |
-
|
42 |
-
### Distil-whisper
|
43 |
-
|
44 |
-
| Implementation | Precision | Beam size | Time | Gigaspeech WER |
|
45 |
-
| --- | --- | --- | --- | --- |
|
46 |
-
| distil-whisper/distil-large-v2 | fp16 | 4 |- | 10.36 |
|
47 |
-
| [faster-distil-large-v2](https://huggingface.co/Systran/faster-distil-whisper-large-v2) | fp16 | 5 | - | 10.28 |
|
48 |
-
| distil-whisper/distil-medium.en | fp16 | 4 | - | 11.21 |
|
49 |
-
| [faster-distil-medium.en](https://huggingface.co/Systran/faster-distil-whisper-medium.en) | fp16 | 5 | - | 11.21 |
|
50 |
-
|
51 |
-
*Executed with CUDA 11.4 on a NVIDIA 3090.*
|
52 |
-
|
53 |
-
<details>
|
54 |
-
<summary>testing details (click to expand)</summary>
|
55 |
-
|
56 |
-
For `distil-whisper/distil-large-v2`, the WER is tested with code sample from [link](https://huggingface.co/distil-whisper/distil-large-v2#evaluation). for `faster-distil-whisper`, the WER is tested with setting:
|
57 |
-
```python
|
58 |
-
from faster_whisper import WhisperModel
|
59 |
-
|
60 |
-
model_size = "distil-large-v2"
|
61 |
-
# model_size = "distil-medium.en"
|
62 |
-
# Run on GPU with FP16
|
63 |
-
model = WhisperModel(model_size, device="cuda", compute_type="float16")
|
64 |
-
segments, info = model.transcribe("audio.mp3", beam_size=5, language="en")
|
65 |
-
```
|
66 |
-
</details>
|
67 |
-
|
68 |
-
## Requirements
|
69 |
-
|
70 |
-
* Python 3.8 or greater
|
71 |
-
|
72 |
-
|
73 |
-
### GPU
|
74 |
-
|
75 |
-
GPU execution requires the following NVIDIA libraries to be installed:
|
76 |
-
|
77 |
-
* [cuBLAS for CUDA 12](https://developer.nvidia.com/cublas)
|
78 |
-
* [cuDNN 8 for CUDA 12](https://developer.nvidia.com/cudnn)
|
79 |
-
|
80 |
-
**Note**: Latest versions of `ctranslate2` support CUDA 12 only. For CUDA 11, the current workaround is downgrading to the `3.24.0` version of `ctranslate2` (This can be done with `pip install --force-reinstall ctranslate2==3.24.0` or specifying the version in a `requirements.txt`).
|
81 |
-
|
82 |
-
There are multiple ways to install the NVIDIA libraries mentioned above. The recommended way is described in the official NVIDIA documentation, but we also suggest other installation methods below.
|
83 |
-
|
84 |
-
<details>
|
85 |
-
<summary>Other installation methods (click to expand)</summary>
|
86 |
-
|
87 |
-
|
88 |
-
**Note:** For all these methods below, keep in mind the above note regarding CUDA versions. Depending on your setup, you may need to install the _CUDA 11_ versions of libraries that correspond to the CUDA 12 libraries listed in the instructions below.
|
89 |
-
|
90 |
-
#### Use Docker
|
91 |
-
|
92 |
-
The libraries (cuBLAS, cuDNN) are installed in these official NVIDIA CUDA Docker images: `nvidia/cuda:12.0.0-runtime-ubuntu20.04` or `nvidia/cuda:12.0.0-runtime-ubuntu22.04`.
|
93 |
-
|
94 |
-
#### Install with `pip` (Linux only)
|
95 |
-
|
96 |
-
On Linux these libraries can be installed with `pip`. Note that `LD_LIBRARY_PATH` must be set before launching Python.
|
97 |
-
|
98 |
-
```bash
|
99 |
-
pip install nvidia-cublas-cu12 nvidia-cudnn-cu12
|
100 |
-
|
101 |
-
export LD_LIBRARY_PATH=`python3 -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))'`
|
102 |
-
```
|
103 |
-
|
104 |
-
**Note**: Version 9+ of `nvidia-cudnn-cu12` appears to cause issues due its reliance on cuDNN 9 (Faster-Whisper does not currently support cuDNN 9). Ensure your version of the Python package is for cuDNN 8.
|
105 |
-
|
106 |
-
#### Download the libraries from Purfview's repository (Windows & Linux)
|
107 |
-
|
108 |
-
Purfview's [whisper-standalone-win](https://github.com/Purfview/whisper-standalone-win) provides the required NVIDIA libraries for Windows & Linux in a [single archive](https://github.com/Purfview/whisper-standalone-win/releases/tag/libs). Decompress the archive and place the libraries in a directory included in the `PATH`.
|
109 |
-
|
110 |
-
</details>
|
111 |
-
|
112 |
-
## Installation
|
113 |
-
|
114 |
-
The module can be installed from [PyPI](https://pypi.org/project/faster-whisper/):
|
115 |
-
|
116 |
-
```bash
|
117 |
-
pip install faster-whisper
|
118 |
-
```
|
119 |
-
|
120 |
-
<details>
|
121 |
-
<summary>Other installation methods (click to expand)</summary>
|
122 |
-
|
123 |
-
### Install the master branch
|
124 |
-
|
125 |
-
```bash
|
126 |
-
pip install --force-reinstall "faster-whisper @ https://github.com/SYSTRAN/faster-whisper/archive/refs/heads/master.tar.gz"
|
127 |
-
```
|
128 |
-
|
129 |
-
### Install a specific commit
|
130 |
-
|
131 |
-
```bash
|
132 |
-
pip install --force-reinstall "faster-whisper @ https://github.com/SYSTRAN/faster-whisper/archive/a4f1cc8f11433e454c3934442b5e1a4ed5e865c3.tar.gz"
|
133 |
-
```
|
134 |
-
|
135 |
-
</details>
|
136 |
-
|
137 |
-
## Usage
|
138 |
-
|
139 |
-
### Faster-whisper
|
140 |
-
|
141 |
-
```python
|
142 |
-
from faster_whisper import WhisperModel
|
143 |
-
|
144 |
-
model_size = "large-v3"
|
145 |
-
|
146 |
-
# Run on GPU with FP16
|
147 |
-
model = WhisperModel(model_size, device="cuda", compute_type="float16")
|
148 |
-
|
149 |
-
# or run on GPU with INT8
|
150 |
-
# model = WhisperModel(model_size, device="cuda", compute_type="int8_float16")
|
151 |
-
# or run on CPU with INT8
|
152 |
-
# model = WhisperModel(model_size, device="cpu", compute_type="int8")
|
153 |
-
|
154 |
-
segments, info = model.transcribe("audio.mp3", beam_size=5)
|
155 |
-
|
156 |
-
print("Detected language '%s' with probability %f" % (info.language, info.language_probability))
|
157 |
-
|
158 |
-
for segment in segments:
|
159 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
160 |
-
```
|
161 |
-
|
162 |
-
**Warning:** `segments` is a *generator* so the transcription only starts when you iterate over it. The transcription can be run to completion by gathering the segments in a list or a `for` loop:
|
163 |
-
|
164 |
-
```python
|
165 |
-
segments, _ = model.transcribe("audio.mp3")
|
166 |
-
segments = list(segments) # The transcription will actually run here.
|
167 |
-
```
|
168 |
-
|
169 |
-
### multi-segment language detection
|
170 |
-
|
171 |
-
To directly use the model for improved language detection, the following code snippet can be used:
|
172 |
-
|
173 |
-
```python
|
174 |
-
from faster_whisper import WhisperModel
|
175 |
-
model = WhisperModel("medium", device="cuda", compute_type="float16")
|
176 |
-
language_info = model.detect_language_multi_segment("audio.mp3")
|
177 |
-
```
|
178 |
-
|
179 |
-
### Batched faster-whisper
|
180 |
-
|
181 |
-
|
182 |
-
The batched version of faster-whisper is inspired by [whisper-x](https://github.com/m-bain/whisperX) licensed under the BSD-2 Clause license and integrates its VAD model to this library. We modify this implementation and also replaced the feature extraction with a faster torch-based implementation. Batched version improves the speed upto 10-12x compared to openAI implementation and 3-4x compared to the sequential faster_whisper version. It works by transcribing semantically meaningful audio chunks as batches leading to faster inference.
|
183 |
-
|
184 |
-
The following code snippet illustrates how to run inference with batched version on an example audio file. Please also refer to the test scripts of batched faster whisper.
|
185 |
-
|
186 |
-
```python
|
187 |
-
from faster_whisper import WhisperModel, BatchedInferencePipeline
|
188 |
-
|
189 |
-
model = WhisperModel("medium", device="cuda", compute_type="float16")
|
190 |
-
batched_model = BatchedInferencePipeline(model=model)
|
191 |
-
segments, info = batched_model.transcribe("audio.mp3", batch_size=16)
|
192 |
-
|
193 |
-
for segment in segments:
|
194 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
195 |
-
```
|
196 |
-
|
197 |
-
### Faster Distil-Whisper
|
198 |
-
|
199 |
-
The Distil-Whisper checkpoints are compatible with the Faster-Whisper package. In particular, the latest [distil-large-v3](https://huggingface.co/distil-whisper/distil-large-v3)
|
200 |
-
checkpoint is intrinsically designed to work with the Faster-Whisper transcription algorithm. The following code snippet
|
201 |
-
demonstrates how to run inference with distil-large-v3 on a specified audio file:
|
202 |
-
|
203 |
-
```python
|
204 |
-
from faster_whisper import WhisperModel
|
205 |
-
|
206 |
-
model_size = "distil-large-v3"
|
207 |
-
|
208 |
-
model = WhisperModel(model_size, device="cuda", compute_type="float16")
|
209 |
-
segments, info = model.transcribe("audio.mp3", beam_size=5, language="en", condition_on_previous_text=False)
|
210 |
-
|
211 |
-
for segment in segments:
|
212 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
213 |
-
```
|
214 |
-
|
215 |
-
For more information about the distil-large-v3 model, refer to the original [model card](https://huggingface.co/distil-whisper/distil-large-v3).
|
216 |
-
|
217 |
-
### Word-level timestamps
|
218 |
-
|
219 |
-
```python
|
220 |
-
segments, _ = model.transcribe("audio.mp3", word_timestamps=True)
|
221 |
-
|
222 |
-
for segment in segments:
|
223 |
-
for word in segment.words:
|
224 |
-
print("[%.2fs -> %.2fs] %s" % (word.start, word.end, word.word))
|
225 |
-
```
|
226 |
-
|
227 |
-
### VAD filter
|
228 |
-
|
229 |
-
The library integrates the [Silero VAD](https://github.com/snakers4/silero-vad) model to filter out parts of the audio without speech:
|
230 |
-
|
231 |
-
```python
|
232 |
-
segments, _ = model.transcribe("audio.mp3", vad_filter=True)
|
233 |
-
```
|
234 |
-
|
235 |
-
The default behavior is conservative and only removes silence longer than 2 seconds. See the available VAD parameters and default values in the [source code](https://github.com/SYSTRAN/faster-whisper/blob/master/faster_whisper/vad.py). They can be customized with the dictionary argument `vad_parameters`:
|
236 |
-
|
237 |
-
```python
|
238 |
-
segments, _ = model.transcribe(
|
239 |
-
"audio.mp3",
|
240 |
-
vad_filter=True,
|
241 |
-
vad_parameters=dict(min_silence_duration_ms=500),
|
242 |
-
)
|
243 |
-
```
|
244 |
-
|
245 |
-
### Logging
|
246 |
-
|
247 |
-
The library logging level can be configured like this:
|
248 |
-
|
249 |
-
```python
|
250 |
-
import logging
|
251 |
-
|
252 |
-
logging.basicConfig()
|
253 |
-
logging.getLogger("faster_whisper").setLevel(logging.DEBUG)
|
254 |
-
```
|
255 |
-
|
256 |
-
### Going further
|
257 |
-
|
258 |
-
See more model and transcription options in the [`WhisperModel`](https://github.com/SYSTRAN/faster-whisper/blob/master/faster_whisper/transcribe.py) class implementation.
|
259 |
-
|
260 |
-
## Community integrations
|
261 |
-
|
262 |
-
Here is a non exhaustive list of open-source projects using faster-whisper. Feel free to add your project to the list!
|
263 |
-
|
264 |
-
|
265 |
-
* [faster-whisper-server](https://github.com/fedirz/faster-whisper-server) is an OpenAI compatible server using `faster-whisper`. It's easily deployable with Docker, works with OpenAI SDKs/CLI, supports streaming, and live transcription.
|
266 |
-
* [WhisperX](https://github.com/m-bain/whisperX) is an award-winning Python library that offers speaker diarization and accurate word-level timestamps using wav2vec2 alignment
|
267 |
-
* [whisper-ctranslate2](https://github.com/Softcatala/whisper-ctranslate2) is a command line client based on faster-whisper and compatible with the original client from openai/whisper.
|
268 |
-
* [whisper-diarize](https://github.com/MahmoudAshraf97/whisper-diarization) is a speaker diarization tool that is based on faster-whisper and NVIDIA NeMo.
|
269 |
-
* [whisper-standalone-win](https://github.com/Purfview/whisper-standalone-win) Standalone CLI executables of faster-whisper for Windows, Linux & macOS.
|
270 |
-
* [asr-sd-pipeline](https://github.com/hedrergudene/asr-sd-pipeline) provides a scalable, modular, end to end multi-speaker speech to text solution implemented using AzureML pipelines.
|
271 |
-
* [Open-Lyrics](https://github.com/zh-plus/Open-Lyrics) is a Python library that transcribes voice files using faster-whisper, and translates/polishes the resulting text into `.lrc` files in the desired language using OpenAI-GPT.
|
272 |
-
* [wscribe](https://github.com/geekodour/wscribe) is a flexible transcript generation tool supporting faster-whisper, it can export word level transcript and the exported transcript then can be edited with [wscribe-editor](https://github.com/geekodour/wscribe-editor)
|
273 |
-
* [aTrain](https://github.com/BANDAS-Center/aTrain) is a graphical user interface implementation of faster-whisper developed at the BANDAS-Center at the University of Graz for transcription and diarization in Windows ([Windows Store App](https://apps.microsoft.com/detail/atrain/9N15Q44SZNS2)) and Linux.
|
274 |
-
* [Whisper-Streaming](https://github.com/ufal/whisper_streaming) implements real-time mode for offline Whisper-like speech-to-text models with faster-whisper as the most recommended back-end. It implements a streaming policy with self-adaptive latency based on the actual source complexity, and demonstrates the state of the art.
|
275 |
-
* [WhisperLive](https://github.com/collabora/WhisperLive) is a nearly-live implementation of OpenAI's Whisper which uses faster-whisper as the backend to transcribe audio in real-time.
|
276 |
-
* [Faster-Whisper-Transcriber](https://github.com/BBC-Esq/ctranslate2-faster-whisper-transcriber) is a simple but reliable voice transcriber that provides a user-friendly interface.
|
277 |
-
|
278 |
-
## Model conversion
|
279 |
-
|
280 |
-
When loading a model from its size such as `WhisperModel("large-v3")`, the corresponding CTranslate2 model is automatically downloaded from the [Hugging Face Hub](https://huggingface.co/Systran).
|
281 |
-
|
282 |
-
We also provide a script to convert any Whisper models compatible with the Transformers library. They could be the original OpenAI models or user fine-tuned models.
|
283 |
-
|
284 |
-
For example the command below converts the [original "large-v3" Whisper model](https://huggingface.co/openai/whisper-large-v3) and saves the weights in FP16:
|
285 |
-
|
286 |
-
```bash
|
287 |
-
pip install transformers[torch]>=4.23
|
288 |
-
|
289 |
-
ct2-transformers-converter --model openai/whisper-large-v3 --output_dir whisper-large-v3-ct2
|
290 |
-
--copy_files tokenizer.json preprocessor_config.json --quantization float16
|
291 |
-
```
|
292 |
-
|
293 |
-
* The option `--model` accepts a model name on the Hub or a path to a model directory.
|
294 |
-
* If the option `--copy_files tokenizer.json` is not used, the tokenizer configuration is automatically downloaded when the model is loaded later.
|
295 |
-
|
296 |
-
Models can also be converted from the code. See the [conversion API](https://opennmt.net/CTranslate2/python/ctranslate2.converters.TransformersConverter.html).
|
297 |
-
|
298 |
-
### Load a converted model
|
299 |
-
|
300 |
-
1. Directly load the model from a local directory:
|
301 |
-
```python
|
302 |
-
model = faster_whisper.WhisperModel("whisper-large-v3-ct2")
|
303 |
-
```
|
304 |
-
|
305 |
-
2. [Upload your model to the Hugging Face Hub](https://huggingface.co/docs/transformers/model_sharing#upload-with-the-web-interface) and load it from its name:
|
306 |
-
```python
|
307 |
-
model = faster_whisper.WhisperModel("username/whisper-large-v3-ct2")
|
308 |
-
```
|
309 |
-
|
310 |
-
## Comparing performance against other implementations
|
311 |
-
|
312 |
-
If you are comparing the performance against other Whisper implementations, you should make sure to run the comparison with similar settings. In particular:
|
313 |
-
|
314 |
-
* Verify that the same transcription options are used, especially the same beam size. For example in openai/whisper, `model.transcribe` uses a default beam size of 1 but here we use a default beam size of 5.
|
315 |
-
* When running on CPU, make sure to set the same number of threads. Many frameworks will read the environment variable `OMP_NUM_THREADS`, which can be set when running your script:
|
316 |
-
|
317 |
-
```bash
|
318 |
-
OMP_NUM_THREADS=4 python3 my_script.py
|
319 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/benchmark/benchmark.m4a
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:5dedec4f587a7940cfab93ff36e5014f155f80e10b7935f67d9eee8761663c34
|
3 |
-
size 12935433
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/benchmark/memory_benchmark.py
DELETED
@@ -1,94 +0,0 @@
|
|
1 |
-
import argparse
|
2 |
-
import time
|
3 |
-
|
4 |
-
from typing import Callable
|
5 |
-
|
6 |
-
import py3nvml.py3nvml as nvml
|
7 |
-
|
8 |
-
from memory_profiler import memory_usage
|
9 |
-
from utils import MyThread, get_logger, inference
|
10 |
-
|
11 |
-
logger = get_logger("faster-whisper")
|
12 |
-
parser = argparse.ArgumentParser(description="Memory benchmark")
|
13 |
-
parser.add_argument(
|
14 |
-
"--gpu_memory", action="store_true", help="Measure GPU memory usage"
|
15 |
-
)
|
16 |
-
parser.add_argument("--device-index", type=int, default=0, help="GPU device index")
|
17 |
-
parser.add_argument(
|
18 |
-
"--interval",
|
19 |
-
type=float,
|
20 |
-
default=0.5,
|
21 |
-
help="Interval at which measurements are collected",
|
22 |
-
)
|
23 |
-
args = parser.parse_args()
|
24 |
-
device_idx = args.device_index
|
25 |
-
interval = args.interval
|
26 |
-
|
27 |
-
|
28 |
-
def measure_memory(func: Callable[[], None]):
|
29 |
-
if args.gpu_memory:
|
30 |
-
logger.info(
|
31 |
-
"Measuring maximum GPU memory usage on GPU device."
|
32 |
-
" Make sure to not have additional processes running on the same GPU."
|
33 |
-
)
|
34 |
-
# init nvml
|
35 |
-
nvml.nvmlInit()
|
36 |
-
handle = nvml.nvmlDeviceGetHandleByIndex(device_idx)
|
37 |
-
gpu_name = nvml.nvmlDeviceGetName(handle)
|
38 |
-
gpu_memory_limit = nvml.nvmlDeviceGetMemoryInfo(handle).total >> 20
|
39 |
-
gpu_power_limit = nvml.nvmlDeviceGetPowerManagementLimit(handle) / 1000.0
|
40 |
-
info = {"gpu_memory_usage": [], "gpu_power_usage": []}
|
41 |
-
|
42 |
-
def _get_gpu_info():
|
43 |
-
while True:
|
44 |
-
info["gpu_memory_usage"].append(
|
45 |
-
nvml.nvmlDeviceGetMemoryInfo(handle).used >> 20
|
46 |
-
)
|
47 |
-
info["gpu_power_usage"].append(
|
48 |
-
nvml.nvmlDeviceGetPowerUsage(handle) / 1000
|
49 |
-
)
|
50 |
-
time.sleep(interval)
|
51 |
-
|
52 |
-
if stop:
|
53 |
-
break
|
54 |
-
|
55 |
-
return info
|
56 |
-
|
57 |
-
stop = False
|
58 |
-
thread = MyThread(_get_gpu_info, params=())
|
59 |
-
thread.start()
|
60 |
-
func()
|
61 |
-
stop = True
|
62 |
-
thread.join()
|
63 |
-
result = thread.get_result()
|
64 |
-
|
65 |
-
# shutdown nvml
|
66 |
-
nvml.nvmlShutdown()
|
67 |
-
max_memory_usage = max(result["gpu_memory_usage"])
|
68 |
-
max_power_usage = max(result["gpu_power_usage"])
|
69 |
-
print("GPU name: %s" % gpu_name)
|
70 |
-
print("GPU device index: %s" % device_idx)
|
71 |
-
print(
|
72 |
-
"Maximum GPU memory usage: %dMiB / %dMiB (%.2f%%)"
|
73 |
-
% (
|
74 |
-
max_memory_usage,
|
75 |
-
gpu_memory_limit,
|
76 |
-
(max_memory_usage / gpu_memory_limit) * 100,
|
77 |
-
)
|
78 |
-
)
|
79 |
-
print(
|
80 |
-
"Maximum GPU power usage: %dW / %dW (%.2f%%)"
|
81 |
-
% (
|
82 |
-
max_power_usage,
|
83 |
-
gpu_power_limit,
|
84 |
-
(max_power_usage / gpu_power_limit) * 100,
|
85 |
-
)
|
86 |
-
)
|
87 |
-
else:
|
88 |
-
logger.info("Measuring maximum increase of memory usage.")
|
89 |
-
max_usage = memory_usage(func, max_usage=True, interval=interval)
|
90 |
-
print("Maximum increase of RAM memory usage: %d MiB" % max_usage)
|
91 |
-
|
92 |
-
|
93 |
-
if __name__ == "__main__":
|
94 |
-
measure_memory(inference)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/benchmark/normalizer.json
DELETED
@@ -1,1742 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"accessorise": "accessorize",
|
3 |
-
"accessorised": "accessorized",
|
4 |
-
"accessorises": "accessorizes",
|
5 |
-
"accessorising": "accessorizing",
|
6 |
-
"acclimatisation": "acclimatization",
|
7 |
-
"acclimatise": "acclimatize",
|
8 |
-
"acclimatised": "acclimatized",
|
9 |
-
"acclimatises": "acclimatizes",
|
10 |
-
"acclimatising": "acclimatizing",
|
11 |
-
"accoutrements": "accouterments",
|
12 |
-
"aeon": "eon",
|
13 |
-
"aeons": "eons",
|
14 |
-
"aerogramme": "aerogram",
|
15 |
-
"aerogrammes": "aerograms",
|
16 |
-
"aeroplane": "airplane",
|
17 |
-
"aeroplanes": "airplanes",
|
18 |
-
"aesthete": "esthete",
|
19 |
-
"aesthetes": "esthetes",
|
20 |
-
"aesthetic": "esthetic",
|
21 |
-
"aesthetically": "esthetically",
|
22 |
-
"aesthetics": "esthetics",
|
23 |
-
"aetiology": "etiology",
|
24 |
-
"ageing": "aging",
|
25 |
-
"aggrandisement": "aggrandizement",
|
26 |
-
"agonise": "agonize",
|
27 |
-
"agonised": "agonized",
|
28 |
-
"agonises": "agonizes",
|
29 |
-
"agonising": "agonizing",
|
30 |
-
"agonisingly": "agonizingly",
|
31 |
-
"almanack": "almanac",
|
32 |
-
"almanacks": "almanacs",
|
33 |
-
"aluminium": "aluminum",
|
34 |
-
"amortisable": "amortizable",
|
35 |
-
"amortisation": "amortization",
|
36 |
-
"amortisations": "amortizations",
|
37 |
-
"amortise": "amortize",
|
38 |
-
"amortised": "amortized",
|
39 |
-
"amortises": "amortizes",
|
40 |
-
"amortising": "amortizing",
|
41 |
-
"amphitheatre": "amphitheater",
|
42 |
-
"amphitheatres": "amphitheaters",
|
43 |
-
"anaemia": "anemia",
|
44 |
-
"anaemic": "anemic",
|
45 |
-
"anaesthesia": "anesthesia",
|
46 |
-
"anaesthetic": "anesthetic",
|
47 |
-
"anaesthetics": "anesthetics",
|
48 |
-
"anaesthetise": "anesthetize",
|
49 |
-
"anaesthetised": "anesthetized",
|
50 |
-
"anaesthetises": "anesthetizes",
|
51 |
-
"anaesthetising": "anesthetizing",
|
52 |
-
"anaesthetist": "anesthetist",
|
53 |
-
"anaesthetists": "anesthetists",
|
54 |
-
"anaesthetize": "anesthetize",
|
55 |
-
"anaesthetized": "anesthetized",
|
56 |
-
"anaesthetizes": "anesthetizes",
|
57 |
-
"anaesthetizing": "anesthetizing",
|
58 |
-
"analogue": "analog",
|
59 |
-
"analogues": "analogs",
|
60 |
-
"analyse": "analyze",
|
61 |
-
"analysed": "analyzed",
|
62 |
-
"analyses": "analyzes",
|
63 |
-
"analysing": "analyzing",
|
64 |
-
"anglicise": "anglicize",
|
65 |
-
"anglicised": "anglicized",
|
66 |
-
"anglicises": "anglicizes",
|
67 |
-
"anglicising": "anglicizing",
|
68 |
-
"annualised": "annualized",
|
69 |
-
"antagonise": "antagonize",
|
70 |
-
"antagonised": "antagonized",
|
71 |
-
"antagonises": "antagonizes",
|
72 |
-
"antagonising": "antagonizing",
|
73 |
-
"apologise": "apologize",
|
74 |
-
"apologised": "apologized",
|
75 |
-
"apologises": "apologizes",
|
76 |
-
"apologising": "apologizing",
|
77 |
-
"appal": "appall",
|
78 |
-
"appals": "appalls",
|
79 |
-
"appetiser": "appetizer",
|
80 |
-
"appetisers": "appetizers",
|
81 |
-
"appetising": "appetizing",
|
82 |
-
"appetisingly": "appetizingly",
|
83 |
-
"arbour": "arbor",
|
84 |
-
"arbours": "arbors",
|
85 |
-
"archaeologically": "archeologically",
|
86 |
-
"archaeologist": "archeologist",
|
87 |
-
"archaeologists": "archeologists",
|
88 |
-
"archaeology": "archeology</span>",
|
89 |
-
"archeological": "archaeological",
|
90 |
-
"ardour": "ardor",
|
91 |
-
"armour": "armor",
|
92 |
-
"armoured": "armored",
|
93 |
-
"armourer": "armorer",
|
94 |
-
"armourers": "armorers",
|
95 |
-
"armouries": "armories",
|
96 |
-
"armoury": "armory",
|
97 |
-
"artefact": "artifact",
|
98 |
-
"artefacts": "artifacts",
|
99 |
-
"authorise": "authorize",
|
100 |
-
"authorised": "authorized",
|
101 |
-
"authorises": "authorizes",
|
102 |
-
"authorising": "authorizing",
|
103 |
-
"axe": "ax",
|
104 |
-
"backpedalled": "backpedaled",
|
105 |
-
"backpedalling": "backpedaling",
|
106 |
-
"bannister": "banister",
|
107 |
-
"bannisters": "banisters",
|
108 |
-
"baptise": "baptize",
|
109 |
-
"baptised": "baptized",
|
110 |
-
"baptises": "baptizes",
|
111 |
-
"baptising": "baptizing",
|
112 |
-
"bastardise": "bastardize",
|
113 |
-
"bastardised": "bastardized",
|
114 |
-
"bastardises": "bastardizes",
|
115 |
-
"bastardising": "bastardizing",
|
116 |
-
"battleax": "battleaxe",
|
117 |
-
"baulk": "balk",
|
118 |
-
"baulked": "balked",
|
119 |
-
"baulking": "balking",
|
120 |
-
"baulks": "balks",
|
121 |
-
"bedevilled": "bedeviled",
|
122 |
-
"bedevilling": "bedeviling",
|
123 |
-
"behaviour": "behavior",
|
124 |
-
"behavioural": "behavioral",
|
125 |
-
"behaviourism": "behaviorism",
|
126 |
-
"behaviourist": "behaviorist",
|
127 |
-
"behaviourists": "behaviorists",
|
128 |
-
"behaviours": "behaviors",
|
129 |
-
"behove": "behoove",
|
130 |
-
"behoved": "behooved",
|
131 |
-
"behoves": "behooves",
|
132 |
-
"bejewelled": "bejeweled",
|
133 |
-
"belabour": "belabor",
|
134 |
-
"belaboured": "belabored",
|
135 |
-
"belabouring": "belaboring",
|
136 |
-
"belabours": "belabors",
|
137 |
-
"bevelled": "beveled",
|
138 |
-
"bevvies": "bevies",
|
139 |
-
"bevvy": "bevy",
|
140 |
-
"biassed": "biased",
|
141 |
-
"biassing": "biasing",
|
142 |
-
"bingeing": "binging",
|
143 |
-
"bougainvillaea": "bougainvillea",
|
144 |
-
"bougainvillaeas": "bougainvilleas",
|
145 |
-
"bowdlerise": "bowdlerize",
|
146 |
-
"bowdlerised": "bowdlerized",
|
147 |
-
"bowdlerises": "bowdlerizes",
|
148 |
-
"bowdlerising": "bowdlerizing",
|
149 |
-
"breathalyse": "breathalyze",
|
150 |
-
"breathalysed": "breathalyzed",
|
151 |
-
"breathalyser": "breathalyzer",
|
152 |
-
"breathalysers": "breathalyzers",
|
153 |
-
"breathalyses": "breathalyzes",
|
154 |
-
"breathalysing": "breathalyzing",
|
155 |
-
"brutalise": "brutalize",
|
156 |
-
"brutalised": "brutalized",
|
157 |
-
"brutalises": "brutalizes",
|
158 |
-
"brutalising": "brutalizing",
|
159 |
-
"busses": "buses",
|
160 |
-
"bussing": "busing",
|
161 |
-
"caesarean": "cesarean",
|
162 |
-
"caesareans": "cesareans",
|
163 |
-
"calibre": "caliber",
|
164 |
-
"calibres": "calibers",
|
165 |
-
"calliper": "caliper",
|
166 |
-
"callipers": "calipers",
|
167 |
-
"callisthenics": "calisthenics",
|
168 |
-
"canalise": "canalize",
|
169 |
-
"canalised": "canalized",
|
170 |
-
"canalises": "canalizes",
|
171 |
-
"canalising": "canalizing",
|
172 |
-
"cancelation": "cancellation",
|
173 |
-
"cancelations": "cancellations",
|
174 |
-
"cancelled": "canceled",
|
175 |
-
"cancelling": "canceling",
|
176 |
-
"candour": "candor",
|
177 |
-
"cannibalise": "cannibalize",
|
178 |
-
"cannibalised": "cannibalized",
|
179 |
-
"cannibalises": "cannibalizes",
|
180 |
-
"cannibalising": "cannibalizing",
|
181 |
-
"canonise": "canonize",
|
182 |
-
"canonised": "canonized",
|
183 |
-
"canonises": "canonizes",
|
184 |
-
"canonising": "canonizing",
|
185 |
-
"capitalise": "capitalize",
|
186 |
-
"capitalised": "capitalized",
|
187 |
-
"capitalises": "capitalizes",
|
188 |
-
"capitalising": "capitalizing",
|
189 |
-
"caramelise": "caramelize",
|
190 |
-
"caramelised": "caramelized",
|
191 |
-
"caramelises": "caramelizes",
|
192 |
-
"caramelising": "caramelizing",
|
193 |
-
"carbonise": "carbonize",
|
194 |
-
"carbonised": "carbonized",
|
195 |
-
"carbonises": "carbonizes",
|
196 |
-
"carbonising": "carbonizing",
|
197 |
-
"carolled": "caroled",
|
198 |
-
"carolling": "caroling",
|
199 |
-
"catalogue": "catalog",
|
200 |
-
"catalogued": "cataloged",
|
201 |
-
"catalogues": "catalogs",
|
202 |
-
"cataloguing": "cataloging",
|
203 |
-
"catalyse": "catalyze",
|
204 |
-
"catalysed": "catalyzed",
|
205 |
-
"catalyses": "catalyzes",
|
206 |
-
"catalysing": "catalyzing",
|
207 |
-
"categorise": "categorize",
|
208 |
-
"categorised": "categorized",
|
209 |
-
"categorises": "categorizes",
|
210 |
-
"categorising": "categorizing",
|
211 |
-
"cauterise": "cauterize",
|
212 |
-
"cauterised": "cauterized",
|
213 |
-
"cauterises": "cauterizes",
|
214 |
-
"cauterising": "cauterizing",
|
215 |
-
"cavilled": "caviled",
|
216 |
-
"cavilling": "caviling",
|
217 |
-
"centigramme": "centigram",
|
218 |
-
"centigrammes": "centigrams",
|
219 |
-
"centilitre": "centiliter",
|
220 |
-
"centilitres": "centiliters",
|
221 |
-
"centimetre": "centimeter",
|
222 |
-
"centimetres": "centimeters",
|
223 |
-
"centralise": "centralize",
|
224 |
-
"centralised": "centralized",
|
225 |
-
"centralises": "centralizes",
|
226 |
-
"centralising": "centralizing",
|
227 |
-
"centre": "center",
|
228 |
-
"centred": "centered",
|
229 |
-
"centrefold": "centerfold",
|
230 |
-
"centrefolds": "centerfolds",
|
231 |
-
"centrepiece": "centerpiece",
|
232 |
-
"centrepieces": "centerpieces",
|
233 |
-
"centres": "centers",
|
234 |
-
"channelled": "channeled",
|
235 |
-
"channelling": "channeling",
|
236 |
-
"characterise": "characterize",
|
237 |
-
"characterised": "characterized",
|
238 |
-
"characterises": "characterizes",
|
239 |
-
"characterising": "characterizing",
|
240 |
-
"cheque": "check",
|
241 |
-
"chequebook": "checkbook",
|
242 |
-
"chequebooks": "checkbooks",
|
243 |
-
"chequered": "checkered",
|
244 |
-
"cheques": "checks",
|
245 |
-
"chilli": "chili",
|
246 |
-
"chimaera": "chimera",
|
247 |
-
"chimaeras": "chimeras",
|
248 |
-
"chiselled": "chiseled",
|
249 |
-
"chiselling": "chiseling",
|
250 |
-
"circularise": "circularize",
|
251 |
-
"circularised": "circularized",
|
252 |
-
"circularises": "circularizes",
|
253 |
-
"circularising": "circularizing",
|
254 |
-
"civilise": "civilize",
|
255 |
-
"civilised": "civilized",
|
256 |
-
"civilises": "civilizes",
|
257 |
-
"civilising": "civilizing",
|
258 |
-
"clamour": "clamor",
|
259 |
-
"clamoured": "clamored",
|
260 |
-
"clamouring": "clamoring",
|
261 |
-
"clamours": "clamors",
|
262 |
-
"clangour": "clangor",
|
263 |
-
"clarinettist": "clarinetist",
|
264 |
-
"clarinettists": "clarinetists",
|
265 |
-
"collectivise": "collectivize",
|
266 |
-
"collectivised": "collectivized",
|
267 |
-
"collectivises": "collectivizes",
|
268 |
-
"collectivising": "collectivizing",
|
269 |
-
"colonisation": "colonization",
|
270 |
-
"colonise": "colonize",
|
271 |
-
"colonised": "colonized",
|
272 |
-
"coloniser": "colonizer",
|
273 |
-
"colonisers": "colonizers",
|
274 |
-
"colonises": "colonizes",
|
275 |
-
"colonising": "colonizing",
|
276 |
-
"colour": "color",
|
277 |
-
"colourant": "colorant",
|
278 |
-
"colourants": "colorants",
|
279 |
-
"coloured": "colored",
|
280 |
-
"coloureds": "coloreds",
|
281 |
-
"colourful": "colorful",
|
282 |
-
"colourfully": "colorfully",
|
283 |
-
"colouring": "coloring",
|
284 |
-
"colourize": "colorize",
|
285 |
-
"colourized": "colorized",
|
286 |
-
"colourizes": "colorizes",
|
287 |
-
"colourizing": "colorizing",
|
288 |
-
"colourless": "colorless",
|
289 |
-
"colours": "colors",
|
290 |
-
"commercialise": "commercialize",
|
291 |
-
"commercialised": "commercialized",
|
292 |
-
"commercialises": "commercializes",
|
293 |
-
"commercialising": "commercializing",
|
294 |
-
"compartmentalise": "compartmentalize",
|
295 |
-
"compartmentalised": "compartmentalized",
|
296 |
-
"compartmentalises": "compartmentalizes",
|
297 |
-
"compartmentalising": "compartmentalizing",
|
298 |
-
"computerise": "computerize",
|
299 |
-
"computerised": "computerized",
|
300 |
-
"computerises": "computerizes",
|
301 |
-
"computerising": "computerizing",
|
302 |
-
"conceptualise": "conceptualize",
|
303 |
-
"conceptualised": "conceptualized",
|
304 |
-
"conceptualises": "conceptualizes",
|
305 |
-
"conceptualising": "conceptualizing",
|
306 |
-
"connexion": "connection",
|
307 |
-
"connexions": "connections",
|
308 |
-
"contextualise": "contextualize",
|
309 |
-
"contextualised": "contextualized",
|
310 |
-
"contextualises": "contextualizes",
|
311 |
-
"contextualising": "contextualizing",
|
312 |
-
"cosier": "cozier",
|
313 |
-
"cosies": "cozies",
|
314 |
-
"cosiest": "coziest",
|
315 |
-
"cosily": "cozily",
|
316 |
-
"cosiness": "coziness",
|
317 |
-
"cosy": "cozy",
|
318 |
-
"councillor": "councilor",
|
319 |
-
"councillors": "councilors",
|
320 |
-
"counselled": "counseled",
|
321 |
-
"counselling": "counseling",
|
322 |
-
"counsellor": "counselor",
|
323 |
-
"counsellors": "counselors",
|
324 |
-
"crenelated": "crenellated",
|
325 |
-
"criminalise": "criminalize",
|
326 |
-
"criminalised": "criminalized",
|
327 |
-
"criminalises": "criminalizes",
|
328 |
-
"criminalising": "criminalizing",
|
329 |
-
"criticise": "criticize",
|
330 |
-
"criticised": "criticized",
|
331 |
-
"criticises": "criticizes",
|
332 |
-
"criticising": "criticizing",
|
333 |
-
"crueller": "crueler",
|
334 |
-
"cruellest": "cruelest",
|
335 |
-
"crystallisation": "crystallization",
|
336 |
-
"crystallise": "crystallize",
|
337 |
-
"crystallised": "crystallized",
|
338 |
-
"crystallises": "crystallizes",
|
339 |
-
"crystallising": "crystallizing",
|
340 |
-
"cudgelled": "cudgeled",
|
341 |
-
"cudgelling": "cudgeling",
|
342 |
-
"customise": "customize",
|
343 |
-
"customised": "customized",
|
344 |
-
"customises": "customizes",
|
345 |
-
"customising": "customizing",
|
346 |
-
"cypher": "cipher",
|
347 |
-
"cyphers": "ciphers",
|
348 |
-
"decentralisation": "decentralization",
|
349 |
-
"decentralise": "decentralize",
|
350 |
-
"decentralised": "decentralized",
|
351 |
-
"decentralises": "decentralizes",
|
352 |
-
"decentralising": "decentralizing",
|
353 |
-
"decriminalisation": "decriminalization",
|
354 |
-
"decriminalise": "decriminalize",
|
355 |
-
"decriminalised": "decriminalized",
|
356 |
-
"decriminalises": "decriminalizes",
|
357 |
-
"decriminalising": "decriminalizing",
|
358 |
-
"defence": "defense",
|
359 |
-
"defenceless": "defenseless",
|
360 |
-
"defences": "defenses",
|
361 |
-
"dehumanisation": "dehumanization",
|
362 |
-
"dehumanise": "dehumanize",
|
363 |
-
"dehumanised": "dehumanized",
|
364 |
-
"dehumanises": "dehumanizes",
|
365 |
-
"dehumanising": "dehumanizing",
|
366 |
-
"demeanour": "demeanor",
|
367 |
-
"demilitarisation": "demilitarization",
|
368 |
-
"demilitarise": "demilitarize",
|
369 |
-
"demilitarised": "demilitarized",
|
370 |
-
"demilitarises": "demilitarizes",
|
371 |
-
"demilitarising": "demilitarizing",
|
372 |
-
"demobilisation": "demobilization",
|
373 |
-
"demobilise": "demobilize",
|
374 |
-
"demobilised": "demobilized",
|
375 |
-
"demobilises": "demobilizes",
|
376 |
-
"demobilising": "demobilizing",
|
377 |
-
"democratisation": "democratization",
|
378 |
-
"democratise": "democratize",
|
379 |
-
"democratised": "democratized",
|
380 |
-
"democratises": "democratizes",
|
381 |
-
"democratising": "democratizing",
|
382 |
-
"demonise": "demonize",
|
383 |
-
"demonised": "demonized",
|
384 |
-
"demonises": "demonizes",
|
385 |
-
"demonising": "demonizing",
|
386 |
-
"demoralisation": "demoralization",
|
387 |
-
"demoralise": "demoralize",
|
388 |
-
"demoralised": "demoralized",
|
389 |
-
"demoralises": "demoralizes",
|
390 |
-
"demoralising": "demoralizing",
|
391 |
-
"denationalisation": "denationalization",
|
392 |
-
"denationalise": "denationalize",
|
393 |
-
"denationalised": "denationalized",
|
394 |
-
"denationalises": "denationalizes",
|
395 |
-
"denationalising": "denationalizing",
|
396 |
-
"deodorise": "deodorize",
|
397 |
-
"deodorised": "deodorized",
|
398 |
-
"deodorises": "deodorizes",
|
399 |
-
"deodorising": "deodorizing",
|
400 |
-
"depersonalise": "depersonalize",
|
401 |
-
"depersonalised": "depersonalized",
|
402 |
-
"depersonalises": "depersonalizes",
|
403 |
-
"depersonalising": "depersonalizing",
|
404 |
-
"deputise": "deputize",
|
405 |
-
"deputised": "deputized",
|
406 |
-
"deputises": "deputizes",
|
407 |
-
"deputising": "deputizing",
|
408 |
-
"desensitisation": "desensitization",
|
409 |
-
"desensitise": "desensitize",
|
410 |
-
"desensitised": "desensitized",
|
411 |
-
"desensitises": "desensitizes",
|
412 |
-
"desensitising": "desensitizing",
|
413 |
-
"destabilisation": "destabilization",
|
414 |
-
"destabilise": "destabilize",
|
415 |
-
"destabilised": "destabilized",
|
416 |
-
"destabilises": "destabilizes",
|
417 |
-
"destabilising": "destabilizing",
|
418 |
-
"dialled": "dialed",
|
419 |
-
"dialling": "dialing",
|
420 |
-
"dialogue": "dialog",
|
421 |
-
"dialogues": "dialogs",
|
422 |
-
"diarrhoea": "diarrhea",
|
423 |
-
"digitise": "digitize",
|
424 |
-
"digitised": "digitized",
|
425 |
-
"digitises": "digitizes",
|
426 |
-
"digitising": "digitizing",
|
427 |
-
"disc": "disk",
|
428 |
-
"discolour": "discolor",
|
429 |
-
"discoloured": "discolored",
|
430 |
-
"discolouring": "discoloring",
|
431 |
-
"discolours": "discolors",
|
432 |
-
"discs": "disks",
|
433 |
-
"disembowelled": "disemboweled",
|
434 |
-
"disembowelling": "disemboweling",
|
435 |
-
"disfavour": "disfavor",
|
436 |
-
"dishevelled": "disheveled",
|
437 |
-
"dishonour": "dishonor",
|
438 |
-
"dishonourable": "dishonorable",
|
439 |
-
"dishonourably": "dishonorably",
|
440 |
-
"dishonoured": "dishonored",
|
441 |
-
"dishonouring": "dishonoring",
|
442 |
-
"dishonours": "dishonors",
|
443 |
-
"disorganisation": "disorganization",
|
444 |
-
"disorganised": "disorganized",
|
445 |
-
"distil": "distill",
|
446 |
-
"distils": "distills",
|
447 |
-
"dramatisation": "dramatization",
|
448 |
-
"dramatisations": "dramatizations",
|
449 |
-
"dramatise": "dramatize",
|
450 |
-
"dramatised": "dramatized",
|
451 |
-
"dramatises": "dramatizes",
|
452 |
-
"dramatising": "dramatizing",
|
453 |
-
"draught": "draft",
|
454 |
-
"draughtboard": "draftboard",
|
455 |
-
"draughtboards": "draftboards",
|
456 |
-
"draughtier": "draftier",
|
457 |
-
"draughtiest": "draftiest",
|
458 |
-
"draughts": "drafts",
|
459 |
-
"draughtsman": "draftsman",
|
460 |
-
"draughtsmanship": "draftsmanship",
|
461 |
-
"draughtsmen": "draftsmen",
|
462 |
-
"draughtswoman": "draftswoman",
|
463 |
-
"draughtswomen": "draftswomen",
|
464 |
-
"draughty": "drafty",
|
465 |
-
"drivelled": "driveled",
|
466 |
-
"drivelling": "driveling",
|
467 |
-
"duelled": "dueled",
|
468 |
-
"duelling": "dueling",
|
469 |
-
"economise": "economize",
|
470 |
-
"economised": "economized",
|
471 |
-
"economises": "economizes",
|
472 |
-
"economising": "economizing",
|
473 |
-
"editorialise": "editorialize",
|
474 |
-
"editorialised": "editorialized",
|
475 |
-
"editorialises": "editorializes",
|
476 |
-
"editorialising": "editorializing",
|
477 |
-
"edoema": "edema",
|
478 |
-
"empathise": "empathize",
|
479 |
-
"empathised": "empathized",
|
480 |
-
"empathises": "empathizes",
|
481 |
-
"empathising": "empathizing",
|
482 |
-
"emphasise": "emphasize",
|
483 |
-
"emphasised": "emphasized",
|
484 |
-
"emphasises": "emphasizes",
|
485 |
-
"emphasising": "emphasizing",
|
486 |
-
"enamelled": "enameled",
|
487 |
-
"enamelling": "enameling",
|
488 |
-
"enamoured": "enamored",
|
489 |
-
"encyclopaedia": "encyclopedia",
|
490 |
-
"encyclopaedias": "encyclopedias",
|
491 |
-
"encyclopaedic": "encyclopedic",
|
492 |
-
"endeavour": "endeavor",
|
493 |
-
"endeavoured": "endeavored",
|
494 |
-
"endeavouring": "endeavoring",
|
495 |
-
"endeavours": "endeavors",
|
496 |
-
"energise": "energize",
|
497 |
-
"energised": "energized",
|
498 |
-
"energises": "energizes",
|
499 |
-
"energising": "energizing",
|
500 |
-
"enrol": "enroll",
|
501 |
-
"enrols": "enrolls",
|
502 |
-
"enthral": "enthrall",
|
503 |
-
"enthrals": "enthralls",
|
504 |
-
"epaulette": "epaulet",
|
505 |
-
"epaulettes": "epaulets",
|
506 |
-
"epicentre": "epicenter",
|
507 |
-
"epicentres": "epicenters",
|
508 |
-
"epilogue": "epilog",
|
509 |
-
"epilogues": "epilogs",
|
510 |
-
"epitomise": "epitomize",
|
511 |
-
"epitomised": "epitomized",
|
512 |
-
"epitomises": "epitomizes",
|
513 |
-
"epitomising": "epitomizing",
|
514 |
-
"equalisation": "equalization",
|
515 |
-
"equalise": "equalize",
|
516 |
-
"equalised": "equalized",
|
517 |
-
"equaliser": "equalizer",
|
518 |
-
"equalisers": "equalizers",
|
519 |
-
"equalises": "equalizes",
|
520 |
-
"equalising": "equalizing",
|
521 |
-
"eulogise": "eulogize",
|
522 |
-
"eulogised": "eulogized",
|
523 |
-
"eulogises": "eulogizes",
|
524 |
-
"eulogising": "eulogizing",
|
525 |
-
"evangelise": "evangelize",
|
526 |
-
"evangelised": "evangelized",
|
527 |
-
"evangelises": "evangelizes",
|
528 |
-
"evangelising": "evangelizing",
|
529 |
-
"exorcise": "exorcize",
|
530 |
-
"exorcised": "exorcized",
|
531 |
-
"exorcises": "exorcizes",
|
532 |
-
"exorcising": "exorcizing",
|
533 |
-
"extemporisation": "extemporization",
|
534 |
-
"extemporise": "extemporize",
|
535 |
-
"extemporised": "extemporized",
|
536 |
-
"extemporises": "extemporizes",
|
537 |
-
"extemporising": "extemporizing",
|
538 |
-
"externalisation": "externalization",
|
539 |
-
"externalisations": "externalizations",
|
540 |
-
"externalise": "externalize",
|
541 |
-
"externalised": "externalized",
|
542 |
-
"externalises": "externalizes",
|
543 |
-
"externalising": "externalizing",
|
544 |
-
"factorise": "factorize",
|
545 |
-
"factorised": "factorized",
|
546 |
-
"factorises": "factorizes",
|
547 |
-
"factorising": "factorizing",
|
548 |
-
"faecal": "fecal",
|
549 |
-
"faeces": "feces",
|
550 |
-
"familiarisation": "familiarization",
|
551 |
-
"familiarise": "familiarize",
|
552 |
-
"familiarised": "familiarized",
|
553 |
-
"familiarises": "familiarizes",
|
554 |
-
"familiarising": "familiarizing",
|
555 |
-
"fantasise": "fantasize",
|
556 |
-
"fantasised": "fantasized",
|
557 |
-
"fantasises": "fantasizes",
|
558 |
-
"fantasising": "fantasizing",
|
559 |
-
"favour": "favor",
|
560 |
-
"favourable": "favorable",
|
561 |
-
"favourably": "favorably",
|
562 |
-
"favoured": "favored",
|
563 |
-
"favouring": "favoring",
|
564 |
-
"favourite": "favorite",
|
565 |
-
"favourites": "favorites",
|
566 |
-
"favouritism": "favoritism",
|
567 |
-
"favours": "favors",
|
568 |
-
"feminise": "feminize",
|
569 |
-
"feminised": "feminized",
|
570 |
-
"feminises": "feminizes",
|
571 |
-
"feminising": "feminizing",
|
572 |
-
"fertilisation": "fertilization",
|
573 |
-
"fertilise": "fertilize",
|
574 |
-
"fertilised": "fertilized",
|
575 |
-
"fertiliser": "fertilizer",
|
576 |
-
"fertilisers": "fertilizers",
|
577 |
-
"fertilises": "fertilizes",
|
578 |
-
"fertilising": "fertilizing",
|
579 |
-
"fervour": "fervor",
|
580 |
-
"fibre": "fiber",
|
581 |
-
"fibreglass": "fiberglass",
|
582 |
-
"fibres": "fibers",
|
583 |
-
"fictionalisation": "fictionalization",
|
584 |
-
"fictionalisations": "fictionalizations",
|
585 |
-
"fictionalise": "fictionalize",
|
586 |
-
"fictionalised": "fictionalized",
|
587 |
-
"fictionalises": "fictionalizes",
|
588 |
-
"fictionalising": "fictionalizing",
|
589 |
-
"fillet": "filet",
|
590 |
-
"filleted": "fileted",
|
591 |
-
"filleting": "fileting",
|
592 |
-
"fillets": "filets",
|
593 |
-
"finalisation": "finalization",
|
594 |
-
"finalise": "finalize",
|
595 |
-
"finalised": "finalized",
|
596 |
-
"finalises": "finalizes",
|
597 |
-
"finalising": "finalizing",
|
598 |
-
"flautist": "flutist",
|
599 |
-
"flautists": "flutists",
|
600 |
-
"flavour": "flavor",
|
601 |
-
"flavoured": "flavored",
|
602 |
-
"flavouring": "flavoring",
|
603 |
-
"flavourings": "flavorings",
|
604 |
-
"flavourless": "flavorless",
|
605 |
-
"flavours": "flavors",
|
606 |
-
"flavoursome": "flavorsome",
|
607 |
-
"flyer / flier": "flier / flyer",
|
608 |
-
"foetal": "fetal",
|
609 |
-
"foetid": "fetid",
|
610 |
-
"foetus": "fetus",
|
611 |
-
"foetuses": "fetuses",
|
612 |
-
"formalisation": "formalization",
|
613 |
-
"formalise": "formalize",
|
614 |
-
"formalised": "formalized",
|
615 |
-
"formalises": "formalizes",
|
616 |
-
"formalising": "formalizing",
|
617 |
-
"fossilisation": "fossilization",
|
618 |
-
"fossilise": "fossilize",
|
619 |
-
"fossilised": "fossilized",
|
620 |
-
"fossilises": "fossilizes",
|
621 |
-
"fossilising": "fossilizing",
|
622 |
-
"fraternisation": "fraternization",
|
623 |
-
"fraternise": "fraternize",
|
624 |
-
"fraternised": "fraternized",
|
625 |
-
"fraternises": "fraternizes",
|
626 |
-
"fraternising": "fraternizing",
|
627 |
-
"fulfil": "fulfill",
|
628 |
-
"fulfilment": "fulfillment",
|
629 |
-
"fulfils": "fulfills",
|
630 |
-
"funnelled": "funneled",
|
631 |
-
"funnelling": "funneling",
|
632 |
-
"gage": "gauge",
|
633 |
-
"gaged": "gauged",
|
634 |
-
"gages": "gauges",
|
635 |
-
"gaging": "gauging",
|
636 |
-
"galvanise": "galvanize",
|
637 |
-
"galvanised": "galvanized",
|
638 |
-
"galvanises": "galvanizes",
|
639 |
-
"galvanising": "galvanizing",
|
640 |
-
"gambolled": "gamboled",
|
641 |
-
"gambolling": "gamboling",
|
642 |
-
"gaol": "jail",
|
643 |
-
"gaolbird": "jailbird",
|
644 |
-
"gaolbirds": "jailbirds",
|
645 |
-
"gaolbreak": "jailbreak",
|
646 |
-
"gaolbreaks": "jailbreaks",
|
647 |
-
"gaoled": "jailed",
|
648 |
-
"gaoler": "jailer",
|
649 |
-
"gaolers": "jailers",
|
650 |
-
"gaoling": "jailing",
|
651 |
-
"gaols": "jails",
|
652 |
-
"gasses": "gases",
|
653 |
-
"generalisation": "generalization",
|
654 |
-
"generalisations": "generalizations",
|
655 |
-
"generalise": "generalize",
|
656 |
-
"generalised": "generalized",
|
657 |
-
"generalises": "generalizes",
|
658 |
-
"generalising": "generalizing",
|
659 |
-
"ghettoise": "ghettoize",
|
660 |
-
"ghettoised": "ghettoized",
|
661 |
-
"ghettoises": "ghettoizes",
|
662 |
-
"ghettoising": "ghettoizing",
|
663 |
-
"gipsies": "gypsies",
|
664 |
-
"glamor": "glamour",
|
665 |
-
"glamorise": "glamorize",
|
666 |
-
"glamorised": "glamorized",
|
667 |
-
"glamorises": "glamorizes",
|
668 |
-
"glamorising": "glamorizing",
|
669 |
-
"globalisation": "globalization",
|
670 |
-
"globalise": "globalize",
|
671 |
-
"globalised": "globalized",
|
672 |
-
"globalises": "globalizes",
|
673 |
-
"globalising": "globalizing",
|
674 |
-
"glueing": "gluing",
|
675 |
-
"goitre": "goiter",
|
676 |
-
"goitres": "goiters",
|
677 |
-
"gonorrhoea": "gonorrhea",
|
678 |
-
"gramme": "gram",
|
679 |
-
"grammes": "grams",
|
680 |
-
"gravelled": "graveled",
|
681 |
-
"grey": "gray",
|
682 |
-
"greyed": "grayed",
|
683 |
-
"greying": "graying",
|
684 |
-
"greyish": "grayish",
|
685 |
-
"greyness": "grayness",
|
686 |
-
"greys": "grays",
|
687 |
-
"grovelled": "groveled",
|
688 |
-
"grovelling": "groveling",
|
689 |
-
"groyne": "groin",
|
690 |
-
"groynes": "groins",
|
691 |
-
"gruelling": "grueling",
|
692 |
-
"gruellingly": "gruelingly",
|
693 |
-
"gryphon": "griffin",
|
694 |
-
"gryphons": "griffins",
|
695 |
-
"gynaecological": "gynecological",
|
696 |
-
"gynaecologist": "gynecologist",
|
697 |
-
"gynaecologists": "gynecologists",
|
698 |
-
"gynaecology": "gynecology",
|
699 |
-
"haematological": "hematological",
|
700 |
-
"haematologist": "hematologist",
|
701 |
-
"haematologists": "hematologists",
|
702 |
-
"haematology": "hematology",
|
703 |
-
"haemoglobin": "hemoglobin",
|
704 |
-
"haemophilia": "hemophilia",
|
705 |
-
"haemophiliac": "hemophiliac",
|
706 |
-
"haemophiliacs": "hemophiliacs",
|
707 |
-
"haemorrhage": "hemorrhage",
|
708 |
-
"haemorrhaged": "hemorrhaged",
|
709 |
-
"haemorrhages": "hemorrhages",
|
710 |
-
"haemorrhaging": "hemorrhaging",
|
711 |
-
"haemorrhoids": "hemorrhoids",
|
712 |
-
"harbour": "harbor",
|
713 |
-
"harboured": "harbored",
|
714 |
-
"harbouring": "harboring",
|
715 |
-
"harbours": "harbors",
|
716 |
-
"harmonisation": "harmonization",
|
717 |
-
"harmonise": "harmonize",
|
718 |
-
"harmonised": "harmonized",
|
719 |
-
"harmonises": "harmonizes",
|
720 |
-
"harmonising": "harmonizing",
|
721 |
-
"homoeopath": "homeopath",
|
722 |
-
"homoeopathic": "homeopathic",
|
723 |
-
"homoeopaths": "homeopaths",
|
724 |
-
"homoeopathy": "homeopathy",
|
725 |
-
"homogenise": "homogenize",
|
726 |
-
"homogenised": "homogenized",
|
727 |
-
"homogenises": "homogenizes",
|
728 |
-
"homogenising": "homogenizing",
|
729 |
-
"honour": "honor",
|
730 |
-
"honourable": "honorable",
|
731 |
-
"honourably": "honorably",
|
732 |
-
"honoured": "honored",
|
733 |
-
"honouring": "honoring",
|
734 |
-
"honours": "honors",
|
735 |
-
"hospitalisation": "hospitalization",
|
736 |
-
"hospitalise": "hospitalize",
|
737 |
-
"hospitalised": "hospitalized",
|
738 |
-
"hospitalises": "hospitalizes",
|
739 |
-
"hospitalising": "hospitalizing",
|
740 |
-
"humanise": "humanize",
|
741 |
-
"humanised": "humanized",
|
742 |
-
"humanises": "humanizes",
|
743 |
-
"humanising": "humanizing",
|
744 |
-
"humour": "humor",
|
745 |
-
"humoured": "humored",
|
746 |
-
"humouring": "humoring",
|
747 |
-
"humourless": "humorless",
|
748 |
-
"humours": "humors",
|
749 |
-
"hybridise": "hybridize",
|
750 |
-
"hybridised": "hybridized",
|
751 |
-
"hybridises": "hybridizes",
|
752 |
-
"hybridising": "hybridizing",
|
753 |
-
"hypnotise": "hypnotize",
|
754 |
-
"hypnotised": "hypnotized",
|
755 |
-
"hypnotises": "hypnotizes",
|
756 |
-
"hypnotising": "hypnotizing",
|
757 |
-
"hypothesise": "hypothesize",
|
758 |
-
"hypothesised": "hypothesized",
|
759 |
-
"hypothesises": "hypothesizes",
|
760 |
-
"hypothesising": "hypothesizing",
|
761 |
-
"idealisation": "idealization",
|
762 |
-
"idealise": "idealize",
|
763 |
-
"idealised": "idealized",
|
764 |
-
"idealises": "idealizes",
|
765 |
-
"idealising": "idealizing",
|
766 |
-
"idolise": "idolize",
|
767 |
-
"idolised": "idolized",
|
768 |
-
"idolises": "idolizes",
|
769 |
-
"idolising": "idolizing",
|
770 |
-
"immobilisation": "immobilization",
|
771 |
-
"immobilise": "immobilize",
|
772 |
-
"immobilised": "immobilized",
|
773 |
-
"immobiliser": "immobilizer",
|
774 |
-
"immobilisers": "immobilizers",
|
775 |
-
"immobilises": "immobilizes",
|
776 |
-
"immobilising": "immobilizing",
|
777 |
-
"immortalise": "immortalize",
|
778 |
-
"immortalised": "immortalized",
|
779 |
-
"immortalises": "immortalizes",
|
780 |
-
"immortalising": "immortalizing",
|
781 |
-
"immunisation": "immunization",
|
782 |
-
"immunise": "immunize",
|
783 |
-
"immunised": "immunized",
|
784 |
-
"immunises": "immunizes",
|
785 |
-
"immunising": "immunizing",
|
786 |
-
"impanelled": "impaneled",
|
787 |
-
"impanelling": "impaneling",
|
788 |
-
"imperilled": "imperiled",
|
789 |
-
"imperilling": "imperiling",
|
790 |
-
"individualise": "individualize",
|
791 |
-
"individualised": "individualized",
|
792 |
-
"individualises": "individualizes",
|
793 |
-
"individualising": "individualizing",
|
794 |
-
"industrialise": "industrialize",
|
795 |
-
"industrialised": "industrialized",
|
796 |
-
"industrialises": "industrializes",
|
797 |
-
"industrialising": "industrializing",
|
798 |
-
"inflexion": "inflection",
|
799 |
-
"inflexions": "inflections",
|
800 |
-
"initialise": "initialize",
|
801 |
-
"initialised": "initialized",
|
802 |
-
"initialises": "initializes",
|
803 |
-
"initialising": "initializing",
|
804 |
-
"initialled": "initialed",
|
805 |
-
"initialling": "initialing",
|
806 |
-
"instal": "install",
|
807 |
-
"instalment": "installment",
|
808 |
-
"instalments": "installments",
|
809 |
-
"instals": "installs",
|
810 |
-
"instil": "instill",
|
811 |
-
"instils": "instills",
|
812 |
-
"institutionalisation": "institutionalization",
|
813 |
-
"institutionalise": "institutionalize",
|
814 |
-
"institutionalised": "institutionalized",
|
815 |
-
"institutionalises": "institutionalizes",
|
816 |
-
"institutionalising": "institutionalizing",
|
817 |
-
"intellectualise": "intellectualize",
|
818 |
-
"intellectualised": "intellectualized",
|
819 |
-
"intellectualises": "intellectualizes",
|
820 |
-
"intellectualising": "intellectualizing",
|
821 |
-
"internalisation": "internalization",
|
822 |
-
"internalise": "internalize",
|
823 |
-
"internalised": "internalized",
|
824 |
-
"internalises": "internalizes",
|
825 |
-
"internalising": "internalizing",
|
826 |
-
"internationalisation": "internationalization",
|
827 |
-
"internationalise": "internationalize",
|
828 |
-
"internationalised": "internationalized",
|
829 |
-
"internationalises": "internationalizes",
|
830 |
-
"internationalising": "internationalizing",
|
831 |
-
"ionisation": "ionization",
|
832 |
-
"ionise": "ionize",
|
833 |
-
"ionised": "ionized",
|
834 |
-
"ioniser": "ionizer",
|
835 |
-
"ionisers": "ionizers",
|
836 |
-
"ionises": "ionizes",
|
837 |
-
"ionising": "ionizing",
|
838 |
-
"italicise": "italicize",
|
839 |
-
"italicised": "italicized",
|
840 |
-
"italicises": "italicizes",
|
841 |
-
"italicising": "italicizing",
|
842 |
-
"itemise": "itemize",
|
843 |
-
"itemised": "itemized",
|
844 |
-
"itemises": "itemizes",
|
845 |
-
"itemising": "itemizing",
|
846 |
-
"jeopardise": "jeopardize",
|
847 |
-
"jeopardised": "jeopardized",
|
848 |
-
"jeopardises": "jeopardizes",
|
849 |
-
"jeopardising": "jeopardizing",
|
850 |
-
"jewelled": "jeweled",
|
851 |
-
"jeweller": "jeweler",
|
852 |
-
"jewellers": "jewelers",
|
853 |
-
"jewellery": "jewelry",
|
854 |
-
"judgement": "judgment",
|
855 |
-
"kilogramme": "kilogram",
|
856 |
-
"kilogrammes": "kilograms",
|
857 |
-
"kilometre": "kilometer",
|
858 |
-
"kilometres": "kilometers",
|
859 |
-
"labelled": "labeled",
|
860 |
-
"labelling": "labeling",
|
861 |
-
"labour": "labor",
|
862 |
-
"laboured": "labored",
|
863 |
-
"labourer": "laborer",
|
864 |
-
"labourers": "laborers",
|
865 |
-
"labouring": "laboring",
|
866 |
-
"labours": "labors",
|
867 |
-
"lacklustre": "lackluster",
|
868 |
-
"legalisation": "legalization",
|
869 |
-
"legalise": "legalize",
|
870 |
-
"legalised": "legalized",
|
871 |
-
"legalises": "legalizes",
|
872 |
-
"legalising": "legalizing",
|
873 |
-
"legitimise": "legitimize",
|
874 |
-
"legitimised": "legitimized",
|
875 |
-
"legitimises": "legitimizes",
|
876 |
-
"legitimising": "legitimizing",
|
877 |
-
"leukaemia": "leukemia",
|
878 |
-
"levelled": "leveled",
|
879 |
-
"leveller": "leveler",
|
880 |
-
"levellers": "levelers",
|
881 |
-
"levelling": "leveling",
|
882 |
-
"libelled": "libeled",
|
883 |
-
"libelling": "libeling",
|
884 |
-
"libellous": "libelous",
|
885 |
-
"liberalisation": "liberalization",
|
886 |
-
"liberalise": "liberalize",
|
887 |
-
"liberalised": "liberalized",
|
888 |
-
"liberalises": "liberalizes",
|
889 |
-
"liberalising": "liberalizing",
|
890 |
-
"licence": "license",
|
891 |
-
"licenced": "licensed",
|
892 |
-
"licences": "licenses",
|
893 |
-
"licencing": "licensing",
|
894 |
-
"likeable": "likable",
|
895 |
-
"lionisation": "lionization",
|
896 |
-
"lionise": "lionize",
|
897 |
-
"lionised": "lionized",
|
898 |
-
"lionises": "lionizes",
|
899 |
-
"lionising": "lionizing",
|
900 |
-
"liquidise": "liquidize",
|
901 |
-
"liquidised": "liquidized",
|
902 |
-
"liquidiser": "liquidizer",
|
903 |
-
"liquidisers": "liquidizers",
|
904 |
-
"liquidises": "liquidizes",
|
905 |
-
"liquidising": "liquidizing",
|
906 |
-
"litre": "liter",
|
907 |
-
"litres": "liters",
|
908 |
-
"localise": "localize",
|
909 |
-
"localised": "localized",
|
910 |
-
"localises": "localizes",
|
911 |
-
"localising": "localizing",
|
912 |
-
"louvre": "louver",
|
913 |
-
"louvred": "louvered",
|
914 |
-
"louvres": "louvers",
|
915 |
-
"lustre": "luster",
|
916 |
-
"magnetise": "magnetize",
|
917 |
-
"magnetised": "magnetized",
|
918 |
-
"magnetises": "magnetizes",
|
919 |
-
"magnetising": "magnetizing",
|
920 |
-
"manoeuvrability": "maneuverability",
|
921 |
-
"manoeuvrable": "maneuverable",
|
922 |
-
"manoeuvre": "maneuver",
|
923 |
-
"manoeuvred": "maneuvered",
|
924 |
-
"manoeuvres": "maneuvers",
|
925 |
-
"manoeuvring": "maneuvering",
|
926 |
-
"manoeuvrings": "maneuverings",
|
927 |
-
"marginalisation": "marginalization",
|
928 |
-
"marginalise": "marginalize",
|
929 |
-
"marginalised": "marginalized",
|
930 |
-
"marginalises": "marginalizes",
|
931 |
-
"marginalising": "marginalizing",
|
932 |
-
"marshalled": "marshaled",
|
933 |
-
"marshalling": "marshaling",
|
934 |
-
"marvelled": "marveled",
|
935 |
-
"marvelling": "marveling",
|
936 |
-
"marvellous": "marvelous",
|
937 |
-
"marvellously": "marvelously",
|
938 |
-
"materialisation": "materialization",
|
939 |
-
"materialise": "materialize",
|
940 |
-
"materialised": "materialized",
|
941 |
-
"materialises": "materializes",
|
942 |
-
"materialising": "materializing",
|
943 |
-
"maximisation": "maximization",
|
944 |
-
"maximise": "maximize",
|
945 |
-
"maximised": "maximized",
|
946 |
-
"maximises": "maximizes",
|
947 |
-
"maximising": "maximizing",
|
948 |
-
"meagre": "meager",
|
949 |
-
"mechanisation": "mechanization",
|
950 |
-
"mechanise": "mechanize",
|
951 |
-
"mechanised": "mechanized",
|
952 |
-
"mechanises": "mechanizes",
|
953 |
-
"mechanising": "mechanizing",
|
954 |
-
"mediaeval": "medieval",
|
955 |
-
"memorialise": "memorialize",
|
956 |
-
"memorialised": "memorialized",
|
957 |
-
"memorialises": "memorializes",
|
958 |
-
"memorialising": "memorializing",
|
959 |
-
"memorise": "memorize",
|
960 |
-
"memorised": "memorized",
|
961 |
-
"memorises": "memorizes",
|
962 |
-
"memorising": "memorizing",
|
963 |
-
"mesmerise": "mesmerize",
|
964 |
-
"mesmerised": "mesmerized",
|
965 |
-
"mesmerises": "mesmerizes",
|
966 |
-
"mesmerising": "mesmerizing",
|
967 |
-
"metabolise": "metabolize",
|
968 |
-
"metabolised": "metabolized",
|
969 |
-
"metabolises": "metabolizes",
|
970 |
-
"metabolising": "metabolizing",
|
971 |
-
"metre": "meter",
|
972 |
-
"metres": "meters",
|
973 |
-
"mhm": "hmm",
|
974 |
-
"micrometre": "micrometer",
|
975 |
-
"micrometres": "micrometers",
|
976 |
-
"militarise": "militarize",
|
977 |
-
"militarised": "militarized",
|
978 |
-
"militarises": "militarizes",
|
979 |
-
"militarising": "militarizing",
|
980 |
-
"milligramme": "milligram",
|
981 |
-
"milligrammes": "milligrams",
|
982 |
-
"millilitre": "milliliter",
|
983 |
-
"millilitres": "milliliters",
|
984 |
-
"millimetre": "millimeter",
|
985 |
-
"millimetres": "millimeters",
|
986 |
-
"miniaturisation": "miniaturization",
|
987 |
-
"miniaturise": "miniaturize",
|
988 |
-
"miniaturised": "miniaturized",
|
989 |
-
"miniaturises": "miniaturizes",
|
990 |
-
"miniaturising": "miniaturizing",
|
991 |
-
"minibusses": "minibuses",
|
992 |
-
"minimise": "minimize",
|
993 |
-
"minimised": "minimized",
|
994 |
-
"minimises": "minimizes",
|
995 |
-
"minimising": "minimizing",
|
996 |
-
"misbehaviour": "misbehavior",
|
997 |
-
"misdemeanour": "misdemeanor",
|
998 |
-
"misdemeanours": "misdemeanors",
|
999 |
-
"misspelt": "misspelled",
|
1000 |
-
"mitre": "miter",
|
1001 |
-
"mitres": "miters",
|
1002 |
-
"mm": "hmm",
|
1003 |
-
"mmm": "hmm",
|
1004 |
-
"mobilisation": "mobilization",
|
1005 |
-
"mobilise": "mobilize",
|
1006 |
-
"mobilised": "mobilized",
|
1007 |
-
"mobilises": "mobilizes",
|
1008 |
-
"mobilising": "mobilizing",
|
1009 |
-
"modelled": "modeled",
|
1010 |
-
"modeller": "modeler",
|
1011 |
-
"modellers": "modelers",
|
1012 |
-
"modelling": "modeling",
|
1013 |
-
"modernise": "modernize",
|
1014 |
-
"modernised": "modernized",
|
1015 |
-
"modernises": "modernizes",
|
1016 |
-
"modernising": "modernizing",
|
1017 |
-
"moisturise": "moisturize",
|
1018 |
-
"moisturised": "moisturized",
|
1019 |
-
"moisturiser": "moisturizer",
|
1020 |
-
"moisturisers": "moisturizers",
|
1021 |
-
"moisturises": "moisturizes",
|
1022 |
-
"moisturising": "moisturizing",
|
1023 |
-
"monologue": "monolog",
|
1024 |
-
"monologues": "monologs",
|
1025 |
-
"monopolisation": "monopolization",
|
1026 |
-
"monopolise": "monopolize",
|
1027 |
-
"monopolised": "monopolized",
|
1028 |
-
"monopolises": "monopolizes",
|
1029 |
-
"monopolising": "monopolizing",
|
1030 |
-
"moralise": "moralize",
|
1031 |
-
"moralised": "moralized",
|
1032 |
-
"moralises": "moralizes",
|
1033 |
-
"moralising": "moralizing",
|
1034 |
-
"motorised": "motorized",
|
1035 |
-
"mould": "mold",
|
1036 |
-
"moulded": "molded",
|
1037 |
-
"moulder": "molder",
|
1038 |
-
"mouldered": "moldered",
|
1039 |
-
"mouldering": "moldering",
|
1040 |
-
"moulders": "molders",
|
1041 |
-
"mouldier": "moldier",
|
1042 |
-
"mouldiest": "moldiest",
|
1043 |
-
"moulding": "molding",
|
1044 |
-
"mouldings": "moldings",
|
1045 |
-
"moulds": "molds",
|
1046 |
-
"mouldy": "moldy",
|
1047 |
-
"moult": "molt",
|
1048 |
-
"moulted": "molted",
|
1049 |
-
"moulting": "molting",
|
1050 |
-
"moults": "molts",
|
1051 |
-
"moustache": "mustache",
|
1052 |
-
"moustached": "mustached",
|
1053 |
-
"moustaches": "mustaches",
|
1054 |
-
"moustachioed": "mustachioed",
|
1055 |
-
"multicoloured": "multicolored",
|
1056 |
-
"nationalisation": "nationalization",
|
1057 |
-
"nationalisations": "nationalizations",
|
1058 |
-
"nationalise": "nationalize",
|
1059 |
-
"nationalised": "nationalized",
|
1060 |
-
"nationalises": "nationalizes",
|
1061 |
-
"nationalising": "nationalizing",
|
1062 |
-
"naturalisation": "naturalization",
|
1063 |
-
"naturalise": "naturalize",
|
1064 |
-
"naturalised": "naturalized",
|
1065 |
-
"naturalises": "naturalizes",
|
1066 |
-
"naturalising": "naturalizing",
|
1067 |
-
"neighbour": "neighbor",
|
1068 |
-
"neighbourhood": "neighborhood",
|
1069 |
-
"neighbourhoods": "neighborhoods",
|
1070 |
-
"neighbouring": "neighboring",
|
1071 |
-
"neighbourliness": "neighborliness",
|
1072 |
-
"neighbourly": "neighborly",
|
1073 |
-
"neighbours": "neighbors",
|
1074 |
-
"neutralisation": "neutralization",
|
1075 |
-
"neutralise": "neutralize",
|
1076 |
-
"neutralised": "neutralized",
|
1077 |
-
"neutralises": "neutralizes",
|
1078 |
-
"neutralising": "neutralizing",
|
1079 |
-
"normalisation": "normalization",
|
1080 |
-
"normalise": "normalize",
|
1081 |
-
"normalised": "normalized",
|
1082 |
-
"normalises": "normalizes",
|
1083 |
-
"normalising": "normalizing",
|
1084 |
-
"odour": "odor",
|
1085 |
-
"odourless": "odorless",
|
1086 |
-
"odours": "odors",
|
1087 |
-
"oesophagus": "esophagus",
|
1088 |
-
"oesophaguses": "esophaguses",
|
1089 |
-
"oestrogen": "estrogen",
|
1090 |
-
"offence": "offense",
|
1091 |
-
"offences": "offenses",
|
1092 |
-
"omelette": "omelet",
|
1093 |
-
"omelettes": "omelets",
|
1094 |
-
"optimise": "optimize",
|
1095 |
-
"optimised": "optimized",
|
1096 |
-
"optimises": "optimizes",
|
1097 |
-
"optimising": "optimizing",
|
1098 |
-
"organisation": "organization",
|
1099 |
-
"organisational": "organizational",
|
1100 |
-
"organisations": "organizations",
|
1101 |
-
"organise": "organize",
|
1102 |
-
"organised": "organized",
|
1103 |
-
"organiser": "organizer",
|
1104 |
-
"organisers": "organizers",
|
1105 |
-
"organises": "organizes",
|
1106 |
-
"organising": "organizing",
|
1107 |
-
"orthopaedic": "orthopedic",
|
1108 |
-
"orthopaedics": "orthopedics",
|
1109 |
-
"ostracise": "ostracize",
|
1110 |
-
"ostracised": "ostracized",
|
1111 |
-
"ostracises": "ostracizes",
|
1112 |
-
"ostracising": "ostracizing",
|
1113 |
-
"outmanoeuvre": "outmaneuver",
|
1114 |
-
"outmanoeuvred": "outmaneuvered",
|
1115 |
-
"outmanoeuvres": "outmaneuvers",
|
1116 |
-
"outmanoeuvring": "outmaneuvering",
|
1117 |
-
"overemphasise": "overemphasize",
|
1118 |
-
"overemphasised": "overemphasized",
|
1119 |
-
"overemphasises": "overemphasizes",
|
1120 |
-
"overemphasising": "overemphasizing",
|
1121 |
-
"oxidisation": "oxidization",
|
1122 |
-
"oxidise": "oxidize",
|
1123 |
-
"oxidised": "oxidized",
|
1124 |
-
"oxidises": "oxidizes",
|
1125 |
-
"oxidising": "oxidizing",
|
1126 |
-
"paederast": "pederast",
|
1127 |
-
"paederasts": "pederasts",
|
1128 |
-
"paediatric": "pediatric",
|
1129 |
-
"paediatrician": "pediatrician",
|
1130 |
-
"paediatricians": "pediatricians",
|
1131 |
-
"paediatrics": "pediatrics",
|
1132 |
-
"paedophile": "pedophile",
|
1133 |
-
"paedophiles": "pedophiles",
|
1134 |
-
"paedophilia": "pedophilia",
|
1135 |
-
"palaeolithic": "paleolithic",
|
1136 |
-
"palaeontologist": "paleontologist",
|
1137 |
-
"palaeontologists": "paleontologists",
|
1138 |
-
"palaeontology": "paleontology",
|
1139 |
-
"panelled": "paneled",
|
1140 |
-
"panelling": "paneling",
|
1141 |
-
"panellist": "panelist",
|
1142 |
-
"panellists": "panelists",
|
1143 |
-
"paralyse": "paralyze",
|
1144 |
-
"paralysed": "paralyzed",
|
1145 |
-
"paralyses": "paralyzes",
|
1146 |
-
"paralysing": "paralyzing",
|
1147 |
-
"parcelled": "parceled",
|
1148 |
-
"parcelling": "parceling",
|
1149 |
-
"parlour": "parlor",
|
1150 |
-
"parlours": "parlors",
|
1151 |
-
"particularise": "particularize",
|
1152 |
-
"particularised": "particularized",
|
1153 |
-
"particularises": "particularizes",
|
1154 |
-
"particularising": "particularizing",
|
1155 |
-
"passivisation": "passivization",
|
1156 |
-
"passivise": "passivize",
|
1157 |
-
"passivised": "passivized",
|
1158 |
-
"passivises": "passivizes",
|
1159 |
-
"passivising": "passivizing",
|
1160 |
-
"pasteurisation": "pasteurization",
|
1161 |
-
"pasteurise": "pasteurize",
|
1162 |
-
"pasteurised": "pasteurized",
|
1163 |
-
"pasteurises": "pasteurizes",
|
1164 |
-
"pasteurising": "pasteurizing",
|
1165 |
-
"patronise": "patronize",
|
1166 |
-
"patronised": "patronized",
|
1167 |
-
"patronises": "patronizes",
|
1168 |
-
"patronising": "patronizing",
|
1169 |
-
"patronisingly": "patronizingly",
|
1170 |
-
"pedalled": "pedaled",
|
1171 |
-
"pedalling": "pedaling",
|
1172 |
-
"pedestrianisation": "pedestrianization",
|
1173 |
-
"pedestrianise": "pedestrianize",
|
1174 |
-
"pedestrianised": "pedestrianized",
|
1175 |
-
"pedestrianises": "pedestrianizes",
|
1176 |
-
"pedestrianising": "pedestrianizing",
|
1177 |
-
"penalise": "penalize",
|
1178 |
-
"penalised": "penalized",
|
1179 |
-
"penalises": "penalizes",
|
1180 |
-
"penalising": "penalizing",
|
1181 |
-
"pencilled": "penciled",
|
1182 |
-
"pencilling": "penciling",
|
1183 |
-
"personalise": "personalize",
|
1184 |
-
"personalised": "personalized",
|
1185 |
-
"personalises": "personalizes",
|
1186 |
-
"personalising": "personalizing",
|
1187 |
-
"pharmacopoeia": "pharmacopeia",
|
1188 |
-
"pharmacopoeias": "pharmacopeias",
|
1189 |
-
"philosophise": "philosophize",
|
1190 |
-
"philosophised": "philosophized",
|
1191 |
-
"philosophises": "philosophizes",
|
1192 |
-
"philosophising": "philosophizing",
|
1193 |
-
"philtre": "filter",
|
1194 |
-
"philtres": "filters",
|
1195 |
-
"phoney": "phony",
|
1196 |
-
"plagiarise": "plagiarize",
|
1197 |
-
"plagiarised": "plagiarized",
|
1198 |
-
"plagiarises": "plagiarizes",
|
1199 |
-
"plagiarising": "plagiarizing",
|
1200 |
-
"plough": "plow",
|
1201 |
-
"ploughed": "plowed",
|
1202 |
-
"ploughing": "plowing",
|
1203 |
-
"ploughman": "plowman",
|
1204 |
-
"ploughmen": "plowmen",
|
1205 |
-
"ploughs": "plows",
|
1206 |
-
"ploughshare": "plowshare",
|
1207 |
-
"ploughshares": "plowshares",
|
1208 |
-
"polarisation": "polarization",
|
1209 |
-
"polarise": "polarize",
|
1210 |
-
"polarised": "polarized",
|
1211 |
-
"polarises": "polarizes",
|
1212 |
-
"polarising": "polarizing",
|
1213 |
-
"politicisation": "politicization",
|
1214 |
-
"politicise": "politicize",
|
1215 |
-
"politicised": "politicized",
|
1216 |
-
"politicises": "politicizes",
|
1217 |
-
"politicising": "politicizing",
|
1218 |
-
"popularisation": "popularization",
|
1219 |
-
"popularise": "popularize",
|
1220 |
-
"popularised": "popularized",
|
1221 |
-
"popularises": "popularizes",
|
1222 |
-
"popularising": "popularizing",
|
1223 |
-
"pouffe": "pouf",
|
1224 |
-
"pouffes": "poufs",
|
1225 |
-
"practise": "practice",
|
1226 |
-
"practised": "practiced",
|
1227 |
-
"practises": "practices",
|
1228 |
-
"practising": "practicing",
|
1229 |
-
"praesidium": "presidium",
|
1230 |
-
"praesidiums": "presidiums",
|
1231 |
-
"pressurisation": "pressurization",
|
1232 |
-
"pressurise": "pressurize",
|
1233 |
-
"pressurised": "pressurized",
|
1234 |
-
"pressurises": "pressurizes",
|
1235 |
-
"pressurising": "pressurizing",
|
1236 |
-
"pretence": "pretense",
|
1237 |
-
"pretences": "pretenses",
|
1238 |
-
"primaeval": "primeval",
|
1239 |
-
"prioritisation": "prioritization",
|
1240 |
-
"prioritise": "prioritize",
|
1241 |
-
"prioritised": "prioritized",
|
1242 |
-
"prioritises": "prioritizes",
|
1243 |
-
"prioritising": "prioritizing",
|
1244 |
-
"privatisation": "privatization",
|
1245 |
-
"privatisations": "privatizations",
|
1246 |
-
"privatise": "privatize",
|
1247 |
-
"privatised": "privatized",
|
1248 |
-
"privatises": "privatizes",
|
1249 |
-
"privatising": "privatizing",
|
1250 |
-
"professionalisation": "professionalization",
|
1251 |
-
"professionalise": "professionalize",
|
1252 |
-
"professionalised": "professionalized",
|
1253 |
-
"professionalises": "professionalizes",
|
1254 |
-
"professionalising": "professionalizing",
|
1255 |
-
"programme": "program",
|
1256 |
-
"programmes": "programs",
|
1257 |
-
"prologue": "prolog",
|
1258 |
-
"prologues": "prologs",
|
1259 |
-
"propagandise": "propagandize",
|
1260 |
-
"propagandised": "propagandized",
|
1261 |
-
"propagandises": "propagandizes",
|
1262 |
-
"propagandising": "propagandizing",
|
1263 |
-
"proselytise": "proselytize",
|
1264 |
-
"proselytised": "proselytized",
|
1265 |
-
"proselytiser": "proselytizer",
|
1266 |
-
"proselytisers": "proselytizers",
|
1267 |
-
"proselytises": "proselytizes",
|
1268 |
-
"proselytising": "proselytizing",
|
1269 |
-
"psychoanalyse": "psychoanalyze",
|
1270 |
-
"psychoanalysed": "psychoanalyzed",
|
1271 |
-
"psychoanalyses": "psychoanalyzes",
|
1272 |
-
"psychoanalysing": "psychoanalyzing",
|
1273 |
-
"publicise": "publicize",
|
1274 |
-
"publicised": "publicized",
|
1275 |
-
"publicises": "publicizes",
|
1276 |
-
"publicising": "publicizing",
|
1277 |
-
"pulverisation": "pulverization",
|
1278 |
-
"pulverise": "pulverize",
|
1279 |
-
"pulverised": "pulverized",
|
1280 |
-
"pulverises": "pulverizes",
|
1281 |
-
"pulverising": "pulverizing",
|
1282 |
-
"pummelled": "pummel",
|
1283 |
-
"pummelling": "pummeled",
|
1284 |
-
"pyjama": "pajama",
|
1285 |
-
"pyjamas": "pajamas",
|
1286 |
-
"pzazz": "pizzazz",
|
1287 |
-
"quarrelled": "quarreled",
|
1288 |
-
"quarrelling": "quarreling",
|
1289 |
-
"radicalise": "radicalize",
|
1290 |
-
"radicalised": "radicalized",
|
1291 |
-
"radicalises": "radicalizes",
|
1292 |
-
"radicalising": "radicalizing",
|
1293 |
-
"rancour": "rancor",
|
1294 |
-
"randomise": "randomize",
|
1295 |
-
"randomised": "randomized",
|
1296 |
-
"randomises": "randomizes",
|
1297 |
-
"randomising": "randomizing",
|
1298 |
-
"rationalisation": "rationalization",
|
1299 |
-
"rationalisations": "rationalizations",
|
1300 |
-
"rationalise": "rationalize",
|
1301 |
-
"rationalised": "rationalized",
|
1302 |
-
"rationalises": "rationalizes",
|
1303 |
-
"rationalising": "rationalizing",
|
1304 |
-
"ravelled": "raveled",
|
1305 |
-
"ravelling": "raveling",
|
1306 |
-
"realisable": "realizable",
|
1307 |
-
"realisation": "realization",
|
1308 |
-
"realisations": "realizations",
|
1309 |
-
"realise": "realize",
|
1310 |
-
"realised": "realized",
|
1311 |
-
"realises": "realizes",
|
1312 |
-
"realising": "realizing",
|
1313 |
-
"recognisable": "recognizable",
|
1314 |
-
"recognisably": "recognizably",
|
1315 |
-
"recognisance": "recognizance",
|
1316 |
-
"recognise": "recognize",
|
1317 |
-
"recognised": "recognized",
|
1318 |
-
"recognises": "recognizes",
|
1319 |
-
"recognising": "recognizing",
|
1320 |
-
"reconnoitre": "reconnoiter",
|
1321 |
-
"reconnoitred": "reconnoitered",
|
1322 |
-
"reconnoitres": "reconnoiters",
|
1323 |
-
"reconnoitring": "reconnoitering",
|
1324 |
-
"refuelled": "refueled",
|
1325 |
-
"refuelling": "refueling",
|
1326 |
-
"regularisation": "regularization",
|
1327 |
-
"regularise": "regularize",
|
1328 |
-
"regularised": "regularized",
|
1329 |
-
"regularises": "regularizes",
|
1330 |
-
"regularising": "regularizing",
|
1331 |
-
"remodelled": "remodeled",
|
1332 |
-
"remodelling": "remodeling",
|
1333 |
-
"remould": "remold",
|
1334 |
-
"remoulded": "remolded",
|
1335 |
-
"remoulding": "remolding",
|
1336 |
-
"remoulds": "remolds",
|
1337 |
-
"reorganisation": "reorganization",
|
1338 |
-
"reorganisations": "reorganizations",
|
1339 |
-
"reorganise": "reorganize",
|
1340 |
-
"reorganised": "reorganized",
|
1341 |
-
"reorganises": "reorganizes",
|
1342 |
-
"reorganising": "reorganizing",
|
1343 |
-
"revelled": "reveled",
|
1344 |
-
"reveller": "reveler",
|
1345 |
-
"revellers": "revelers",
|
1346 |
-
"revelling": "reveling",
|
1347 |
-
"revitalise": "revitalize",
|
1348 |
-
"revitalised": "revitalized",
|
1349 |
-
"revitalises": "revitalizes",
|
1350 |
-
"revitalising": "revitalizing",
|
1351 |
-
"revolutionise": "revolutionize",
|
1352 |
-
"revolutionised": "revolutionized",
|
1353 |
-
"revolutionises": "revolutionizes",
|
1354 |
-
"revolutionising": "revolutionizing",
|
1355 |
-
"rhapsodise": "rhapsodize",
|
1356 |
-
"rhapsodised": "rhapsodized",
|
1357 |
-
"rhapsodises": "rhapsodizes",
|
1358 |
-
"rhapsodising": "rhapsodizing",
|
1359 |
-
"rigour": "rigor",
|
1360 |
-
"rigours": "rigors",
|
1361 |
-
"ritualised": "ritualized",
|
1362 |
-
"rivalled": "rivaled",
|
1363 |
-
"rivalling": "rivaling",
|
1364 |
-
"romanticise": "romanticize",
|
1365 |
-
"romanticised": "romanticized",
|
1366 |
-
"romanticises": "romanticizes",
|
1367 |
-
"romanticising": "romanticizing",
|
1368 |
-
"rumour": "rumor",
|
1369 |
-
"rumoured": "rumored",
|
1370 |
-
"rumours": "rumors",
|
1371 |
-
"sabre": "saber",
|
1372 |
-
"sabres": "sabers",
|
1373 |
-
"saltpetre": "saltpeter",
|
1374 |
-
"sanitise": "sanitize",
|
1375 |
-
"sanitised": "sanitized",
|
1376 |
-
"sanitises": "sanitizes",
|
1377 |
-
"sanitising": "sanitizing",
|
1378 |
-
"satirise": "satirize",
|
1379 |
-
"satirised": "satirized",
|
1380 |
-
"satirises": "satirizes",
|
1381 |
-
"satirising": "satirizing",
|
1382 |
-
"saviour": "savior",
|
1383 |
-
"saviours": "saviors",
|
1384 |
-
"savour": "savor",
|
1385 |
-
"savoured": "savored",
|
1386 |
-
"savouries": "savories",
|
1387 |
-
"savouring": "savoring",
|
1388 |
-
"savours": "savors",
|
1389 |
-
"savoury": "savory",
|
1390 |
-
"scandalise": "scandalize",
|
1391 |
-
"scandalised": "scandalized",
|
1392 |
-
"scandalises": "scandalizes",
|
1393 |
-
"scandalising": "scandalizing",
|
1394 |
-
"sceptic": "skeptic",
|
1395 |
-
"sceptical": "skeptical",
|
1396 |
-
"sceptically": "skeptically",
|
1397 |
-
"scepticism": "skepticism",
|
1398 |
-
"sceptics": "skeptics",
|
1399 |
-
"sceptre": "scepter",
|
1400 |
-
"sceptres": "scepters",
|
1401 |
-
"scrutinise": "scrutinize",
|
1402 |
-
"scrutinised": "scrutinized",
|
1403 |
-
"scrutinises": "scrutinizes",
|
1404 |
-
"scrutinising": "scrutinizing",
|
1405 |
-
"secularisation": "secularization",
|
1406 |
-
"secularise": "secularize",
|
1407 |
-
"secularised": "secularized",
|
1408 |
-
"secularises": "secularizes",
|
1409 |
-
"secularising": "secularizing",
|
1410 |
-
"sensationalise": "sensationalize",
|
1411 |
-
"sensationalised": "sensationalized",
|
1412 |
-
"sensationalises": "sensationalizes",
|
1413 |
-
"sensationalising": "sensationalizing",
|
1414 |
-
"sensitise": "sensitize",
|
1415 |
-
"sensitised": "sensitized",
|
1416 |
-
"sensitises": "sensitizes",
|
1417 |
-
"sensitising": "sensitizing",
|
1418 |
-
"sentimentalise": "sentimentalize",
|
1419 |
-
"sentimentalised": "sentimentalized",
|
1420 |
-
"sentimentalises": "sentimentalizes",
|
1421 |
-
"sentimentalising": "sentimentalizing",
|
1422 |
-
"sepulchre": "sepulcher",
|
1423 |
-
"sepulchres": "sepulchers",
|
1424 |
-
"serialisation": "serialization",
|
1425 |
-
"serialisations": "serializations",
|
1426 |
-
"serialise": "serialize",
|
1427 |
-
"serialised": "serialized",
|
1428 |
-
"serialises": "serializes",
|
1429 |
-
"serialising": "serializing",
|
1430 |
-
"sermonise": "sermonize",
|
1431 |
-
"sermonised": "sermonized",
|
1432 |
-
"sermonises": "sermonizes",
|
1433 |
-
"sermonising": "sermonizing",
|
1434 |
-
"sheikh": "sheik",
|
1435 |
-
"shovelled": "shoveled",
|
1436 |
-
"shovelling": "shoveling",
|
1437 |
-
"shrivelled": "shriveled",
|
1438 |
-
"shrivelling": "shriveling",
|
1439 |
-
"signalise": "signalize",
|
1440 |
-
"signalised": "signalized",
|
1441 |
-
"signalises": "signalizes",
|
1442 |
-
"signalising": "signalizing",
|
1443 |
-
"signalled": "signaled",
|
1444 |
-
"signalling": "signaling",
|
1445 |
-
"smoulder": "smolder",
|
1446 |
-
"smouldered": "smoldered",
|
1447 |
-
"smouldering": "smoldering",
|
1448 |
-
"smoulders": "smolders",
|
1449 |
-
"snivelled": "sniveled",
|
1450 |
-
"snivelling": "sniveling",
|
1451 |
-
"snorkelled": "snorkeled",
|
1452 |
-
"snorkelling": "snorkeling",
|
1453 |
-
"snowplough": "snowplow",
|
1454 |
-
"snowploughs": "snowplow",
|
1455 |
-
"socialisation": "socialization",
|
1456 |
-
"socialise": "socialize",
|
1457 |
-
"socialised": "socialized",
|
1458 |
-
"socialises": "socializes",
|
1459 |
-
"socialising": "socializing",
|
1460 |
-
"sodomise": "sodomize",
|
1461 |
-
"sodomised": "sodomized",
|
1462 |
-
"sodomises": "sodomizes",
|
1463 |
-
"sodomising": "sodomizing",
|
1464 |
-
"solemnise": "solemnize",
|
1465 |
-
"solemnised": "solemnized",
|
1466 |
-
"solemnises": "solemnizes",
|
1467 |
-
"solemnising": "solemnizing",
|
1468 |
-
"sombre": "somber",
|
1469 |
-
"specialisation": "specialization",
|
1470 |
-
"specialisations": "specializations",
|
1471 |
-
"specialise": "specialize",
|
1472 |
-
"specialised": "specialized",
|
1473 |
-
"specialises": "specializes",
|
1474 |
-
"specialising": "specializing",
|
1475 |
-
"spectre": "specter",
|
1476 |
-
"spectres": "specters",
|
1477 |
-
"spiralled": "spiraled",
|
1478 |
-
"spiralling": "spiraling",
|
1479 |
-
"splendour": "splendor",
|
1480 |
-
"splendours": "splendors",
|
1481 |
-
"squirrelled": "squirreled",
|
1482 |
-
"squirrelling": "squirreling",
|
1483 |
-
"stabilisation": "stabilization",
|
1484 |
-
"stabilise": "stabilize",
|
1485 |
-
"stabilised": "stabilized",
|
1486 |
-
"stabiliser": "stabilizer",
|
1487 |
-
"stabilisers": "stabilizers",
|
1488 |
-
"stabilises": "stabilizes",
|
1489 |
-
"stabilising": "stabilizing",
|
1490 |
-
"standardisation": "standardization",
|
1491 |
-
"standardise": "standardize",
|
1492 |
-
"standardised": "standardized",
|
1493 |
-
"standardises": "standardizes",
|
1494 |
-
"standardising": "standardizing",
|
1495 |
-
"stencilled": "stenciled",
|
1496 |
-
"stencilling": "stenciling",
|
1497 |
-
"sterilisation": "sterilization",
|
1498 |
-
"sterilisations": "sterilizations",
|
1499 |
-
"sterilise": "sterilize",
|
1500 |
-
"sterilised": "sterilized",
|
1501 |
-
"steriliser": "sterilizer",
|
1502 |
-
"sterilisers": "sterilizers",
|
1503 |
-
"sterilises": "sterilizes",
|
1504 |
-
"sterilising": "sterilizing",
|
1505 |
-
"stigmatisation": "stigmatization",
|
1506 |
-
"stigmatise": "stigmatize",
|
1507 |
-
"stigmatised": "stigmatized",
|
1508 |
-
"stigmatises": "stigmatizes",
|
1509 |
-
"stigmatising": "stigmatizing",
|
1510 |
-
"storey": "story",
|
1511 |
-
"storeys": "stories",
|
1512 |
-
"subsidisation": "subsidization",
|
1513 |
-
"subsidise": "subsidize",
|
1514 |
-
"subsidised": "subsidized",
|
1515 |
-
"subsidiser": "subsidizer",
|
1516 |
-
"subsidisers": "subsidizers",
|
1517 |
-
"subsidises": "subsidizes",
|
1518 |
-
"subsidising": "subsidizing",
|
1519 |
-
"succour": "succor",
|
1520 |
-
"succoured": "succored",
|
1521 |
-
"succouring": "succoring",
|
1522 |
-
"succours": "succors",
|
1523 |
-
"sulphate": "sulfate",
|
1524 |
-
"sulphates": "sulfates",
|
1525 |
-
"sulphide": "sulfide",
|
1526 |
-
"sulphides": "sulfides",
|
1527 |
-
"sulphur": "sulfur",
|
1528 |
-
"sulphurous": "sulfurous",
|
1529 |
-
"summarise": "summarize",
|
1530 |
-
"summarised": "summarized",
|
1531 |
-
"summarises": "summarizes",
|
1532 |
-
"summarising": "summarizing",
|
1533 |
-
"swivelled": "swiveled",
|
1534 |
-
"swivelling": "swiveling",
|
1535 |
-
"symbolise": "symbolize",
|
1536 |
-
"symbolised": "symbolized",
|
1537 |
-
"symbolises": "symbolizes",
|
1538 |
-
"symbolising": "symbolizing",
|
1539 |
-
"sympathise": "sympathize",
|
1540 |
-
"sympathised": "sympathized",
|
1541 |
-
"sympathiser": "sympathizer",
|
1542 |
-
"sympathisers": "sympathizers",
|
1543 |
-
"sympathises": "sympathizes",
|
1544 |
-
"sympathising": "sympathizing",
|
1545 |
-
"synchronisation": "synchronization",
|
1546 |
-
"synchronise": "synchronize",
|
1547 |
-
"synchronised": "synchronized",
|
1548 |
-
"synchronises": "synchronizes",
|
1549 |
-
"synchronising": "synchronizing",
|
1550 |
-
"synthesise": "synthesize",
|
1551 |
-
"synthesised": "synthesized",
|
1552 |
-
"synthesiser": "synthesizer",
|
1553 |
-
"synthesisers": "synthesizers",
|
1554 |
-
"synthesises": "synthesizes",
|
1555 |
-
"synthesising": "synthesizing",
|
1556 |
-
"syphon": "siphon",
|
1557 |
-
"syphoned": "siphoned",
|
1558 |
-
"syphoning": "siphoning",
|
1559 |
-
"syphons": "siphons",
|
1560 |
-
"systematisation": "systematization",
|
1561 |
-
"systematise": "systematize",
|
1562 |
-
"systematised": "systematized",
|
1563 |
-
"systematises": "systematizes",
|
1564 |
-
"systematising": "systematizing",
|
1565 |
-
"tantalise": "tantalize",
|
1566 |
-
"tantalised": "tantalized",
|
1567 |
-
"tantalises": "tantalizes",
|
1568 |
-
"tantalising": "tantalizing",
|
1569 |
-
"tantalisingly": "tantalizingly",
|
1570 |
-
"tasselled": "tasseled",
|
1571 |
-
"technicolour": "technicolor",
|
1572 |
-
"temporise": "temporize",
|
1573 |
-
"temporised": "temporized",
|
1574 |
-
"temporises": "temporizes",
|
1575 |
-
"temporising": "temporizing",
|
1576 |
-
"tenderise": "tenderize",
|
1577 |
-
"tenderised": "tenderized",
|
1578 |
-
"tenderises": "tenderizes",
|
1579 |
-
"tenderising": "tenderizing",
|
1580 |
-
"terrorise": "terrorize",
|
1581 |
-
"terrorised": "terrorized",
|
1582 |
-
"terrorises": "terrorizes",
|
1583 |
-
"terrorising": "terrorizing",
|
1584 |
-
"theatre": "theater",
|
1585 |
-
"theatregoer": "theatergoer",
|
1586 |
-
"theatregoers": "theatergoers",
|
1587 |
-
"theatres": "theaters",
|
1588 |
-
"theorise": "theorize",
|
1589 |
-
"theorised": "theorized",
|
1590 |
-
"theorises": "theorizes",
|
1591 |
-
"theorising": "theorizing",
|
1592 |
-
"tonne": "ton",
|
1593 |
-
"tonnes": "tons",
|
1594 |
-
"towelled": "toweled",
|
1595 |
-
"towelling": "toweling",
|
1596 |
-
"toxaemia": "toxemia",
|
1597 |
-
"tranquillise": "tranquilize",
|
1598 |
-
"tranquillised": "tranquilized",
|
1599 |
-
"tranquilliser": "tranquilizer",
|
1600 |
-
"tranquillisers": "tranquilizers",
|
1601 |
-
"tranquillises": "tranquilizes",
|
1602 |
-
"tranquillising": "tranquilizing",
|
1603 |
-
"tranquillity": "tranquility",
|
1604 |
-
"tranquillize": "tranquilize",
|
1605 |
-
"tranquillized": "tranquilized",
|
1606 |
-
"tranquillizer": "tranquilizer",
|
1607 |
-
"tranquillizers": "tranquilizers",
|
1608 |
-
"tranquillizes": "tranquilizes",
|
1609 |
-
"tranquillizing": "tranquilizing",
|
1610 |
-
"tranquilly": "tranquility",
|
1611 |
-
"transistorised": "transistorized",
|
1612 |
-
"traumatise": "traumatize",
|
1613 |
-
"traumatised": "traumatized",
|
1614 |
-
"traumatises": "traumatizes",
|
1615 |
-
"traumatising": "traumatizing",
|
1616 |
-
"travelled": "traveled",
|
1617 |
-
"traveller": "traveler",
|
1618 |
-
"travellers": "travelers",
|
1619 |
-
"travelling": "traveling",
|
1620 |
-
"travelog": "travelogue",
|
1621 |
-
"travelogs": "travelogues",
|
1622 |
-
"trialled": "trialed",
|
1623 |
-
"trialling": "trialing",
|
1624 |
-
"tricolour": "tricolor",
|
1625 |
-
"tricolours": "tricolors",
|
1626 |
-
"trivialise": "trivialize",
|
1627 |
-
"trivialised": "trivialized",
|
1628 |
-
"trivialises": "trivializes",
|
1629 |
-
"trivialising": "trivializing",
|
1630 |
-
"tumour": "tumor",
|
1631 |
-
"tumours": "tumors",
|
1632 |
-
"tunnelled": "tunneled",
|
1633 |
-
"tunnelling": "tunneling",
|
1634 |
-
"tyrannise": "tyrannize",
|
1635 |
-
"tyrannised": "tyrannized",
|
1636 |
-
"tyrannises": "tyrannizes",
|
1637 |
-
"tyrannising": "tyrannizing",
|
1638 |
-
"tyre": "tire",
|
1639 |
-
"tyres": "tires",
|
1640 |
-
"unauthorised": "unauthorized",
|
1641 |
-
"uncivilised": "uncivilized",
|
1642 |
-
"underutilised": "underutilized",
|
1643 |
-
"unequalled": "unequaled",
|
1644 |
-
"unfavourable": "unfavorable",
|
1645 |
-
"unfavourably": "unfavorably",
|
1646 |
-
"unionisation": "unionization",
|
1647 |
-
"unionise": "unionize",
|
1648 |
-
"unionised": "unionized",
|
1649 |
-
"unionises": "unionizes",
|
1650 |
-
"unionising": "unionizing",
|
1651 |
-
"unorganised": "unorganized",
|
1652 |
-
"unravelled": "unraveled",
|
1653 |
-
"unravelling": "unraveling",
|
1654 |
-
"unrecognisable": "unrecognizable",
|
1655 |
-
"unrecognised": "unrecognized",
|
1656 |
-
"unrivalled": "unrivaled",
|
1657 |
-
"unsavoury": "unsavory",
|
1658 |
-
"untrammelled": "untrammeled",
|
1659 |
-
"urbanisation": "urbanization",
|
1660 |
-
"urbanise": "urbanize",
|
1661 |
-
"urbanised": "urbanized",
|
1662 |
-
"urbanises": "urbanizes",
|
1663 |
-
"urbanising": "urbanizing",
|
1664 |
-
"utilisable": "utilizable",
|
1665 |
-
"utilisation": "utilization",
|
1666 |
-
"utilise": "utilize",
|
1667 |
-
"utilised": "utilized",
|
1668 |
-
"utilises": "utilizes",
|
1669 |
-
"utilising": "utilizing",
|
1670 |
-
"valour": "valor",
|
1671 |
-
"vandalise": "vandalize",
|
1672 |
-
"vandalised": "vandalized",
|
1673 |
-
"vandalises": "vandalizes",
|
1674 |
-
"vandalising": "vandalizing",
|
1675 |
-
"vaporisation": "vaporization",
|
1676 |
-
"vaporise": "vaporize",
|
1677 |
-
"vaporised": "vaporized",
|
1678 |
-
"vaporises": "vaporizes",
|
1679 |
-
"vaporising": "vaporizing",
|
1680 |
-
"vapour": "vapor",
|
1681 |
-
"vapours": "vapors",
|
1682 |
-
"verbalise": "verbalize",
|
1683 |
-
"verbalised": "verbalized",
|
1684 |
-
"verbalises": "verbalizes",
|
1685 |
-
"verbalising": "verbalizing",
|
1686 |
-
"victimisation": "victimization",
|
1687 |
-
"victimise": "victimize",
|
1688 |
-
"victimised": "victimized",
|
1689 |
-
"victimises": "victimizes",
|
1690 |
-
"victimising": "victimizing",
|
1691 |
-
"videodisc": "videodisk",
|
1692 |
-
"videodiscs": "videodisks",
|
1693 |
-
"vigour": "vigor",
|
1694 |
-
"visualisation": "visualization",
|
1695 |
-
"visualisations": "visualizations",
|
1696 |
-
"visualise": "visualize",
|
1697 |
-
"visualised": "visualized",
|
1698 |
-
"visualises": "visualizes",
|
1699 |
-
"visualising": "visualizing",
|
1700 |
-
"vocalisation": "vocalization",
|
1701 |
-
"vocalisations": "vocalizations",
|
1702 |
-
"vocalise": "vocalize",
|
1703 |
-
"vocalised": "vocalized",
|
1704 |
-
"vocalises": "vocalizes",
|
1705 |
-
"vocalising": "vocalizing",
|
1706 |
-
"vulcanised": "vulcanized",
|
1707 |
-
"vulgarisation": "vulgarization",
|
1708 |
-
"vulgarise": "vulgarize",
|
1709 |
-
"vulgarised": "vulgarized",
|
1710 |
-
"vulgarises": "vulgarizes",
|
1711 |
-
"vulgarising": "vulgarizing",
|
1712 |
-
"waggon": "wagon",
|
1713 |
-
"waggons": "wagons",
|
1714 |
-
"watercolour": "watercolor",
|
1715 |
-
"watercolours": "watercolors",
|
1716 |
-
"weaselled": "weaseled",
|
1717 |
-
"weaselling": "weaseling",
|
1718 |
-
"westernisation": "westernization",
|
1719 |
-
"westernise": "westernize",
|
1720 |
-
"westernised": "westernized",
|
1721 |
-
"westernises": "westernizes",
|
1722 |
-
"westernising": "westernizing",
|
1723 |
-
"womanise": "womanize",
|
1724 |
-
"womanised": "womanized",
|
1725 |
-
"womaniser": "womanizer",
|
1726 |
-
"womanisers": "womanizers",
|
1727 |
-
"womanises": "womanizes",
|
1728 |
-
"womanising": "womanizing",
|
1729 |
-
"woollen": "woolen",
|
1730 |
-
"woollens": "woolens",
|
1731 |
-
"woollies": "woolies",
|
1732 |
-
"woolly": "wooly",
|
1733 |
-
"worshipped": "worshiped",
|
1734 |
-
"worshipper": "worshiper",
|
1735 |
-
"worshipping": "worshiping",
|
1736 |
-
"yodelled": "yodeled",
|
1737 |
-
"yodelling": "yodeling",
|
1738 |
-
"yoghourt": "yogurt",
|
1739 |
-
"yoghourts": "yogurts",
|
1740 |
-
"yoghurt": "yogurt",
|
1741 |
-
"yoghurts": "yogurts"
|
1742 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/benchmark/requirements.benchmark.txt
DELETED
@@ -1,6 +0,0 @@
|
|
1 |
-
transformers
|
2 |
-
jiwer
|
3 |
-
evaluate
|
4 |
-
datasets
|
5 |
-
memory_profiler
|
6 |
-
py3nvml
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/benchmark/speed_benchmark.py
DELETED
@@ -1,31 +0,0 @@
|
|
1 |
-
import argparse
|
2 |
-
import timeit
|
3 |
-
|
4 |
-
from typing import Callable
|
5 |
-
|
6 |
-
from utils import inference
|
7 |
-
|
8 |
-
parser = argparse.ArgumentParser(description="Speed benchmark")
|
9 |
-
parser.add_argument(
|
10 |
-
"--repeat",
|
11 |
-
type=int,
|
12 |
-
default=3,
|
13 |
-
help="Times an experiment will be run.",
|
14 |
-
)
|
15 |
-
args = parser.parse_args()
|
16 |
-
|
17 |
-
|
18 |
-
def measure_speed(func: Callable[[], None]):
|
19 |
-
# as written in https://docs.python.org/3/library/timeit.html#timeit.Timer.repeat,
|
20 |
-
# min should be taken rather than the average
|
21 |
-
runtimes = timeit.repeat(
|
22 |
-
func,
|
23 |
-
repeat=args.repeat,
|
24 |
-
number=10,
|
25 |
-
)
|
26 |
-
print(runtimes)
|
27 |
-
print("Min execution time: %.3fs" % (min(runtimes) / 10.0))
|
28 |
-
|
29 |
-
|
30 |
-
if __name__ == "__main__":
|
31 |
-
measure_speed(inference)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/benchmark/utils.py
DELETED
@@ -1,39 +0,0 @@
|
|
1 |
-
import logging
|
2 |
-
|
3 |
-
from threading import Thread
|
4 |
-
from typing import Optional
|
5 |
-
|
6 |
-
from faster_whisper import WhisperModel
|
7 |
-
|
8 |
-
model_path = "large-v3"
|
9 |
-
model = WhisperModel(model_path, device="cuda")
|
10 |
-
|
11 |
-
|
12 |
-
def inference():
|
13 |
-
segments, info = model.transcribe("benchmark.m4a", language="fr")
|
14 |
-
for segment in segments:
|
15 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
16 |
-
|
17 |
-
|
18 |
-
def get_logger(name: Optional[str] = None) -> logging.Logger:
|
19 |
-
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
20 |
-
logger = logging.getLogger(name)
|
21 |
-
logger.setLevel(logging.DEBUG)
|
22 |
-
handler = logging.StreamHandler()
|
23 |
-
handler.setFormatter(formatter)
|
24 |
-
logger.addHandler(handler)
|
25 |
-
return logger
|
26 |
-
|
27 |
-
|
28 |
-
class MyThread(Thread):
|
29 |
-
def __init__(self, func, params):
|
30 |
-
super(MyThread, self).__init__()
|
31 |
-
self.func = func
|
32 |
-
self.params = params
|
33 |
-
self.result = None
|
34 |
-
|
35 |
-
def run(self):
|
36 |
-
self.result = self.func(*self.params)
|
37 |
-
|
38 |
-
def get_result(self):
|
39 |
-
return self.result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/benchmark/wer_benchmark.py
DELETED
@@ -1,64 +0,0 @@
|
|
1 |
-
import argparse
|
2 |
-
import json
|
3 |
-
import os
|
4 |
-
|
5 |
-
from datasets import load_dataset
|
6 |
-
from evaluate import load
|
7 |
-
from tqdm import tqdm
|
8 |
-
from transformers.models.whisper.english_normalizer import EnglishTextNormalizer
|
9 |
-
|
10 |
-
from faster_whisper import WhisperModel
|
11 |
-
|
12 |
-
parser = argparse.ArgumentParser(description="WER benchmark")
|
13 |
-
parser.add_argument(
|
14 |
-
"--audio_numb",
|
15 |
-
type=int,
|
16 |
-
default=None,
|
17 |
-
help="Specify the number of validation audio files in the dataset."
|
18 |
-
" Set to None to retrieve all audio files.",
|
19 |
-
)
|
20 |
-
args = parser.parse_args()
|
21 |
-
|
22 |
-
model_path = "large-v3"
|
23 |
-
model = WhisperModel(model_path, device="cuda")
|
24 |
-
|
25 |
-
# load the dataset with streaming mode
|
26 |
-
dataset = load_dataset("librispeech_asr", "clean", split="validation", streaming=True)
|
27 |
-
|
28 |
-
# define the evaluation metric
|
29 |
-
wer_metric = load("wer")
|
30 |
-
|
31 |
-
with open(os.path.join(os.path.dirname(__file__), "normalizer.json"), "r") as f:
|
32 |
-
normalizer = EnglishTextNormalizer(json.load(f))
|
33 |
-
|
34 |
-
|
35 |
-
def inference(batch):
|
36 |
-
batch["transcription"] = []
|
37 |
-
for sample in batch["audio"]:
|
38 |
-
segments, info = model.transcribe(sample["array"], language="en")
|
39 |
-
batch["transcription"].append("".join([segment.text for segment in segments]))
|
40 |
-
batch["reference"] = batch["text"]
|
41 |
-
return batch
|
42 |
-
|
43 |
-
|
44 |
-
dataset = dataset.map(function=inference, batched=True, batch_size=16)
|
45 |
-
|
46 |
-
all_transcriptions = []
|
47 |
-
all_references = []
|
48 |
-
|
49 |
-
# iterate over the dataset and run inference
|
50 |
-
for i, result in tqdm(enumerate(dataset), desc="Evaluating..."):
|
51 |
-
all_transcriptions.append(result["transcription"])
|
52 |
-
all_references.append(result["reference"])
|
53 |
-
if args.audio_numb and i == (args.audio_numb - 1):
|
54 |
-
break
|
55 |
-
|
56 |
-
# normalize predictions and references
|
57 |
-
all_transcriptions = [normalizer(transcription) for transcription in all_transcriptions]
|
58 |
-
all_references = [normalizer(reference) for reference in all_references]
|
59 |
-
|
60 |
-
# compute the WER metric
|
61 |
-
wer = 100 * wer_metric.compute(
|
62 |
-
predictions=all_transcriptions, references=all_references
|
63 |
-
)
|
64 |
-
print("WER: %.3f" % wer)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/__init__.py
DELETED
@@ -1,14 +0,0 @@
|
|
1 |
-
from faster_whisper.audio import decode_audio
|
2 |
-
from faster_whisper.transcribe import BatchedInferencePipeline, WhisperModel
|
3 |
-
from faster_whisper.utils import available_models, download_model, format_timestamp
|
4 |
-
from faster_whisper.version import __version__
|
5 |
-
|
6 |
-
__all__ = [
|
7 |
-
"available_models",
|
8 |
-
"decode_audio",
|
9 |
-
"WhisperModel",
|
10 |
-
"BatchedInferencePipeline",
|
11 |
-
"download_model",
|
12 |
-
"format_timestamp",
|
13 |
-
"__version__",
|
14 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/assets/__init__.py
DELETED
File without changes
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/assets/pyannote_vad_model.bin
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:0b5b3216d60a2d32fc086b47ea8c67589aaeb26b7e07fcbe620d6d0b83e209ea
|
3 |
-
size 17719103
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/assets/silero_vad.onnx
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:6b99cbfd39246b6706f98ec13c7c50c6b299181f2474fa05cbc8046acc274396
|
3 |
-
size 2313101
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/audio.py
DELETED
@@ -1,58 +0,0 @@
|
|
1 |
-
from typing import BinaryIO, Union
|
2 |
-
|
3 |
-
import torch
|
4 |
-
import torchaudio
|
5 |
-
|
6 |
-
|
7 |
-
def decode_audio(
|
8 |
-
input_file: Union[str, BinaryIO],
|
9 |
-
sampling_rate: int = 16000,
|
10 |
-
split_stereo: bool = False,
|
11 |
-
):
|
12 |
-
"""Decodes the audio.
|
13 |
-
|
14 |
-
Args:
|
15 |
-
input_file: Path to the input file or a file-like object.
|
16 |
-
sampling_rate: Resample the audio to this sample rate.
|
17 |
-
split_stereo: Return separate left and right channels.
|
18 |
-
|
19 |
-
Returns:
|
20 |
-
A float32 Torch Tensor.
|
21 |
-
|
22 |
-
If `split_stereo` is enabled, the function returns a 2-tuple with the
|
23 |
-
separated left and right channels.
|
24 |
-
"""
|
25 |
-
|
26 |
-
waveform, audio_sf = torchaudio.load(input_file) # waveform: channels X T
|
27 |
-
|
28 |
-
if audio_sf != sampling_rate:
|
29 |
-
waveform = torchaudio.functional.resample(
|
30 |
-
waveform, orig_freq=audio_sf, new_freq=sampling_rate
|
31 |
-
)
|
32 |
-
if split_stereo:
|
33 |
-
return waveform[0], waveform[1]
|
34 |
-
|
35 |
-
return waveform.mean(0)
|
36 |
-
|
37 |
-
|
38 |
-
def pad_or_trim(array, length: int, *, axis: int = -1):
|
39 |
-
"""
|
40 |
-
Pad or trim the audio array to N_SAMPLES, as expected by the encoder.
|
41 |
-
"""
|
42 |
-
axis = axis % array.ndim
|
43 |
-
if array.shape[axis] > length:
|
44 |
-
idx = [Ellipsis] * axis + [slice(length)] + [Ellipsis] * (array.ndim - axis - 1)
|
45 |
-
return array[idx]
|
46 |
-
|
47 |
-
if array.shape[axis] < length:
|
48 |
-
pad_widths = (
|
49 |
-
[
|
50 |
-
0,
|
51 |
-
]
|
52 |
-
* array.ndim
|
53 |
-
* 2
|
54 |
-
)
|
55 |
-
pad_widths[2 * axis] = length - array.shape[axis]
|
56 |
-
array = torch.nn.functional.pad(array, tuple(pad_widths[::-1]))
|
57 |
-
|
58 |
-
return array
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/feature_extractor.py
DELETED
@@ -1,114 +0,0 @@
|
|
1 |
-
import torch
|
2 |
-
|
3 |
-
|
4 |
-
# Adapted from https://github.com/huggingface/transformers/blob/main/src/transformers/models/whisper/feature_extraction_whisper.py # noqa: E501
|
5 |
-
class FeatureExtractor:
|
6 |
-
def __init__(
|
7 |
-
self,
|
8 |
-
device: str = "auto",
|
9 |
-
feature_size=80,
|
10 |
-
sampling_rate=16000,
|
11 |
-
hop_length=160,
|
12 |
-
chunk_length=30,
|
13 |
-
n_fft=400,
|
14 |
-
):
|
15 |
-
if device == "auto":
|
16 |
-
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
17 |
-
else:
|
18 |
-
self.device = device
|
19 |
-
self.n_fft = n_fft
|
20 |
-
self.hop_length = hop_length
|
21 |
-
self.chunk_length = chunk_length
|
22 |
-
self.n_samples = chunk_length * sampling_rate
|
23 |
-
self.nb_max_frames = self.n_samples // hop_length
|
24 |
-
self.time_per_frame = hop_length / sampling_rate
|
25 |
-
self.sampling_rate = sampling_rate
|
26 |
-
self.mel_filters = self.get_mel_filters(
|
27 |
-
sampling_rate, n_fft, n_mels=feature_size
|
28 |
-
)
|
29 |
-
|
30 |
-
@staticmethod
|
31 |
-
def get_mel_filters(sr, n_fft, n_mels=128):
|
32 |
-
"""
|
33 |
-
Implementation of librosa.filters.mel in Pytorch
|
34 |
-
"""
|
35 |
-
# Initialize the weights
|
36 |
-
n_mels = int(n_mels)
|
37 |
-
|
38 |
-
# Center freqs of each FFT bin
|
39 |
-
fftfreqs = torch.fft.rfftfreq(n=n_fft, d=1.0 / sr)
|
40 |
-
|
41 |
-
# 'Center freqs' of mel bands - uniformly spaced between limits
|
42 |
-
min_mel = 0.0
|
43 |
-
max_mel = 45.245640471924965
|
44 |
-
|
45 |
-
mels = torch.linspace(min_mel, max_mel, n_mels + 2)
|
46 |
-
|
47 |
-
# Fill in the linear scale
|
48 |
-
f_min = 0.0
|
49 |
-
f_sp = 200.0 / 3
|
50 |
-
freqs = f_min + f_sp * mels
|
51 |
-
|
52 |
-
# And now the nonlinear scale
|
53 |
-
min_log_hz = 1000.0 # beginning of log region (Hz)
|
54 |
-
min_log_mel = (min_log_hz - f_min) / f_sp # same (Mels)
|
55 |
-
logstep = torch.log(torch.tensor(6.4)) / 27.0 # step size for log region
|
56 |
-
|
57 |
-
# If we have vector data, vectorize
|
58 |
-
log_t = mels >= min_log_mel
|
59 |
-
freqs[log_t] = min_log_hz * torch.exp(logstep * (mels[log_t] - min_log_mel))
|
60 |
-
|
61 |
-
mel_f = freqs
|
62 |
-
|
63 |
-
fdiff = torch.diff(mel_f)
|
64 |
-
ramps = mel_f.view(-1, 1) - fftfreqs.view(1, -1)
|
65 |
-
|
66 |
-
lower = -ramps[:-2] / fdiff[:-1].unsqueeze(1)
|
67 |
-
upper = ramps[2:] / fdiff[1:].unsqueeze(1)
|
68 |
-
|
69 |
-
# Intersect them with each other and zero, vectorized across all i
|
70 |
-
weights = torch.maximum(torch.zeros_like(lower), torch.minimum(lower, upper))
|
71 |
-
|
72 |
-
# Slaney-style mel is scaled to be approx constant energy per channel
|
73 |
-
enorm = 2.0 / (mel_f[2 : n_mels + 2] - mel_f[:n_mels])
|
74 |
-
weights *= enorm.unsqueeze(1)
|
75 |
-
|
76 |
-
return weights
|
77 |
-
|
78 |
-
def __call__(self, waveform, padding=True, chunk_length=None, to_cpu=False):
|
79 |
-
"""
|
80 |
-
Compute the log-Mel spectrogram of the provided audio.
|
81 |
-
"""
|
82 |
-
|
83 |
-
if chunk_length is not None:
|
84 |
-
self.n_samples = chunk_length * self.sampling_rate
|
85 |
-
self.nb_max_frames = self.n_samples // self.hop_length
|
86 |
-
|
87 |
-
if waveform.dtype is not torch.float32:
|
88 |
-
waveform = waveform.to(torch.float32)
|
89 |
-
|
90 |
-
waveform = (
|
91 |
-
waveform.to(self.device)
|
92 |
-
if self.device == "cuda" and not waveform.is_cuda
|
93 |
-
else waveform
|
94 |
-
)
|
95 |
-
|
96 |
-
if padding:
|
97 |
-
waveform = torch.nn.functional.pad(waveform, (0, self.n_samples))
|
98 |
-
|
99 |
-
window = torch.hann_window(self.n_fft).to(waveform.device)
|
100 |
-
|
101 |
-
stft = torch.stft(
|
102 |
-
waveform, self.n_fft, self.hop_length, window=window, return_complex=True
|
103 |
-
)
|
104 |
-
magnitudes = stft[..., :-1].abs() ** 2
|
105 |
-
|
106 |
-
mel_spec = self.mel_filters.to(waveform.device) @ magnitudes
|
107 |
-
|
108 |
-
log_spec = torch.clamp(mel_spec, min=1e-10).log10()
|
109 |
-
log_spec = torch.maximum(log_spec, log_spec.max() - 8.0)
|
110 |
-
log_spec = (log_spec + 4.0) / 4.0
|
111 |
-
|
112 |
-
# When the model is running on multiple GPUs, the output should be moved
|
113 |
-
# to the CPU since we don't know which GPU will handle the next job.
|
114 |
-
return log_spec.cpu() if to_cpu else log_spec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/tokenizer.py
DELETED
@@ -1,314 +0,0 @@
|
|
1 |
-
import string
|
2 |
-
|
3 |
-
from functools import cached_property
|
4 |
-
from typing import List, Optional, Tuple
|
5 |
-
|
6 |
-
import tokenizers
|
7 |
-
|
8 |
-
|
9 |
-
class Tokenizer:
|
10 |
-
"""Simple wrapper around a tokenizers.Tokenizer."""
|
11 |
-
|
12 |
-
def __init__(
|
13 |
-
self,
|
14 |
-
tokenizer: tokenizers.Tokenizer,
|
15 |
-
multilingual: bool,
|
16 |
-
task: Optional[str] = None,
|
17 |
-
language: Optional[str] = None,
|
18 |
-
):
|
19 |
-
self.tokenizer = tokenizer
|
20 |
-
|
21 |
-
if multilingual:
|
22 |
-
if task not in _TASKS:
|
23 |
-
raise ValueError(
|
24 |
-
"'%s' is not a valid task (accepted tasks: %s)"
|
25 |
-
% (task, ", ".join(_TASKS))
|
26 |
-
)
|
27 |
-
|
28 |
-
if language not in _LANGUAGE_CODES:
|
29 |
-
raise ValueError(
|
30 |
-
"'%s' is not a valid language code (accepted language codes: %s)"
|
31 |
-
% (language, ", ".join(_LANGUAGE_CODES))
|
32 |
-
)
|
33 |
-
|
34 |
-
self.task = self.tokenizer.token_to_id("<|%s|>" % task)
|
35 |
-
self.language = self.tokenizer.token_to_id("<|%s|>" % language)
|
36 |
-
self.language_code = language
|
37 |
-
else:
|
38 |
-
self.task = None
|
39 |
-
self.language = None
|
40 |
-
self.language_code = "en"
|
41 |
-
|
42 |
-
@cached_property
|
43 |
-
def transcribe(self) -> int:
|
44 |
-
return self.tokenizer.token_to_id("<|transcribe|>")
|
45 |
-
|
46 |
-
@cached_property
|
47 |
-
def translate(self) -> int:
|
48 |
-
return self.tokenizer.token_to_id("<|translate|>")
|
49 |
-
|
50 |
-
@cached_property
|
51 |
-
def sot(self) -> int:
|
52 |
-
return self.tokenizer.token_to_id("<|startoftranscript|>")
|
53 |
-
|
54 |
-
@cached_property
|
55 |
-
def sot_lm(self) -> int:
|
56 |
-
return self.tokenizer.token_to_id("<|startoflm|>")
|
57 |
-
|
58 |
-
@cached_property
|
59 |
-
def sot_prev(self) -> int:
|
60 |
-
return self.tokenizer.token_to_id("<|startofprev|>")
|
61 |
-
|
62 |
-
@cached_property
|
63 |
-
def eot(self) -> int:
|
64 |
-
return self.tokenizer.token_to_id("<|endoftext|>")
|
65 |
-
|
66 |
-
@cached_property
|
67 |
-
def no_timestamps(self) -> int:
|
68 |
-
return self.tokenizer.token_to_id("<|notimestamps|>")
|
69 |
-
|
70 |
-
@property
|
71 |
-
def timestamp_begin(self) -> int:
|
72 |
-
return self.no_timestamps + 1
|
73 |
-
|
74 |
-
@property
|
75 |
-
def sot_sequence(self) -> List[int]:
|
76 |
-
sequence = [self.sot]
|
77 |
-
|
78 |
-
if self.language is not None:
|
79 |
-
sequence.append(self.language)
|
80 |
-
|
81 |
-
if self.task is not None:
|
82 |
-
sequence.append(self.task)
|
83 |
-
|
84 |
-
return sequence
|
85 |
-
|
86 |
-
def encode(self, text: str) -> List[int]:
|
87 |
-
return self.tokenizer.encode(text, add_special_tokens=False).ids
|
88 |
-
|
89 |
-
def decode(self, tokens: List[int]) -> str:
|
90 |
-
text_tokens = [token for token in tokens if token < self.eot]
|
91 |
-
return self.tokenizer.decode(text_tokens)
|
92 |
-
|
93 |
-
def decode_with_timestamps(self, tokens: List[int]) -> str:
|
94 |
-
outputs = [[]]
|
95 |
-
|
96 |
-
for token in tokens:
|
97 |
-
if token >= self.timestamp_begin:
|
98 |
-
timestamp = f"<|{(token - self.timestamp_begin) * 0.02:.2f}|>"
|
99 |
-
outputs.append(timestamp)
|
100 |
-
outputs.append([])
|
101 |
-
else:
|
102 |
-
outputs[-1].append(token)
|
103 |
-
|
104 |
-
return "".join(
|
105 |
-
[s if isinstance(s, str) else self.tokenizer.decode(s) for s in outputs]
|
106 |
-
)
|
107 |
-
|
108 |
-
@cached_property
|
109 |
-
def non_speech_tokens(self) -> Tuple[int]:
|
110 |
-
"""
|
111 |
-
Returns the list of tokens to suppress in order to avoid any speaker tags or non-speech
|
112 |
-
annotations, to prevent sampling texts that are not actually spoken in the audio, e.g.
|
113 |
-
|
114 |
-
- ♪♪♪
|
115 |
-
- ( SPEAKING FOREIGN LANGUAGE )
|
116 |
-
- [DAVID] Hey there,
|
117 |
-
|
118 |
-
keeping basic punctuations like commas, periods, question marks, exclamation points, etc.
|
119 |
-
"""
|
120 |
-
symbols = list('"#()*+/:;<=>@[\\]^_`{|}~「」『』')
|
121 |
-
symbols += (
|
122 |
-
"<< >> <<< >>> -- --- -( -[ (' (\" (( )) ((( ))) [[ ]] {{ }} ♪♪ ♪♪♪".split()
|
123 |
-
)
|
124 |
-
|
125 |
-
# symbols that may be a single token or multiple tokens depending on the tokenizer.
|
126 |
-
# In case they're multiple tokens, suppress the first token, which is safe because:
|
127 |
-
# These are between U+2640 and U+267F miscellaneous symbols that are okay to suppress
|
128 |
-
# in generations, and in the 3-byte UTF-8 representation they share the first two bytes.
|
129 |
-
miscellaneous = set("♩♪♫♬♭♮♯")
|
130 |
-
assert all(0x2640 <= ord(c) <= 0x267F for c in miscellaneous)
|
131 |
-
|
132 |
-
# allow hyphens "-" and single quotes "'" between words, but not at the beginning of a word
|
133 |
-
result = {self.encode(" -")[0], self.encode(" '")[0]}
|
134 |
-
for symbol in symbols + list(miscellaneous):
|
135 |
-
for tokens in [
|
136 |
-
self.encode(symbol),
|
137 |
-
self.encode(" " + symbol),
|
138 |
-
]:
|
139 |
-
if len(tokens) == 1 or symbol in miscellaneous:
|
140 |
-
result.add(tokens[0])
|
141 |
-
|
142 |
-
return tuple(sorted(result))
|
143 |
-
|
144 |
-
def split_to_word_tokens(
|
145 |
-
self, tokens: List[int]
|
146 |
-
) -> Tuple[List[str], List[List[int]]]:
|
147 |
-
if self.language_code in {"zh", "ja", "th", "lo", "my", "yue"}:
|
148 |
-
# These languages don't typically use spaces, so it is difficult to split words
|
149 |
-
# without morpheme analysis. Here, we instead split words at any
|
150 |
-
# position where the tokens are decoded as valid unicode points
|
151 |
-
return self.split_tokens_on_unicode(tokens)
|
152 |
-
|
153 |
-
return self.split_tokens_on_spaces(tokens)
|
154 |
-
|
155 |
-
def split_tokens_on_unicode(
|
156 |
-
self, tokens: List[int]
|
157 |
-
) -> Tuple[List[str], List[List[int]]]:
|
158 |
-
decoded_full = self.decode_with_timestamps(tokens)
|
159 |
-
replacement_char = "\ufffd"
|
160 |
-
|
161 |
-
words = []
|
162 |
-
word_tokens = []
|
163 |
-
current_tokens = []
|
164 |
-
unicode_offset = 0
|
165 |
-
|
166 |
-
for token in tokens:
|
167 |
-
current_tokens.append(token)
|
168 |
-
decoded = self.decode_with_timestamps(current_tokens)
|
169 |
-
|
170 |
-
try:
|
171 |
-
replacement_char_index = decoded.index(replacement_char)
|
172 |
-
replacement_char_index += unicode_offset
|
173 |
-
except ValueError:
|
174 |
-
replacement_char_index = None
|
175 |
-
|
176 |
-
if replacement_char_index is None or (
|
177 |
-
replacement_char_index < len(decoded_full)
|
178 |
-
and decoded_full[replacement_char_index] == replacement_char
|
179 |
-
):
|
180 |
-
words.append(decoded)
|
181 |
-
word_tokens.append(current_tokens)
|
182 |
-
current_tokens = []
|
183 |
-
unicode_offset += len(decoded)
|
184 |
-
|
185 |
-
return words, word_tokens
|
186 |
-
|
187 |
-
def split_tokens_on_spaces(
|
188 |
-
self, tokens: List[int]
|
189 |
-
) -> Tuple[List[str], List[List[int]]]:
|
190 |
-
subwords, subword_tokens_list = self.split_tokens_on_unicode(tokens)
|
191 |
-
words = []
|
192 |
-
word_tokens = []
|
193 |
-
|
194 |
-
for subword, subword_tokens in zip(subwords, subword_tokens_list):
|
195 |
-
special = subword_tokens[0] >= self.eot
|
196 |
-
with_space = subword.startswith(" ")
|
197 |
-
punctuation = subword.strip() in string.punctuation
|
198 |
-
if special or with_space or punctuation or len(words) == 0:
|
199 |
-
words.append(subword)
|
200 |
-
word_tokens.append(subword_tokens)
|
201 |
-
else:
|
202 |
-
words[-1] = words[-1] + subword
|
203 |
-
word_tokens[-1].extend(subword_tokens)
|
204 |
-
|
205 |
-
return words, word_tokens
|
206 |
-
|
207 |
-
|
208 |
-
_TASKS = (
|
209 |
-
"transcribe",
|
210 |
-
"translate",
|
211 |
-
)
|
212 |
-
|
213 |
-
_LANGUAGE_CODES = (
|
214 |
-
"af",
|
215 |
-
"am",
|
216 |
-
"ar",
|
217 |
-
"as",
|
218 |
-
"az",
|
219 |
-
"ba",
|
220 |
-
"be",
|
221 |
-
"bg",
|
222 |
-
"bn",
|
223 |
-
"bo",
|
224 |
-
"br",
|
225 |
-
"bs",
|
226 |
-
"ca",
|
227 |
-
"cs",
|
228 |
-
"cy",
|
229 |
-
"da",
|
230 |
-
"de",
|
231 |
-
"el",
|
232 |
-
"en",
|
233 |
-
"es",
|
234 |
-
"et",
|
235 |
-
"eu",
|
236 |
-
"fa",
|
237 |
-
"fi",
|
238 |
-
"fo",
|
239 |
-
"fr",
|
240 |
-
"gl",
|
241 |
-
"gu",
|
242 |
-
"ha",
|
243 |
-
"haw",
|
244 |
-
"he",
|
245 |
-
"hi",
|
246 |
-
"hr",
|
247 |
-
"ht",
|
248 |
-
"hu",
|
249 |
-
"hy",
|
250 |
-
"id",
|
251 |
-
"is",
|
252 |
-
"it",
|
253 |
-
"ja",
|
254 |
-
"jw",
|
255 |
-
"ka",
|
256 |
-
"kk",
|
257 |
-
"km",
|
258 |
-
"kn",
|
259 |
-
"ko",
|
260 |
-
"la",
|
261 |
-
"lb",
|
262 |
-
"ln",
|
263 |
-
"lo",
|
264 |
-
"lt",
|
265 |
-
"lv",
|
266 |
-
"mg",
|
267 |
-
"mi",
|
268 |
-
"mk",
|
269 |
-
"ml",
|
270 |
-
"mn",
|
271 |
-
"mr",
|
272 |
-
"ms",
|
273 |
-
"mt",
|
274 |
-
"my",
|
275 |
-
"ne",
|
276 |
-
"nl",
|
277 |
-
"nn",
|
278 |
-
"no",
|
279 |
-
"oc",
|
280 |
-
"pa",
|
281 |
-
"pl",
|
282 |
-
"ps",
|
283 |
-
"pt",
|
284 |
-
"ro",
|
285 |
-
"ru",
|
286 |
-
"sa",
|
287 |
-
"sd",
|
288 |
-
"si",
|
289 |
-
"sk",
|
290 |
-
"sl",
|
291 |
-
"sn",
|
292 |
-
"so",
|
293 |
-
"sq",
|
294 |
-
"sr",
|
295 |
-
"su",
|
296 |
-
"sv",
|
297 |
-
"sw",
|
298 |
-
"ta",
|
299 |
-
"te",
|
300 |
-
"tg",
|
301 |
-
"th",
|
302 |
-
"tk",
|
303 |
-
"tl",
|
304 |
-
"tr",
|
305 |
-
"tt",
|
306 |
-
"uk",
|
307 |
-
"ur",
|
308 |
-
"uz",
|
309 |
-
"vi",
|
310 |
-
"yi",
|
311 |
-
"yo",
|
312 |
-
"zh",
|
313 |
-
"yue",
|
314 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/transcribe.py
DELETED
@@ -1,2170 +0,0 @@
|
|
1 |
-
import itertools
|
2 |
-
import json
|
3 |
-
import logging
|
4 |
-
import os
|
5 |
-
import random
|
6 |
-
import zlib
|
7 |
-
|
8 |
-
from collections import Counter, defaultdict
|
9 |
-
from inspect import signature
|
10 |
-
from typing import BinaryIO, Iterable, List, NamedTuple, Optional, Tuple, Union
|
11 |
-
|
12 |
-
import ctranslate2
|
13 |
-
import numpy as np
|
14 |
-
import tokenizers
|
15 |
-
import torch
|
16 |
-
|
17 |
-
from pyannote.audio import Model
|
18 |
-
from tqdm import tqdm
|
19 |
-
|
20 |
-
from faster_whisper.audio import decode_audio, pad_or_trim
|
21 |
-
from faster_whisper.feature_extractor import FeatureExtractor
|
22 |
-
from faster_whisper.tokenizer import _LANGUAGE_CODES, Tokenizer
|
23 |
-
from faster_whisper.utils import (
|
24 |
-
download_model,
|
25 |
-
format_timestamp,
|
26 |
-
get_assets_path,
|
27 |
-
get_end,
|
28 |
-
get_logger,
|
29 |
-
)
|
30 |
-
from faster_whisper.vad import (
|
31 |
-
SpeechTimestampsMap,
|
32 |
-
VadOptions,
|
33 |
-
VoiceActivitySegmentation,
|
34 |
-
collect_chunks,
|
35 |
-
get_speech_timestamps,
|
36 |
-
merge_chunks,
|
37 |
-
)
|
38 |
-
|
39 |
-
|
40 |
-
class Word(NamedTuple):
|
41 |
-
start: float
|
42 |
-
end: float
|
43 |
-
word: str
|
44 |
-
probability: float
|
45 |
-
|
46 |
-
|
47 |
-
class Segment(NamedTuple):
|
48 |
-
id: int
|
49 |
-
seek: int
|
50 |
-
start: float
|
51 |
-
end: float
|
52 |
-
text: str
|
53 |
-
tokens: List[int]
|
54 |
-
avg_logprob: float
|
55 |
-
compression_ratio: float
|
56 |
-
no_speech_prob: float
|
57 |
-
words: Optional[List[Word]]
|
58 |
-
temperature: Optional[float] = 1.0
|
59 |
-
|
60 |
-
|
61 |
-
# Added additional parameters for multilingual videos and fixes below
|
62 |
-
class TranscriptionOptions(NamedTuple):
|
63 |
-
beam_size: int
|
64 |
-
best_of: int
|
65 |
-
patience: float
|
66 |
-
length_penalty: float
|
67 |
-
repetition_penalty: float
|
68 |
-
no_repeat_ngram_size: int
|
69 |
-
log_prob_threshold: Optional[float]
|
70 |
-
log_prob_low_threshold: Optional[float]
|
71 |
-
no_speech_threshold: Optional[float]
|
72 |
-
compression_ratio_threshold: Optional[float]
|
73 |
-
condition_on_previous_text: bool
|
74 |
-
prompt_reset_on_temperature: float
|
75 |
-
temperatures: List[float]
|
76 |
-
initial_prompt: Optional[Union[str, Iterable[int]]]
|
77 |
-
prefix: Optional[str]
|
78 |
-
suppress_blank: bool
|
79 |
-
suppress_tokens: Optional[List[int]]
|
80 |
-
without_timestamps: bool
|
81 |
-
max_initial_timestamp: float
|
82 |
-
word_timestamps: bool
|
83 |
-
prepend_punctuations: str
|
84 |
-
append_punctuations: str
|
85 |
-
multilingual: bool
|
86 |
-
output_language: Optional[str]
|
87 |
-
max_new_tokens: Optional[int]
|
88 |
-
clip_timestamps: Union[str, List[float]]
|
89 |
-
hallucination_silence_threshold: Optional[float]
|
90 |
-
hotwords: Optional[str]
|
91 |
-
|
92 |
-
|
93 |
-
class TranscriptionInfo(NamedTuple):
|
94 |
-
language: str
|
95 |
-
language_probability: float
|
96 |
-
duration: float
|
97 |
-
duration_after_vad: float
|
98 |
-
all_language_probs: Optional[List[Tuple[str, float]]]
|
99 |
-
transcription_options: TranscriptionOptions
|
100 |
-
vad_options: VadOptions
|
101 |
-
|
102 |
-
|
103 |
-
# The code below is originally from HF pipeline and is used in whisper-x
|
104 |
-
# (https://github.com/m-bain/whisperX) and adapted for faster_whisper
|
105 |
-
|
106 |
-
|
107 |
-
class BatchedInferencePipeline:
|
108 |
-
"""
|
109 |
-
Huggingface Pipeline wrapper for WhisperModel.
|
110 |
-
Copyright (c) 2022, Max Bain
|
111 |
-
All rights reserved.
|
112 |
-
Modified by Mobius Labs GmbH
|
113 |
-
"""
|
114 |
-
|
115 |
-
def __init__(
|
116 |
-
self,
|
117 |
-
model,
|
118 |
-
use_vad_model: bool = True,
|
119 |
-
options: Optional[NamedTuple] = None,
|
120 |
-
tokenizer=None,
|
121 |
-
chunk_length: int = 30,
|
122 |
-
vad_device: Union[int, str, "torch.device"] = "auto",
|
123 |
-
vad_onset: float = 0.500,
|
124 |
-
vad_offset: float = 0.363,
|
125 |
-
language: Optional[str] = None,
|
126 |
-
):
|
127 |
-
self.model: WhisperModel = model
|
128 |
-
self.tokenizer = tokenizer
|
129 |
-
self.options = options
|
130 |
-
self.preset_language = language
|
131 |
-
self.use_vad_model = use_vad_model
|
132 |
-
self.vad_onset = vad_onset
|
133 |
-
self.vad_offset = vad_offset
|
134 |
-
self.vad_model_path = os.path.join(get_assets_path(), "pyannote_vad_model.bin")
|
135 |
-
if self.use_vad_model:
|
136 |
-
self.vad_device = self.get_device(vad_device)
|
137 |
-
self.vad_model = self.load_vad_model(
|
138 |
-
vad_onset=self.vad_onset, vad_offset=self.vad_offset
|
139 |
-
)
|
140 |
-
else:
|
141 |
-
self.vad_model = None
|
142 |
-
self.chunk_length = chunk_length # VAD merging size
|
143 |
-
self.last_speech_timestamp = 0.0
|
144 |
-
|
145 |
-
def get_device(self, device: Union[int, str, "torch.device"]):
|
146 |
-
"""
|
147 |
-
Converts the input device into a torch.device object.
|
148 |
-
|
149 |
-
The input can be an integer, a string, or a `torch.device` object.
|
150 |
-
|
151 |
-
The function handles a special case where the input device is "auto".
|
152 |
-
When "auto" is specified, the device will default to the
|
153 |
-
device of the model (self.model.device). If the model's device is also "auto",
|
154 |
-
it selects "cuda" if a CUDA-capable device is available; otherwise, it selects "cpu".
|
155 |
-
"""
|
156 |
-
if isinstance(device, torch.device):
|
157 |
-
return device
|
158 |
-
elif isinstance(device, str):
|
159 |
-
if device == "auto" and self.model.device == "auto":
|
160 |
-
device = "cuda" if torch.cuda.is_available() else "cpu"
|
161 |
-
elif device == "auto":
|
162 |
-
device = self.model.device
|
163 |
-
return torch.device(device)
|
164 |
-
elif device < 0:
|
165 |
-
return torch.device("cpu")
|
166 |
-
else:
|
167 |
-
return torch.device(f"cuda:{device}")
|
168 |
-
|
169 |
-
def forward(self, features, segments_metadata, **forward_params):
|
170 |
-
encoder_output, outputs = self.model.generate_segment_batched(
|
171 |
-
features, self.tokenizer, forward_params
|
172 |
-
)
|
173 |
-
|
174 |
-
segmented_outputs = []
|
175 |
-
segment_sizes = []
|
176 |
-
for segment_metadata, output in zip(segments_metadata, outputs):
|
177 |
-
duration = segment_metadata["end_time"] - segment_metadata["start_time"]
|
178 |
-
segment_size = int(duration * self.model.frames_per_second)
|
179 |
-
segment_sizes.append(segment_size)
|
180 |
-
(
|
181 |
-
subsegments,
|
182 |
-
seek,
|
183 |
-
single_timestamp_ending,
|
184 |
-
) = self.model._split_segments_by_timestamps(
|
185 |
-
tokenizer=self.tokenizer,
|
186 |
-
tokens=output["tokens"],
|
187 |
-
time_offset=segment_metadata["start_time"],
|
188 |
-
segment_size=segment_size,
|
189 |
-
segment_duration=duration,
|
190 |
-
seek=0,
|
191 |
-
)
|
192 |
-
segmented_outputs.append(
|
193 |
-
[
|
194 |
-
dict(
|
195 |
-
text=self.tokenizer.decode(subsegment["tokens"]),
|
196 |
-
avg_logprob=output["avg_logprob"],
|
197 |
-
no_speech_prob=output["no_speech_prob"],
|
198 |
-
tokens=subsegment["tokens"],
|
199 |
-
start=subsegment["start"],
|
200 |
-
end=subsegment["end"],
|
201 |
-
compression_ratio=get_compression_ratio(
|
202 |
-
self.tokenizer.decode(subsegment["tokens"])
|
203 |
-
),
|
204 |
-
)
|
205 |
-
for subsegment in subsegments
|
206 |
-
]
|
207 |
-
)
|
208 |
-
if forward_params["word_timestamps"]:
|
209 |
-
self.last_speech_timestamp = self.model.add_word_timestamps(
|
210 |
-
segmented_outputs,
|
211 |
-
self.tokenizer,
|
212 |
-
encoder_output,
|
213 |
-
segment_sizes,
|
214 |
-
forward_params["prepend_punctuations"],
|
215 |
-
forward_params["append_punctuations"],
|
216 |
-
self.last_speech_timestamp,
|
217 |
-
)
|
218 |
-
|
219 |
-
return segmented_outputs
|
220 |
-
|
221 |
-
def get_language_and_tokenizer(
|
222 |
-
self, audio, task: Optional[str] = None, language: Optional[str] = None
|
223 |
-
):
|
224 |
-
all_language_probs = None
|
225 |
-
language_probability = 1.0
|
226 |
-
|
227 |
-
if self.tokenizer is None:
|
228 |
-
if not language:
|
229 |
-
(
|
230 |
-
language,
|
231 |
-
language_probability,
|
232 |
-
all_language_probs,
|
233 |
-
) = self.model.detect_language(audio)
|
234 |
-
task = task or "transcribe"
|
235 |
-
self.tokenizer = Tokenizer(
|
236 |
-
self.model.hf_tokenizer,
|
237 |
-
self.model.model.is_multilingual,
|
238 |
-
task=task,
|
239 |
-
language=language,
|
240 |
-
)
|
241 |
-
else:
|
242 |
-
if task is not None:
|
243 |
-
self.tokenizer.task = self.tokenizer.tokenizer.token_to_id(
|
244 |
-
f"<|{task}|>"
|
245 |
-
)
|
246 |
-
|
247 |
-
if language is not None:
|
248 |
-
self.tokenizer.language = self.tokenizer.tokenizer.token_to_id(
|
249 |
-
f"<|{language}|>"
|
250 |
-
)
|
251 |
-
self.tokenizer.language_code = language
|
252 |
-
|
253 |
-
return language, language_probability, task, all_language_probs
|
254 |
-
|
255 |
-
@staticmethod
|
256 |
-
def audio_split(audio, segments, sampling_rate):
|
257 |
-
"""Returns splitted audio chunks as iterator"""
|
258 |
-
audio_segments = []
|
259 |
-
segments_metadata = []
|
260 |
-
for seg in segments:
|
261 |
-
f1 = int(seg["start"] * sampling_rate)
|
262 |
-
f2 = int(seg["end"] * sampling_rate)
|
263 |
-
seg_metadata = {
|
264 |
-
"start_time": seg["start"],
|
265 |
-
"end_time": seg["end"],
|
266 |
-
"stitched_seg": seg["segments"],
|
267 |
-
}
|
268 |
-
audio_segments.append(audio[f1:f2])
|
269 |
-
segments_metadata.append(seg_metadata)
|
270 |
-
return audio_segments, segments_metadata
|
271 |
-
|
272 |
-
def load_vad_model(self, vad_onset=0.500, vad_offset=0.363):
|
273 |
-
vad_model = Model.from_pretrained(self.vad_model_path)
|
274 |
-
hyperparameters = {
|
275 |
-
"onset": vad_onset,
|
276 |
-
"offset": vad_offset,
|
277 |
-
"min_duration_on": 0.1,
|
278 |
-
"min_duration_off": 0.1,
|
279 |
-
}
|
280 |
-
|
281 |
-
vad_pipeline = VoiceActivitySegmentation(
|
282 |
-
segmentation=vad_model, device=torch.device(self.vad_device)
|
283 |
-
)
|
284 |
-
vad_pipeline.instantiate(hyperparameters)
|
285 |
-
return vad_pipeline
|
286 |
-
|
287 |
-
def transcribe(
|
288 |
-
self,
|
289 |
-
audio: Union[str, torch.Tensor, np.ndarray],
|
290 |
-
vad_segments: Optional[List[dict]] = None,
|
291 |
-
batch_size: int = 16,
|
292 |
-
language: Optional[str] = None,
|
293 |
-
task: str = None,
|
294 |
-
log_progress: bool = False,
|
295 |
-
beam_size: int = 5,
|
296 |
-
best_of: int = 5,
|
297 |
-
patience: float = 1,
|
298 |
-
length_penalty: float = 1,
|
299 |
-
repetition_penalty: float = 1,
|
300 |
-
no_repeat_ngram_size: int = 0,
|
301 |
-
temperature: Union[float, List[float], Tuple[float, ...]] = [
|
302 |
-
0.0,
|
303 |
-
0.2,
|
304 |
-
0.4,
|
305 |
-
0.6,
|
306 |
-
0.8,
|
307 |
-
1.0,
|
308 |
-
],
|
309 |
-
compression_ratio_threshold: Optional[float] = 2.4,
|
310 |
-
log_prob_threshold: Optional[float] = -1.0,
|
311 |
-
log_prob_low_threshold: Optional[float] = None,
|
312 |
-
no_speech_threshold: Optional[float] = 0.6,
|
313 |
-
initial_prompt: Optional[Union[str, Iterable[int]]] = None,
|
314 |
-
prefix: Optional[str] = None,
|
315 |
-
suppress_blank: bool = True,
|
316 |
-
suppress_tokens: Optional[List[int]] = [-1],
|
317 |
-
prepend_punctuations: str = "\"'“¿([{-",
|
318 |
-
append_punctuations: str = "\"'.。,,!!??::”)]}、",
|
319 |
-
max_new_tokens: Optional[int] = None,
|
320 |
-
hotwords: Optional[str] = None,
|
321 |
-
word_timestamps: bool = False,
|
322 |
-
without_timestamps: bool = True,
|
323 |
-
) -> Tuple[Iterable[Segment], TranscriptionInfo]:
|
324 |
-
"""transcribe audio in chunks in batched fashion and return with language info.
|
325 |
-
|
326 |
-
Arguments:
|
327 |
-
audio: audio file as numpy array/path for batched transcription.
|
328 |
-
vad_segments: Optionally provide list of dictionaries each containing "start", "end",
|
329 |
-
and "segments" keys.
|
330 |
-
"start" and "end" keys specify the start and end of the voiced region within
|
331 |
-
30 sec boundary. An additional key "segments" contains all the start
|
332 |
-
and end of voiced regions within that 30sec boundary as a list of tuples.
|
333 |
-
If no vad_segments specified, it uses internal vad model automatically segment them.
|
334 |
-
batch_size: the maximum number of parallel requests to model for decoding.
|
335 |
-
language: The language spoken in the audio.
|
336 |
-
task: either "transcribe" or "translate".
|
337 |
-
log_progress: whether to show progress bar or not.
|
338 |
-
beam_size: Beam size to use for decoding.
|
339 |
-
best_of: Number of candidates when sampling with non-zero temperature.
|
340 |
-
patience: Beam search patience factor.
|
341 |
-
length_penalty: Exponential length penalty constant.
|
342 |
-
repetition_penalty: Penalty applied to the score of previously generated tokens
|
343 |
-
(set > 1 to penalize).
|
344 |
-
no_repeat_ngram_size: Prevent repetitions of ngrams with this size (set 0 to disable).
|
345 |
-
temperature: Temperature for sampling. It can be a tuple of temperatures,
|
346 |
-
which will be successively used upon failures according to either
|
347 |
-
`compression_ratio_threshold` or `log_prob_threshold`.
|
348 |
-
compression_ratio_threshold: If the gzip compression ratio is above this value,
|
349 |
-
treat as failed.
|
350 |
-
log_prob_threshold: If the average log probability over sampled tokens is
|
351 |
-
below this value, treat as failed.
|
352 |
-
log_prob_low_threshold: This parameter alone is sufficient to skip an output text,
|
353 |
-
whereas log_prob_threshold also looks for appropriate no_speech_threshold value.
|
354 |
-
This value should be less than log_prob_threshold.
|
355 |
-
no_speech_threshold: If the no_speech probability is higher than this value AND
|
356 |
-
the average log probability over sampled tokens is below `log_prob_threshold`,
|
357 |
-
consider the segment as silent.
|
358 |
-
initial_prompt: Optional text string or iterable of token ids to provide as a
|
359 |
-
prompt for the first window.
|
360 |
-
prefix: Optional text to provide as a prefix for the first window.
|
361 |
-
suppress_blank: Suppress blank outputs at the beginning of the sampling.
|
362 |
-
suppress_tokens: List of token IDs to suppress. -1 will suppress a default set
|
363 |
-
of symbols as defined in `tokenizer.non_speech_tokens()`.
|
364 |
-
prepend_punctuations: If word_timestamps is True, merge these punctuation symbols
|
365 |
-
with the next word
|
366 |
-
append_punctuations: If word_timestamps is True, merge these punctuation symbols
|
367 |
-
with the previous word
|
368 |
-
max_new_tokens: Maximum number of new tokens to generate per-chunk. If not set,
|
369 |
-
the maximum will be set by the default max_length.
|
370 |
-
hotwords:
|
371 |
-
Hotwords/hint phrases to the model. Has no effect if prefix is not None.
|
372 |
-
word_timestamps: Extract word-level timestamps using the cross-attention pattern
|
373 |
-
and dynamic time warping, and include the timestamps for each word in each segment.
|
374 |
-
Set as False.
|
375 |
-
without_timestamps: Only sample text tokens.
|
376 |
-
|
377 |
-
Static params: (Fixed for batched version)
|
378 |
-
max_initial_timestamp: The initial timestamp cannot be later than this, set at 0.0.
|
379 |
-
multilingual: If True, perform transcription on multilingual videos. Set as False.
|
380 |
-
output_language: Valid only if multilingual is set to True.
|
381 |
-
Specifies the string representing the output language. One of
|
382 |
-
'en' (English) or 'hybrid' (code-switched transcription). set as None.
|
383 |
-
condition_on_previous_text: If True, the previous output of the model is provided
|
384 |
-
as a prompt for the next window; disabling may make the text inconsistent across
|
385 |
-
windows, but the model becomes less prone to getting stuck in a failure loop,
|
386 |
-
such as repetition looping or timestamps going out of sync. Set as False
|
387 |
-
prompt_reset_on_temperature: Resets prompt if temperature is above this value.
|
388 |
-
Arg has effect only if condition_on_previous_text is True. Set at 0.5
|
389 |
-
#TODO: support "hallucination_silence_threshold" when "word_timestamps=True"
|
390 |
-
hallucination_silence_threshold: Optional[float]
|
391 |
-
When word_timestamps is True, skip silent periods longer than this threshold
|
392 |
-
(in seconds) when a possible hallucination is detected. set as None.
|
393 |
-
clip_timestamps:
|
394 |
-
Comma-separated list start,end,start,end,... timestamps (in seconds) of clips to
|
395 |
-
process. The last end timestamp defaults to the end of the file. Set as "0".
|
396 |
-
|
397 |
-
unused:
|
398 |
-
language_detection_threshold: If the maximum probability of the language tokens is
|
399 |
-
higher than this value, the language is detected.
|
400 |
-
language_detection_segments: Number of segments to consider for the language detection.
|
401 |
-
vad_filter: Enable the voice activity detection (VAD) to filter out parts of the audio
|
402 |
-
without speech. This step is using the Silero VAD model
|
403 |
-
https://github.com/snakers4/silero-vad.
|
404 |
-
vad_parameters: Dictionary of Silero VAD parameters or VadOptions class (see available
|
405 |
-
parameters and default values in the class `VadOptions`).
|
406 |
-
chunk_length: The length of audio segments. If it is not None, it will overwrite the
|
407 |
-
default chunk_length of the FeatureExtractor.
|
408 |
-
|
409 |
-
|
410 |
-
Returns:
|
411 |
-
A tuple with:
|
412 |
-
|
413 |
-
- a generator over transcribed batched segments.
|
414 |
-
- an instance of TranscriptionInfo.
|
415 |
-
"""
|
416 |
-
|
417 |
-
sampling_rate = self.model.feature_extractor.sampling_rate
|
418 |
-
|
419 |
-
if isinstance(audio, np.ndarray):
|
420 |
-
audio = torch.from_numpy(audio)
|
421 |
-
elif not isinstance(audio, torch.Tensor):
|
422 |
-
audio = decode_audio(audio, sampling_rate=sampling_rate)
|
423 |
-
duration = audio.shape[0] / sampling_rate
|
424 |
-
|
425 |
-
# if no segment split is provided, use vad_model and generate segments
|
426 |
-
if not vad_segments:
|
427 |
-
# run the audio if it is less than 30 sec even without vad_segments
|
428 |
-
if self.use_vad_model:
|
429 |
-
vad_segments = self.vad_model(
|
430 |
-
{
|
431 |
-
"waveform": audio.unsqueeze(0),
|
432 |
-
"sample_rate": 16000,
|
433 |
-
}
|
434 |
-
)
|
435 |
-
vad_segments = merge_chunks(
|
436 |
-
vad_segments,
|
437 |
-
self.chunk_length,
|
438 |
-
onset=self.vad_onset,
|
439 |
-
offset=self.vad_offset,
|
440 |
-
)
|
441 |
-
elif duration < self.chunk_length:
|
442 |
-
vad_segments = [
|
443 |
-
{"start": 0.0, "end": duration, "segments": [(0.0, duration)]}
|
444 |
-
]
|
445 |
-
else:
|
446 |
-
raise RuntimeError(
|
447 |
-
"No vad segments found. Set 'use_vad_model' to True while loading the model"
|
448 |
-
)
|
449 |
-
if self.model.model.is_multilingual:
|
450 |
-
language = language or self.preset_language
|
451 |
-
elif language != "en":
|
452 |
-
if language is not None:
|
453 |
-
self.model.logger.warning(
|
454 |
-
f"English-only model is used, but {language} language is"
|
455 |
-
"chosen, setting language to 'en'."
|
456 |
-
)
|
457 |
-
language = "en"
|
458 |
-
|
459 |
-
(
|
460 |
-
language,
|
461 |
-
language_probability,
|
462 |
-
task,
|
463 |
-
all_language_probs,
|
464 |
-
) = self.get_language_and_tokenizer(audio, task, language)
|
465 |
-
|
466 |
-
duration_after_vad = sum(
|
467 |
-
segment["end"] - segment["start"] for segment in vad_segments
|
468 |
-
)
|
469 |
-
|
470 |
-
# batched options: see the difference with default options in WhisperModel
|
471 |
-
batched_options = TranscriptionOptions(
|
472 |
-
beam_size=beam_size,
|
473 |
-
best_of=best_of,
|
474 |
-
patience=patience,
|
475 |
-
length_penalty=length_penalty,
|
476 |
-
repetition_penalty=repetition_penalty,
|
477 |
-
no_repeat_ngram_size=no_repeat_ngram_size,
|
478 |
-
log_prob_threshold=log_prob_threshold,
|
479 |
-
log_prob_low_threshold=log_prob_low_threshold,
|
480 |
-
no_speech_threshold=no_speech_threshold,
|
481 |
-
compression_ratio_threshold=compression_ratio_threshold,
|
482 |
-
temperatures=(
|
483 |
-
temperature if isinstance(temperature, (list, tuple)) else [temperature]
|
484 |
-
),
|
485 |
-
initial_prompt=initial_prompt,
|
486 |
-
prefix=prefix,
|
487 |
-
suppress_blank=suppress_blank,
|
488 |
-
suppress_tokens=get_suppressed_tokens(self.tokenizer, suppress_tokens),
|
489 |
-
prepend_punctuations=prepend_punctuations,
|
490 |
-
append_punctuations=append_punctuations,
|
491 |
-
max_new_tokens=max_new_tokens,
|
492 |
-
hotwords=hotwords,
|
493 |
-
word_timestamps=word_timestamps,
|
494 |
-
hallucination_silence_threshold=None,
|
495 |
-
condition_on_previous_text=False,
|
496 |
-
clip_timestamps="0",
|
497 |
-
prompt_reset_on_temperature=0.5,
|
498 |
-
multilingual=False,
|
499 |
-
output_language=None,
|
500 |
-
without_timestamps=without_timestamps,
|
501 |
-
max_initial_timestamp=0.0,
|
502 |
-
)
|
503 |
-
|
504 |
-
info = TranscriptionInfo(
|
505 |
-
language=language,
|
506 |
-
language_probability=language_probability,
|
507 |
-
duration=duration,
|
508 |
-
duration_after_vad=duration_after_vad,
|
509 |
-
transcription_options=batched_options,
|
510 |
-
vad_options=None,
|
511 |
-
all_language_probs=all_language_probs,
|
512 |
-
)
|
513 |
-
|
514 |
-
audio_segments, segments_metadata = self.audio_split(
|
515 |
-
audio, vad_segments, sampling_rate
|
516 |
-
)
|
517 |
-
to_cpu = (
|
518 |
-
self.model.model.device == "cuda" and len(self.model.model.device_index) > 1
|
519 |
-
)
|
520 |
-
audio_segments = torch.nested.nested_tensor(audio_segments).to_padded_tensor(
|
521 |
-
padding=0
|
522 |
-
)
|
523 |
-
features = torch.stack(
|
524 |
-
[
|
525 |
-
self.model.feature_extractor(audio_segment, to_cpu=to_cpu)[
|
526 |
-
..., : self.model.feature_extractor.nb_max_frames
|
527 |
-
]
|
528 |
-
for audio_segment in audio_segments
|
529 |
-
]
|
530 |
-
)
|
531 |
-
|
532 |
-
segments = self._batched_segments_generator(
|
533 |
-
features,
|
534 |
-
segments_metadata,
|
535 |
-
batch_size,
|
536 |
-
batched_options,
|
537 |
-
log_progress,
|
538 |
-
)
|
539 |
-
|
540 |
-
return segments, info
|
541 |
-
|
542 |
-
def _batched_segments_generator(
|
543 |
-
self, features, segments_metadata, batch_size, options, log_progress
|
544 |
-
):
|
545 |
-
pbar = tqdm(total=len(features), disable=not log_progress, position=0)
|
546 |
-
seg_idx = 0
|
547 |
-
for i in range(0, len(features), batch_size):
|
548 |
-
results = self.forward(
|
549 |
-
features[i : i + batch_size],
|
550 |
-
segments_metadata[i : i + batch_size],
|
551 |
-
**options._asdict(),
|
552 |
-
)
|
553 |
-
|
554 |
-
for result in results:
|
555 |
-
for segment in result:
|
556 |
-
seg_idx += 1
|
557 |
-
yield Segment(
|
558 |
-
seek=int(result[-1]["end"] * self.model.frames_per_second),
|
559 |
-
id=seg_idx,
|
560 |
-
text=segment["text"],
|
561 |
-
start=round(segment["start"], 3),
|
562 |
-
end=round(segment["end"], 3),
|
563 |
-
words=(
|
564 |
-
None
|
565 |
-
if not options.word_timestamps
|
566 |
-
else [Word(**word) for word in segment["words"]]
|
567 |
-
),
|
568 |
-
tokens=segment["tokens"],
|
569 |
-
avg_logprob=segment["avg_logprob"],
|
570 |
-
no_speech_prob=segment["no_speech_prob"],
|
571 |
-
compression_ratio=segment["compression_ratio"],
|
572 |
-
)
|
573 |
-
|
574 |
-
pbar.update(1)
|
575 |
-
|
576 |
-
pbar.close()
|
577 |
-
# revert the tokenizer if multilingual inference is enabled
|
578 |
-
if self.preset_language is None:
|
579 |
-
self.tokenizer = None
|
580 |
-
self.last_speech_timestamp = 0.0
|
581 |
-
|
582 |
-
|
583 |
-
class WhisperModel:
|
584 |
-
def __init__(
|
585 |
-
self,
|
586 |
-
model_size_or_path: str,
|
587 |
-
device: str = "auto",
|
588 |
-
device_index: Union[int, List[int]] = 0,
|
589 |
-
compute_type: str = "default",
|
590 |
-
cpu_threads: int = 16,
|
591 |
-
num_workers: int = 1,
|
592 |
-
download_root: Optional[str] = None,
|
593 |
-
local_files_only: bool = False,
|
594 |
-
files: dict = None,
|
595 |
-
**model_kwargs,
|
596 |
-
):
|
597 |
-
"""Initializes the Whisper model.
|
598 |
-
|
599 |
-
Args:
|
600 |
-
model_size_or_path: Size of the model to use (tiny, tiny.en, base, base.en,
|
601 |
-
small, small.en, distil-small.en, medium, medium.en, distil-medium.en, large-v1,
|
602 |
-
large-v2, large-v3, large, distil-large-v2 or distil-large-v3), a path to a
|
603 |
-
converted model directory, or a CTranslate2-converted Whisper model ID from the HF Hub.
|
604 |
-
When a size or a model ID is configured, the converted model is downloaded
|
605 |
-
from the Hugging Face Hub.
|
606 |
-
device: Device to use for computation ("cpu", "cuda", "auto").
|
607 |
-
device_index: Device ID to use.
|
608 |
-
The model can also be loaded on multiple GPUs by passing a list of IDs
|
609 |
-
(e.g. [0, 1, 2, 3]). In that case, multiple transcriptions can run in parallel
|
610 |
-
when transcribe() is called from multiple Python threads (see also num_workers).
|
611 |
-
compute_type: Type to use for computation.
|
612 |
-
See https://opennmt.net/CTranslate2/quantization.html.
|
613 |
-
cpu_threads: Number of threads to use when running on CPU (4 by default).
|
614 |
-
A non zero value overrides the OMP_NUM_THREADS environment variable.
|
615 |
-
num_workers: When transcribe() is called from multiple Python threads,
|
616 |
-
having multiple workers enables true parallelism when running the model
|
617 |
-
(concurrent calls to self.model.generate() will run in parallel).
|
618 |
-
This can improve the global throughput at the cost of increased memory usage.
|
619 |
-
download_root: Directory where the models should be saved. If not set, the models
|
620 |
-
are saved in the standard Hugging Face cache directory.
|
621 |
-
local_files_only: If True, avoid downloading the file and return the path to the
|
622 |
-
local cached file if it exists.
|
623 |
-
files: Load model files from the memory. This argument is a dictionary mapping file names
|
624 |
-
to file contents as file-like or bytes objects. If this is set, model_path acts as an
|
625 |
-
identifier for this model.
|
626 |
-
"""
|
627 |
-
self.logger = get_logger()
|
628 |
-
|
629 |
-
tokenizer_bytes, preprocessor_bytes = None, None
|
630 |
-
if files:
|
631 |
-
model_path = model_size_or_path
|
632 |
-
tokenizer_bytes = files.pop("tokenizer.json", None)
|
633 |
-
preprocessor_bytes = files.pop("preprocessor_config.json", None)
|
634 |
-
elif os.path.isdir(model_size_or_path):
|
635 |
-
model_path = model_size_or_path
|
636 |
-
else:
|
637 |
-
model_path = download_model(
|
638 |
-
model_size_or_path,
|
639 |
-
local_files_only=local_files_only,
|
640 |
-
cache_dir=download_root,
|
641 |
-
)
|
642 |
-
self.device = device
|
643 |
-
# set the random seed to make sure consistency across runs
|
644 |
-
ctranslate2.set_random_seed(42)
|
645 |
-
self.model = ctranslate2.models.Whisper(
|
646 |
-
model_path,
|
647 |
-
device=self.device,
|
648 |
-
device_index=device_index,
|
649 |
-
compute_type=compute_type,
|
650 |
-
intra_threads=cpu_threads,
|
651 |
-
inter_threads=num_workers,
|
652 |
-
files=files,
|
653 |
-
**model_kwargs,
|
654 |
-
)
|
655 |
-
|
656 |
-
tokenizer_file = os.path.join(model_path, "tokenizer.json")
|
657 |
-
if tokenizer_bytes:
|
658 |
-
self.hf_tokenizer = tokenizers.Tokenizer.from_buffer(tokenizer_bytes)
|
659 |
-
elif os.path.isfile(tokenizer_file):
|
660 |
-
self.hf_tokenizer = tokenizers.Tokenizer.from_file(tokenizer_file)
|
661 |
-
else:
|
662 |
-
self.hf_tokenizer = tokenizers.Tokenizer.from_pretrained(
|
663 |
-
"openai/whisper-tiny" + ("" if self.model.is_multilingual else ".en")
|
664 |
-
)
|
665 |
-
self.feat_kwargs = self._get_feature_kwargs(model_path, preprocessor_bytes)
|
666 |
-
self.feature_extractor = FeatureExtractor(
|
667 |
-
**self.feat_kwargs, device=self.device
|
668 |
-
)
|
669 |
-
self.input_stride = 2
|
670 |
-
self.num_samples_per_token = (
|
671 |
-
self.feature_extractor.hop_length * self.input_stride
|
672 |
-
)
|
673 |
-
self.frames_per_second = (
|
674 |
-
self.feature_extractor.sampling_rate // self.feature_extractor.hop_length
|
675 |
-
)
|
676 |
-
self.tokens_per_second = (
|
677 |
-
self.feature_extractor.sampling_rate // self.num_samples_per_token
|
678 |
-
)
|
679 |
-
self.time_precision = 0.02
|
680 |
-
self.max_length = 448
|
681 |
-
|
682 |
-
@property
|
683 |
-
def supported_languages(self) -> List[str]:
|
684 |
-
"""The languages supported by the model."""
|
685 |
-
return list(_LANGUAGE_CODES) if self.model.is_multilingual else ["en"]
|
686 |
-
|
687 |
-
def _get_feature_kwargs(self, model_path, preprocessor_bytes=None) -> dict:
|
688 |
-
config = {}
|
689 |
-
try:
|
690 |
-
config_path = os.path.join(model_path, "preprocessor_config.json")
|
691 |
-
if preprocessor_bytes:
|
692 |
-
config = json.loads(preprocessor_bytes)
|
693 |
-
elif os.path.isfile(config_path):
|
694 |
-
with open(config_path, "r", encoding="utf-8") as file:
|
695 |
-
config = json.load(file)
|
696 |
-
else:
|
697 |
-
return config
|
698 |
-
valid_keys = signature(FeatureExtractor.__init__).parameters.keys()
|
699 |
-
return {k: v for k, v in config.items() if k in valid_keys}
|
700 |
-
except json.JSONDecodeError as e:
|
701 |
-
self.logger.warning("Could not load preprocessor config: %s", e)
|
702 |
-
|
703 |
-
return config
|
704 |
-
|
705 |
-
def transcribe(
|
706 |
-
self,
|
707 |
-
audio: Union[str, BinaryIO, torch.Tensor, np.ndarray],
|
708 |
-
language: Optional[str] = None,
|
709 |
-
task: str = "transcribe",
|
710 |
-
beam_size: int = 5,
|
711 |
-
best_of: int = 5,
|
712 |
-
patience: float = 1,
|
713 |
-
length_penalty: float = 1,
|
714 |
-
repetition_penalty: float = 1,
|
715 |
-
no_repeat_ngram_size: int = 0,
|
716 |
-
temperature: Union[float, List[float], Tuple[float, ...]] = [
|
717 |
-
0.0,
|
718 |
-
0.2,
|
719 |
-
0.4,
|
720 |
-
0.6,
|
721 |
-
0.8,
|
722 |
-
1.0,
|
723 |
-
],
|
724 |
-
compression_ratio_threshold: Optional[float] = 2.4,
|
725 |
-
log_prob_threshold: Optional[float] = -1.0,
|
726 |
-
log_prob_low_threshold: Optional[float] = None,
|
727 |
-
no_speech_threshold: Optional[float] = 0.6,
|
728 |
-
condition_on_previous_text: bool = True,
|
729 |
-
prompt_reset_on_temperature: float = 0.5,
|
730 |
-
initial_prompt: Optional[Union[str, Iterable[int]]] = None,
|
731 |
-
prefix: Optional[str] = None,
|
732 |
-
suppress_blank: bool = True,
|
733 |
-
suppress_tokens: Optional[List[int]] = [-1],
|
734 |
-
without_timestamps: bool = False,
|
735 |
-
max_initial_timestamp: float = 1.0,
|
736 |
-
word_timestamps: bool = False,
|
737 |
-
prepend_punctuations: str = "\"'“¿([{-",
|
738 |
-
append_punctuations: str = "\"'.。,,!!??::”)]}、",
|
739 |
-
multilingual: bool = False,
|
740 |
-
output_language: Optional[str] = None,
|
741 |
-
vad_filter: bool = False,
|
742 |
-
vad_parameters: Optional[Union[dict, VadOptions]] = None,
|
743 |
-
max_new_tokens: Optional[int] = None,
|
744 |
-
chunk_length: Optional[int] = None,
|
745 |
-
clip_timestamps: Union[str, List[float]] = "0",
|
746 |
-
hallucination_silence_threshold: Optional[float] = None,
|
747 |
-
hotwords: Optional[str] = None,
|
748 |
-
language_detection_threshold: Optional[float] = None,
|
749 |
-
language_detection_segments: int = 1,
|
750 |
-
) -> Tuple[Iterable[Segment], TranscriptionInfo]:
|
751 |
-
"""Transcribes an input file.
|
752 |
-
|
753 |
-
Arguments:
|
754 |
-
audio: Path to the input file (or a file-like object), or the audio waveform.
|
755 |
-
language: The language spoken in the audio. It should be a language code such
|
756 |
-
as "en" or "fr". If not set, the language will be detected in the first 30 seconds
|
757 |
-
of audio.
|
758 |
-
task: Task to execute (transcribe or translate).
|
759 |
-
beam_size: Beam size to use for decoding.
|
760 |
-
best_of: Number of candidates when sampling with non-zero temperature.
|
761 |
-
patience: Beam search patience factor.
|
762 |
-
length_penalty: Exponential length penalty constant.
|
763 |
-
repetition_penalty: Penalty applied to the score of previously generated tokens
|
764 |
-
(set > 1 to penalize).
|
765 |
-
no_repeat_ngram_size: Prevent repetitions of ngrams with this size (set 0 to disable).
|
766 |
-
temperature: Temperature for sampling. It can be a tuple of temperatures,
|
767 |
-
which will be successively used upon failures according to either
|
768 |
-
`compression_ratio_threshold` or `log_prob_threshold`.
|
769 |
-
compression_ratio_threshold: If the gzip compression ratio is above this value,
|
770 |
-
treat as failed.
|
771 |
-
log_prob_threshold: If the average log probability over sampled tokens is
|
772 |
-
below this value, treat as failed.
|
773 |
-
log_prob_low_threshold: This parameter alone is sufficient to skip an output text,
|
774 |
-
wheras log_prob_threshold also looks for appropriate no_speech_threshold value.
|
775 |
-
This value should be less than log_prob_threshold.
|
776 |
-
no_speech_threshold: If the no_speech probability is higher than this value AND
|
777 |
-
the average log probability over sampled tokens is below `log_prob_threshold`,
|
778 |
-
consider the segment as silent.
|
779 |
-
condition_on_previous_text: If True, the previous output of the model is provided
|
780 |
-
as a prompt for the next window; disabling may make the text inconsistent across
|
781 |
-
windows, but the model becomes less prone to getting stuck in a failure loop,
|
782 |
-
such as repetition looping or timestamps going out of sync.
|
783 |
-
prompt_reset_on_temperature: Resets prompt if temperature is above this value.
|
784 |
-
Arg has effect only if condition_on_previous_text is True.
|
785 |
-
initial_prompt: Optional text string or iterable of token ids to provide as a
|
786 |
-
prompt for the first window.
|
787 |
-
prefix: Optional text to provide as a prefix for the first window.
|
788 |
-
suppress_blank: Suppress blank outputs at the beginning of the sampling.
|
789 |
-
suppress_tokens: List of token IDs to suppress. -1 will suppress a default set
|
790 |
-
of symbols as defined in `tokenizer.non_speech_tokens()`.
|
791 |
-
without_timestamps: Only sample text tokens.
|
792 |
-
max_initial_timestamp: The initial timestamp cannot be later than this.
|
793 |
-
word_timestamps: Extract word-level timestamps using the cross-attention pattern
|
794 |
-
and dynamic time warping, and include the timestamps for each word in each segment.
|
795 |
-
prepend_punctuations: If word_timestamps is True, merge these punctuation symbols
|
796 |
-
with the next word
|
797 |
-
append_punctuations: If word_timestamps is True, merge these punctuation symbols
|
798 |
-
with the previous word
|
799 |
-
multilingual: If True, perform transcription on multilingual videos
|
800 |
-
and return the transcript based
|
801 |
-
on the 'output_language' flag.
|
802 |
-
output_language: Valid only if multilingual is set to True.
|
803 |
-
Specifies the string representing the output language. One of
|
804 |
-
'en' (English) or 'hybrid' (code-switched transcription).
|
805 |
-
vad_filter: Enable the voice activity detection (VAD) to filter out parts of the audio
|
806 |
-
without speech. This step is using the Silero VAD model
|
807 |
-
https://github.com/snakers4/silero-vad.
|
808 |
-
vad_parameters: Dictionary of Silero VAD parameters or VadOptions class (see available
|
809 |
-
parameters and default values in the class `VadOptions`).
|
810 |
-
max_new_tokens: Maximum number of new tokens to generate per-chunk. If not set,
|
811 |
-
the maximum will be set by the default max_length.
|
812 |
-
chunk_length: The length of audio segments. If it is not None, it will overwrite the
|
813 |
-
default chunk_length of the FeatureExtractor.
|
814 |
-
clip_timestamps:
|
815 |
-
Comma-separated list start,end,start,end,... timestamps (in seconds) of clips to
|
816 |
-
process. The last end timestamp defaults to the end of the file.
|
817 |
-
vad_filter will be ignored if clip_timestamps is used.
|
818 |
-
hallucination_silence_threshold:
|
819 |
-
When word_timestamps is True, skip silent periods longer than this threshold
|
820 |
-
(in seconds) when a possible hallucination is detected
|
821 |
-
hotwords:
|
822 |
-
Hotwords/hint phrases to provide the model with. Has no effect if prefix is not None.
|
823 |
-
language_detection_threshold: If the maximum probability of the language tokens is higher
|
824 |
-
than this value, the language is detected.
|
825 |
-
language_detection_segments: Number of segments to consider for the language detection.
|
826 |
-
Returns:
|
827 |
-
A tuple with:
|
828 |
-
|
829 |
-
- a generator over transcribed segments
|
830 |
-
- an instance of TranscriptionInfo
|
831 |
-
"""
|
832 |
-
|
833 |
-
sampling_rate = self.feature_extractor.sampling_rate
|
834 |
-
|
835 |
-
if isinstance(audio, np.ndarray):
|
836 |
-
audio = torch.from_numpy(audio)
|
837 |
-
elif not isinstance(audio, torch.Tensor):
|
838 |
-
audio = decode_audio(audio, sampling_rate=sampling_rate)
|
839 |
-
|
840 |
-
duration = audio.shape[0] / sampling_rate
|
841 |
-
duration_after_vad = duration
|
842 |
-
|
843 |
-
self.logger.info(
|
844 |
-
"Processing audio with duration %s", format_timestamp(duration)
|
845 |
-
)
|
846 |
-
|
847 |
-
if vad_filter and clip_timestamps == "0":
|
848 |
-
if vad_parameters is None:
|
849 |
-
vad_parameters = VadOptions()
|
850 |
-
elif isinstance(vad_parameters, dict):
|
851 |
-
vad_parameters = VadOptions(**vad_parameters)
|
852 |
-
speech_chunks = get_speech_timestamps(audio, vad_parameters)
|
853 |
-
audio = collect_chunks(audio, speech_chunks)
|
854 |
-
duration_after_vad = audio.shape[0] / sampling_rate
|
855 |
-
|
856 |
-
self.logger.info(
|
857 |
-
"VAD filter removed %s of audio",
|
858 |
-
format_timestamp(duration - duration_after_vad),
|
859 |
-
)
|
860 |
-
|
861 |
-
if self.logger.isEnabledFor(logging.DEBUG):
|
862 |
-
self.logger.debug(
|
863 |
-
"VAD filter kept the following audio segments: %s",
|
864 |
-
", ".join(
|
865 |
-
"[%s -> %s]"
|
866 |
-
% (
|
867 |
-
format_timestamp(chunk["start"] / sampling_rate),
|
868 |
-
format_timestamp(chunk["end"] / sampling_rate),
|
869 |
-
)
|
870 |
-
for chunk in speech_chunks
|
871 |
-
),
|
872 |
-
)
|
873 |
-
|
874 |
-
else:
|
875 |
-
speech_chunks = None
|
876 |
-
|
877 |
-
to_cpu = self.model.device == "cuda" and len(self.model.device_index) > 1
|
878 |
-
features = self.feature_extractor(
|
879 |
-
audio, chunk_length=chunk_length, to_cpu=to_cpu
|
880 |
-
)
|
881 |
-
|
882 |
-
encoder_output = None
|
883 |
-
all_language_probs = None
|
884 |
-
|
885 |
-
# setting output_language for multilingual videos
|
886 |
-
if multilingual:
|
887 |
-
if output_language is None:
|
888 |
-
output_language = "en"
|
889 |
-
elif output_language not in ["en", "hybrid"]:
|
890 |
-
raise ValueError("Output language needs to be one of 'en'/'hybrid'.")
|
891 |
-
|
892 |
-
# detecting the language if not provided
|
893 |
-
if language is None:
|
894 |
-
if not self.model.is_multilingual:
|
895 |
-
language = "en"
|
896 |
-
language_probability = 1
|
897 |
-
else:
|
898 |
-
if (
|
899 |
-
language_detection_segments is None
|
900 |
-
or language_detection_segments < 1
|
901 |
-
):
|
902 |
-
language_detection_segments = 1
|
903 |
-
start_timestamp = (
|
904 |
-
float(clip_timestamps.split(",")[0])
|
905 |
-
if isinstance(clip_timestamps, str)
|
906 |
-
else clip_timestamps[0]
|
907 |
-
)
|
908 |
-
content_frames = (
|
909 |
-
features.shape[-1] - self.feature_extractor.nb_max_frames
|
910 |
-
)
|
911 |
-
seek = (
|
912 |
-
int(start_timestamp * self.frames_per_second)
|
913 |
-
if start_timestamp * self.frames_per_second < content_frames
|
914 |
-
else 0
|
915 |
-
)
|
916 |
-
end_frames = min(
|
917 |
-
seek
|
918 |
-
+ self.feature_extractor.nb_max_frames
|
919 |
-
* language_detection_segments,
|
920 |
-
content_frames,
|
921 |
-
)
|
922 |
-
detected_language_info = {}
|
923 |
-
while seek <= end_frames:
|
924 |
-
segment = features[
|
925 |
-
:, seek : seek + self.feature_extractor.nb_max_frames
|
926 |
-
]
|
927 |
-
encoder_output = self.encode(segment)
|
928 |
-
# results is a list of tuple[str, float] with language names and
|
929 |
-
# probabilities.
|
930 |
-
results = self.model.detect_language(encoder_output)[0]
|
931 |
-
# Parse language names to strip out markers
|
932 |
-
all_language_probs = [
|
933 |
-
(token[2:-2], prob) for (token, prob) in results
|
934 |
-
]
|
935 |
-
# Get top language token and probability
|
936 |
-
language, language_probability = all_language_probs[0]
|
937 |
-
if (
|
938 |
-
language_detection_threshold is None
|
939 |
-
or language_probability > language_detection_threshold
|
940 |
-
):
|
941 |
-
break
|
942 |
-
detected_language_info.setdefault(language, []).append(
|
943 |
-
language_probability
|
944 |
-
)
|
945 |
-
seek += segment.shape[-1]
|
946 |
-
else:
|
947 |
-
# If no language detected for all segments, the majority vote of the highest
|
948 |
-
# projected languages for all segments is used to determine the language.
|
949 |
-
language = max(
|
950 |
-
detected_language_info,
|
951 |
-
key=lambda lang: len(detected_language_info[lang]),
|
952 |
-
)
|
953 |
-
language_probability = max(detected_language_info[language])
|
954 |
-
|
955 |
-
self.logger.info(
|
956 |
-
"Detected language '%s' with probability %.2f",
|
957 |
-
language,
|
958 |
-
language_probability,
|
959 |
-
)
|
960 |
-
else:
|
961 |
-
if not self.model.is_multilingual and language != "en":
|
962 |
-
self.logger.warning(
|
963 |
-
"The current model is English-only but the language parameter is set to '%s'; "
|
964 |
-
"using 'en' instead." % language
|
965 |
-
)
|
966 |
-
language = "en"
|
967 |
-
|
968 |
-
language_probability = 1
|
969 |
-
|
970 |
-
tokenizer = Tokenizer(
|
971 |
-
self.hf_tokenizer,
|
972 |
-
self.model.is_multilingual,
|
973 |
-
task=task,
|
974 |
-
language=language,
|
975 |
-
)
|
976 |
-
|
977 |
-
options = TranscriptionOptions(
|
978 |
-
beam_size=beam_size,
|
979 |
-
best_of=best_of,
|
980 |
-
patience=patience,
|
981 |
-
length_penalty=length_penalty,
|
982 |
-
repetition_penalty=repetition_penalty,
|
983 |
-
no_repeat_ngram_size=no_repeat_ngram_size,
|
984 |
-
log_prob_threshold=log_prob_threshold,
|
985 |
-
log_prob_low_threshold=log_prob_low_threshold,
|
986 |
-
no_speech_threshold=no_speech_threshold,
|
987 |
-
compression_ratio_threshold=compression_ratio_threshold,
|
988 |
-
condition_on_previous_text=condition_on_previous_text,
|
989 |
-
prompt_reset_on_temperature=prompt_reset_on_temperature,
|
990 |
-
temperatures=(
|
991 |
-
temperature if isinstance(temperature, (list, tuple)) else [temperature]
|
992 |
-
),
|
993 |
-
initial_prompt=initial_prompt,
|
994 |
-
prefix=prefix,
|
995 |
-
suppress_blank=suppress_blank,
|
996 |
-
suppress_tokens=(
|
997 |
-
get_suppressed_tokens(tokenizer, suppress_tokens)
|
998 |
-
if suppress_tokens
|
999 |
-
else suppress_tokens
|
1000 |
-
),
|
1001 |
-
without_timestamps=without_timestamps,
|
1002 |
-
max_initial_timestamp=max_initial_timestamp,
|
1003 |
-
word_timestamps=word_timestamps,
|
1004 |
-
prepend_punctuations=prepend_punctuations,
|
1005 |
-
append_punctuations=append_punctuations,
|
1006 |
-
multilingual=multilingual,
|
1007 |
-
output_language=output_language,
|
1008 |
-
max_new_tokens=max_new_tokens,
|
1009 |
-
clip_timestamps=clip_timestamps,
|
1010 |
-
hallucination_silence_threshold=hallucination_silence_threshold,
|
1011 |
-
hotwords=hotwords,
|
1012 |
-
)
|
1013 |
-
|
1014 |
-
segments = self.generate_segments(features, tokenizer, options, encoder_output)
|
1015 |
-
|
1016 |
-
if speech_chunks:
|
1017 |
-
segments = restore_speech_timestamps(segments, speech_chunks, sampling_rate)
|
1018 |
-
|
1019 |
-
info = TranscriptionInfo(
|
1020 |
-
language=language,
|
1021 |
-
language_probability=language_probability,
|
1022 |
-
duration=duration,
|
1023 |
-
duration_after_vad=duration_after_vad,
|
1024 |
-
transcription_options=options,
|
1025 |
-
vad_options=vad_parameters,
|
1026 |
-
all_language_probs=all_language_probs,
|
1027 |
-
)
|
1028 |
-
return segments, info
|
1029 |
-
|
1030 |
-
def _split_segments_by_timestamps(
|
1031 |
-
self,
|
1032 |
-
tokenizer: Tokenizer,
|
1033 |
-
tokens: List[int],
|
1034 |
-
time_offset: float,
|
1035 |
-
segment_size: int,
|
1036 |
-
segment_duration: float,
|
1037 |
-
seek: int,
|
1038 |
-
) -> List[List[int]]:
|
1039 |
-
current_segments = []
|
1040 |
-
single_timestamp_ending = (
|
1041 |
-
len(tokens) >= 2 and tokens[-2] < tokenizer.timestamp_begin <= tokens[-1]
|
1042 |
-
)
|
1043 |
-
|
1044 |
-
consecutive_timestamps = [
|
1045 |
-
i
|
1046 |
-
for i in range(len(tokens))
|
1047 |
-
if i > 0
|
1048 |
-
and tokens[i] >= tokenizer.timestamp_begin
|
1049 |
-
and tokens[i - 1] >= tokenizer.timestamp_begin
|
1050 |
-
]
|
1051 |
-
|
1052 |
-
if len(consecutive_timestamps) > 0:
|
1053 |
-
slices = list(consecutive_timestamps)
|
1054 |
-
if single_timestamp_ending:
|
1055 |
-
slices.append(len(tokens))
|
1056 |
-
|
1057 |
-
last_slice = 0
|
1058 |
-
for current_slice in slices:
|
1059 |
-
sliced_tokens = tokens[last_slice:current_slice]
|
1060 |
-
start_timestamp_position = sliced_tokens[0] - tokenizer.timestamp_begin
|
1061 |
-
end_timestamp_position = sliced_tokens[-1] - tokenizer.timestamp_begin
|
1062 |
-
start_time = (
|
1063 |
-
time_offset + start_timestamp_position * self.time_precision
|
1064 |
-
)
|
1065 |
-
end_time = time_offset + end_timestamp_position * self.time_precision
|
1066 |
-
|
1067 |
-
current_segments.append(
|
1068 |
-
dict(
|
1069 |
-
seek=seek,
|
1070 |
-
start=start_time,
|
1071 |
-
end=end_time,
|
1072 |
-
tokens=sliced_tokens,
|
1073 |
-
)
|
1074 |
-
)
|
1075 |
-
last_slice = current_slice
|
1076 |
-
|
1077 |
-
if single_timestamp_ending:
|
1078 |
-
# single timestamp at the end means no speech after the last timestamp.
|
1079 |
-
seek += segment_size
|
1080 |
-
else:
|
1081 |
-
# otherwise, ignore the unfinished segment and seek to the last timestamp
|
1082 |
-
last_timestamp_position = (
|
1083 |
-
tokens[last_slice - 1] - tokenizer.timestamp_begin
|
1084 |
-
)
|
1085 |
-
seek += last_timestamp_position * self.input_stride
|
1086 |
-
|
1087 |
-
else:
|
1088 |
-
duration = segment_duration
|
1089 |
-
timestamps = [
|
1090 |
-
token for token in tokens if token >= tokenizer.timestamp_begin
|
1091 |
-
]
|
1092 |
-
if len(timestamps) > 0 and timestamps[-1] != tokenizer.timestamp_begin:
|
1093 |
-
last_timestamp_position = timestamps[-1] - tokenizer.timestamp_begin
|
1094 |
-
duration = last_timestamp_position * self.time_precision
|
1095 |
-
|
1096 |
-
current_segments.append(
|
1097 |
-
dict(
|
1098 |
-
seek=seek,
|
1099 |
-
start=time_offset,
|
1100 |
-
end=time_offset + duration,
|
1101 |
-
tokens=tokens,
|
1102 |
-
)
|
1103 |
-
)
|
1104 |
-
|
1105 |
-
seek += segment_size
|
1106 |
-
|
1107 |
-
return current_segments, seek, single_timestamp_ending
|
1108 |
-
|
1109 |
-
def generate_segments(
|
1110 |
-
self,
|
1111 |
-
features: torch.Tensor,
|
1112 |
-
tokenizer: Tokenizer,
|
1113 |
-
options: TranscriptionOptions,
|
1114 |
-
encoder_output: Optional[ctranslate2.StorageView] = None,
|
1115 |
-
) -> Iterable[Segment]:
|
1116 |
-
content_frames = features.shape[-1] - self.feature_extractor.nb_max_frames
|
1117 |
-
content_duration = float(content_frames * self.feature_extractor.time_per_frame)
|
1118 |
-
|
1119 |
-
if isinstance(options.clip_timestamps, str):
|
1120 |
-
options = options._replace(
|
1121 |
-
clip_timestamps=[
|
1122 |
-
float(ts)
|
1123 |
-
for ts in (
|
1124 |
-
options.clip_timestamps.split(",")
|
1125 |
-
if options.clip_timestamps
|
1126 |
-
else []
|
1127 |
-
)
|
1128 |
-
]
|
1129 |
-
)
|
1130 |
-
seek_points: List[int] = [
|
1131 |
-
round(ts * self.frames_per_second) for ts in options.clip_timestamps
|
1132 |
-
]
|
1133 |
-
if len(seek_points) == 0:
|
1134 |
-
seek_points.append(0)
|
1135 |
-
if len(seek_points) % 2 == 1:
|
1136 |
-
seek_points.append(content_frames)
|
1137 |
-
seek_clips: List[Tuple[int, int]] = list(
|
1138 |
-
zip(seek_points[::2], seek_points[1::2])
|
1139 |
-
)
|
1140 |
-
|
1141 |
-
punctuation = "\"'“¿([{-\"'.。,,!!??::”)]}、"
|
1142 |
-
|
1143 |
-
idx = 0
|
1144 |
-
clip_idx = 0
|
1145 |
-
seek = seek_clips[clip_idx][0]
|
1146 |
-
all_tokens = []
|
1147 |
-
prompt_reset_since = 0
|
1148 |
-
|
1149 |
-
if options.initial_prompt is not None:
|
1150 |
-
if isinstance(options.initial_prompt, str):
|
1151 |
-
initial_prompt = " " + options.initial_prompt.strip()
|
1152 |
-
initial_prompt_tokens = tokenizer.encode(initial_prompt)
|
1153 |
-
all_tokens.extend(initial_prompt_tokens)
|
1154 |
-
else:
|
1155 |
-
all_tokens.extend(options.initial_prompt)
|
1156 |
-
|
1157 |
-
last_speech_timestamp = 0.0
|
1158 |
-
# NOTE: This loop is obscurely flattened to make the diff readable.
|
1159 |
-
# A later commit should turn this into a simpler nested loop.
|
1160 |
-
# for seek_clip_start, seek_clip_end in seek_clips:
|
1161 |
-
# while seek < seek_clip_end
|
1162 |
-
while clip_idx < len(seek_clips):
|
1163 |
-
seek_clip_start, seek_clip_end = seek_clips[clip_idx]
|
1164 |
-
if seek_clip_end > content_frames:
|
1165 |
-
seek_clip_end = content_frames
|
1166 |
-
if seek < seek_clip_start:
|
1167 |
-
seek = seek_clip_start
|
1168 |
-
if seek >= seek_clip_end:
|
1169 |
-
clip_idx += 1
|
1170 |
-
if clip_idx < len(seek_clips):
|
1171 |
-
seek = seek_clips[clip_idx][0]
|
1172 |
-
continue
|
1173 |
-
time_offset = seek * self.feature_extractor.time_per_frame
|
1174 |
-
window_end_time = float(
|
1175 |
-
(seek + self.feature_extractor.nb_max_frames)
|
1176 |
-
* self.feature_extractor.time_per_frame
|
1177 |
-
)
|
1178 |
-
segment_size = min(
|
1179 |
-
self.feature_extractor.nb_max_frames,
|
1180 |
-
content_frames - seek,
|
1181 |
-
seek_clip_end - seek,
|
1182 |
-
)
|
1183 |
-
segment = features[:, seek : seek + segment_size]
|
1184 |
-
segment_duration = segment_size * self.feature_extractor.time_per_frame
|
1185 |
-
segment = pad_or_trim(segment, self.feature_extractor.nb_max_frames)
|
1186 |
-
|
1187 |
-
if self.logger.isEnabledFor(logging.DEBUG):
|
1188 |
-
self.logger.debug(
|
1189 |
-
"Processing segment at %s", format_timestamp(time_offset)
|
1190 |
-
)
|
1191 |
-
|
1192 |
-
previous_tokens = all_tokens[prompt_reset_since:]
|
1193 |
-
|
1194 |
-
if encoder_output is None:
|
1195 |
-
encoder_output = self.encode(segment)
|
1196 |
-
|
1197 |
-
# Perform language detection at every segment to update task based on output language,
|
1198 |
-
# if the language is english, task is transcribe,
|
1199 |
-
# else the task is translate to english (default)
|
1200 |
-
# or transcribe if 'output_language' is 'hybrid'.
|
1201 |
-
if options.multilingual:
|
1202 |
-
results = self.model.detect_language(encoder_output)
|
1203 |
-
language_token, language_probability = results[0][0]
|
1204 |
-
language = language_token[2:-2]
|
1205 |
-
if options.output_language == "en" and language != "en":
|
1206 |
-
task = "translate"
|
1207 |
-
else:
|
1208 |
-
task = "transcribe"
|
1209 |
-
|
1210 |
-
# Update tokenizer based on task and language
|
1211 |
-
tokenizer.task = tokenizer.tokenizer.token_to_id(f"<|{task}|>")
|
1212 |
-
tokenizer.language = tokenizer.tokenizer.token_to_id(language_token)
|
1213 |
-
tokenizer.language_code = language
|
1214 |
-
# Update prompt based on task and language
|
1215 |
-
prompt = self.get_prompt(
|
1216 |
-
tokenizer,
|
1217 |
-
previous_tokens,
|
1218 |
-
without_timestamps=options.without_timestamps,
|
1219 |
-
prefix=options.prefix if seek == 0 else None,
|
1220 |
-
hotwords=options.hotwords,
|
1221 |
-
)
|
1222 |
-
|
1223 |
-
if seek > 0 or encoder_output is None:
|
1224 |
-
encoder_output = self.encode(segment)
|
1225 |
-
|
1226 |
-
(
|
1227 |
-
result,
|
1228 |
-
avg_logprob,
|
1229 |
-
temperature,
|
1230 |
-
compression_ratio,
|
1231 |
-
) = self.generate_with_fallback(encoder_output, prompt, tokenizer, options)
|
1232 |
-
|
1233 |
-
if options.no_speech_threshold is not None:
|
1234 |
-
# no voice activity check
|
1235 |
-
should_skip = result.no_speech_prob > options.no_speech_threshold
|
1236 |
-
|
1237 |
-
if (
|
1238 |
-
options.log_prob_threshold is not None
|
1239 |
-
and avg_logprob > options.log_prob_threshold
|
1240 |
-
):
|
1241 |
-
# don't skip if the logprob is high enough, despite the no_speech_prob
|
1242 |
-
should_skip = False
|
1243 |
-
|
1244 |
-
if should_skip:
|
1245 |
-
self.logger.debug(
|
1246 |
-
"No speech threshold is met (%f > %f)",
|
1247 |
-
result.no_speech_prob,
|
1248 |
-
options.no_speech_threshold,
|
1249 |
-
)
|
1250 |
-
|
1251 |
-
# Skip if the logprob is very low (below the threshold value),
|
1252 |
-
# despite no_speech_prob being low (ex: Too ambiguous outputs)
|
1253 |
-
if options.log_prob_low_threshold:
|
1254 |
-
if avg_logprob < options.log_prob_low_threshold:
|
1255 |
-
should_skip = True
|
1256 |
-
self.logger.debug(
|
1257 |
-
"log prob low threshold is met (%f > %f)",
|
1258 |
-
avg_logprob,
|
1259 |
-
options.log_prob_low_threshold,
|
1260 |
-
)
|
1261 |
-
|
1262 |
-
if should_skip:
|
1263 |
-
# fast-forward to the next segment boundary
|
1264 |
-
seek += segment_size
|
1265 |
-
continue
|
1266 |
-
|
1267 |
-
tokens = result.sequences_ids[0]
|
1268 |
-
|
1269 |
-
previous_seek = seek
|
1270 |
-
|
1271 |
-
# anomalous words are very long/short/improbable
|
1272 |
-
def word_anomaly_score(word: dict) -> float:
|
1273 |
-
probability = word.get("probability", 0.0)
|
1274 |
-
duration = word["end"] - word["start"]
|
1275 |
-
score = 0.0
|
1276 |
-
if probability < 0.15:
|
1277 |
-
score += 1.0
|
1278 |
-
if duration < 0.133:
|
1279 |
-
score += (0.133 - duration) * 15
|
1280 |
-
if duration > 2.0:
|
1281 |
-
score += duration - 2.0
|
1282 |
-
return score
|
1283 |
-
|
1284 |
-
def is_segment_anomaly(segment: Optional[dict]) -> bool:
|
1285 |
-
if segment is None or not segment["words"]:
|
1286 |
-
return False
|
1287 |
-
words = [w for w in segment["words"] if w["word"] not in punctuation]
|
1288 |
-
words = words[:8]
|
1289 |
-
score = sum(word_anomaly_score(w) for w in words)
|
1290 |
-
return score >= 3 or score + 0.01 >= len(words)
|
1291 |
-
|
1292 |
-
def next_words_segment(segments: List[dict]) -> Optional[dict]:
|
1293 |
-
return next((s for s in segments if s["words"]), None)
|
1294 |
-
|
1295 |
-
(
|
1296 |
-
current_segments,
|
1297 |
-
seek,
|
1298 |
-
single_timestamp_ending,
|
1299 |
-
) = self._split_segments_by_timestamps(
|
1300 |
-
tokenizer=tokenizer,
|
1301 |
-
tokens=tokens,
|
1302 |
-
time_offset=time_offset,
|
1303 |
-
segment_size=segment_size,
|
1304 |
-
segment_duration=segment_duration,
|
1305 |
-
seek=seek,
|
1306 |
-
)
|
1307 |
-
|
1308 |
-
if options.word_timestamps:
|
1309 |
-
self.add_word_timestamps(
|
1310 |
-
[current_segments],
|
1311 |
-
tokenizer,
|
1312 |
-
encoder_output,
|
1313 |
-
segment_size,
|
1314 |
-
options.prepend_punctuations,
|
1315 |
-
options.append_punctuations,
|
1316 |
-
last_speech_timestamp=last_speech_timestamp,
|
1317 |
-
)
|
1318 |
-
if not single_timestamp_ending:
|
1319 |
-
last_word_end = get_end(current_segments)
|
1320 |
-
if last_word_end is not None and last_word_end > time_offset:
|
1321 |
-
seek = round(last_word_end * self.frames_per_second)
|
1322 |
-
|
1323 |
-
# skip silence before possible hallucinations
|
1324 |
-
if options.hallucination_silence_threshold is not None:
|
1325 |
-
threshold = options.hallucination_silence_threshold
|
1326 |
-
|
1327 |
-
# if first segment might be a hallucination, skip leading silence
|
1328 |
-
first_segment = next_words_segment(current_segments)
|
1329 |
-
if first_segment is not None and is_segment_anomaly(first_segment):
|
1330 |
-
gap = first_segment["start"] - time_offset
|
1331 |
-
if gap > threshold:
|
1332 |
-
seek = previous_seek + round(gap * self.frames_per_second)
|
1333 |
-
continue
|
1334 |
-
|
1335 |
-
# skip silence before any possible hallucination that is surrounded
|
1336 |
-
# by silence or more hallucinations
|
1337 |
-
hal_last_end = last_speech_timestamp
|
1338 |
-
for si in range(len(current_segments)):
|
1339 |
-
segment = current_segments[si]
|
1340 |
-
if not segment["words"]:
|
1341 |
-
continue
|
1342 |
-
if is_segment_anomaly(segment):
|
1343 |
-
next_segment = next_words_segment(
|
1344 |
-
current_segments[si + 1 :]
|
1345 |
-
)
|
1346 |
-
if next_segment is not None:
|
1347 |
-
hal_next_start = next_segment["words"][0]["start"]
|
1348 |
-
else:
|
1349 |
-
hal_next_start = time_offset + segment_duration
|
1350 |
-
silence_before = (
|
1351 |
-
segment["start"] - hal_last_end > threshold
|
1352 |
-
or segment["start"] < threshold
|
1353 |
-
or segment["start"] - time_offset < 2.0
|
1354 |
-
)
|
1355 |
-
silence_after = (
|
1356 |
-
hal_next_start - segment["end"] > threshold
|
1357 |
-
or is_segment_anomaly(next_segment)
|
1358 |
-
or window_end_time - segment["end"] < 2.0
|
1359 |
-
)
|
1360 |
-
if silence_before and silence_after:
|
1361 |
-
seek = round(
|
1362 |
-
max(time_offset + 1, segment["start"])
|
1363 |
-
* self.frames_per_second
|
1364 |
-
)
|
1365 |
-
if content_duration - segment["end"] < threshold:
|
1366 |
-
seek = content_frames
|
1367 |
-
current_segments[si:] = []
|
1368 |
-
break
|
1369 |
-
hal_last_end = segment["end"]
|
1370 |
-
|
1371 |
-
last_word_end = get_end(current_segments)
|
1372 |
-
if last_word_end is not None:
|
1373 |
-
last_speech_timestamp = last_word_end
|
1374 |
-
for segment in current_segments:
|
1375 |
-
tokens = segment["tokens"]
|
1376 |
-
text = tokenizer.decode(tokens)
|
1377 |
-
|
1378 |
-
if segment["start"] == segment["end"] or not text.strip():
|
1379 |
-
continue
|
1380 |
-
|
1381 |
-
all_tokens.extend(tokens)
|
1382 |
-
idx += 1
|
1383 |
-
|
1384 |
-
yield Segment(
|
1385 |
-
id=idx,
|
1386 |
-
seek=seek,
|
1387 |
-
start=segment["start"],
|
1388 |
-
end=segment["end"],
|
1389 |
-
text=text,
|
1390 |
-
tokens=tokens,
|
1391 |
-
temperature=temperature,
|
1392 |
-
avg_logprob=avg_logprob,
|
1393 |
-
compression_ratio=compression_ratio,
|
1394 |
-
no_speech_prob=result.no_speech_prob,
|
1395 |
-
words=(
|
1396 |
-
[Word(**word) for word in segment["words"]]
|
1397 |
-
if options.word_timestamps
|
1398 |
-
else None
|
1399 |
-
),
|
1400 |
-
)
|
1401 |
-
|
1402 |
-
if (
|
1403 |
-
not options.condition_on_previous_text
|
1404 |
-
or temperature > options.prompt_reset_on_temperature
|
1405 |
-
):
|
1406 |
-
if options.condition_on_previous_text:
|
1407 |
-
self.logger.debug(
|
1408 |
-
"Reset prompt. prompt_reset_on_temperature threshold is met %f > %f",
|
1409 |
-
temperature,
|
1410 |
-
options.prompt_reset_on_temperature,
|
1411 |
-
)
|
1412 |
-
|
1413 |
-
prompt_reset_since = len(all_tokens)
|
1414 |
-
|
1415 |
-
def encode(self, features: torch.Tensor) -> ctranslate2.StorageView:
|
1416 |
-
# When the model is running on multiple GPUs, the encoder output should be moved
|
1417 |
-
# to the CPU since we don't know which GPU will handle the next job.
|
1418 |
-
to_cpu = self.model.device == "cuda" and len(self.model.device_index) > 1
|
1419 |
-
|
1420 |
-
if features.ndim == 2:
|
1421 |
-
features = features.unsqueeze(0)
|
1422 |
-
features = get_ctranslate2_storage(features)
|
1423 |
-
|
1424 |
-
return self.model.encode(features, to_cpu=to_cpu)
|
1425 |
-
|
1426 |
-
def generate_with_fallback(
|
1427 |
-
self,
|
1428 |
-
encoder_output: ctranslate2.StorageView,
|
1429 |
-
prompt: List[int],
|
1430 |
-
tokenizer: Tokenizer,
|
1431 |
-
options: TranscriptionOptions,
|
1432 |
-
) -> Tuple[ctranslate2.models.WhisperGenerationResult, float, float, float]:
|
1433 |
-
decode_result = None
|
1434 |
-
all_results = []
|
1435 |
-
below_cr_threshold_results = []
|
1436 |
-
|
1437 |
-
max_initial_timestamp_index = int(
|
1438 |
-
round(options.max_initial_timestamp / self.time_precision)
|
1439 |
-
)
|
1440 |
-
if options.max_new_tokens is not None:
|
1441 |
-
max_length = len(prompt) + options.max_new_tokens
|
1442 |
-
else:
|
1443 |
-
max_length = self.max_length
|
1444 |
-
|
1445 |
-
if max_length > self.max_length:
|
1446 |
-
raise ValueError(
|
1447 |
-
f"The length of the prompt is {len(prompt)}, and the `max_new_tokens` "
|
1448 |
-
f"{max_length - len(prompt)}. Thus, the combined length of the prompt "
|
1449 |
-
f"and `max_new_tokens` is: {max_length}. This exceeds the "
|
1450 |
-
f"`max_length` of the Whisper model: {self.max_length}. "
|
1451 |
-
"You should either reduce the length of your prompt, or "
|
1452 |
-
"reduce the value of `max_new_tokens`, "
|
1453 |
-
f"so that their combined length is less that {self.max_length}."
|
1454 |
-
)
|
1455 |
-
|
1456 |
-
for temperature in options.temperatures:
|
1457 |
-
if temperature > 0:
|
1458 |
-
kwargs = {
|
1459 |
-
"beam_size": 1,
|
1460 |
-
"num_hypotheses": options.best_of,
|
1461 |
-
"sampling_topk": 0,
|
1462 |
-
"sampling_temperature": temperature,
|
1463 |
-
}
|
1464 |
-
else:
|
1465 |
-
kwargs = {
|
1466 |
-
"beam_size": options.beam_size,
|
1467 |
-
"patience": options.patience,
|
1468 |
-
}
|
1469 |
-
|
1470 |
-
result = self.model.generate(
|
1471 |
-
encoder_output,
|
1472 |
-
[prompt],
|
1473 |
-
length_penalty=options.length_penalty,
|
1474 |
-
repetition_penalty=options.repetition_penalty,
|
1475 |
-
no_repeat_ngram_size=options.no_repeat_ngram_size,
|
1476 |
-
max_length=max_length,
|
1477 |
-
return_scores=True,
|
1478 |
-
return_no_speech_prob=True,
|
1479 |
-
suppress_blank=options.suppress_blank,
|
1480 |
-
suppress_tokens=options.suppress_tokens,
|
1481 |
-
max_initial_timestamp_index=max_initial_timestamp_index,
|
1482 |
-
**kwargs,
|
1483 |
-
)[0]
|
1484 |
-
|
1485 |
-
tokens = result.sequences_ids[0]
|
1486 |
-
|
1487 |
-
# Recover the average log prob from the returned score.
|
1488 |
-
seq_len = len(tokens)
|
1489 |
-
cum_logprob = result.scores[0] * (seq_len**options.length_penalty)
|
1490 |
-
avg_logprob = cum_logprob / (seq_len + 1)
|
1491 |
-
|
1492 |
-
text = tokenizer.decode(tokens).strip()
|
1493 |
-
compression_ratio = get_compression_ratio(text)
|
1494 |
-
|
1495 |
-
decode_result = (
|
1496 |
-
result,
|
1497 |
-
avg_logprob,
|
1498 |
-
temperature,
|
1499 |
-
compression_ratio,
|
1500 |
-
)
|
1501 |
-
all_results.append(decode_result)
|
1502 |
-
|
1503 |
-
needs_fallback = False
|
1504 |
-
|
1505 |
-
if options.compression_ratio_threshold is not None:
|
1506 |
-
if compression_ratio > options.compression_ratio_threshold:
|
1507 |
-
needs_fallback = True # too repetitive
|
1508 |
-
|
1509 |
-
self.logger.debug(
|
1510 |
-
"Compression ratio threshold is not met with temperature %.1f (%f > %f)",
|
1511 |
-
temperature,
|
1512 |
-
compression_ratio,
|
1513 |
-
options.compression_ratio_threshold,
|
1514 |
-
)
|
1515 |
-
else:
|
1516 |
-
below_cr_threshold_results.append(decode_result)
|
1517 |
-
|
1518 |
-
if (
|
1519 |
-
options.log_prob_threshold is not None
|
1520 |
-
and avg_logprob < options.log_prob_threshold
|
1521 |
-
):
|
1522 |
-
needs_fallback = True # average log probability is too low
|
1523 |
-
|
1524 |
-
self.logger.debug(
|
1525 |
-
"Log probability threshold is not met with temperature %.1f (%f < %f)",
|
1526 |
-
temperature,
|
1527 |
-
avg_logprob,
|
1528 |
-
options.log_prob_threshold,
|
1529 |
-
)
|
1530 |
-
|
1531 |
-
if (
|
1532 |
-
options.no_speech_threshold is not None
|
1533 |
-
and result.no_speech_prob > options.no_speech_threshold
|
1534 |
-
and options.log_prob_threshold is not None
|
1535 |
-
and avg_logprob < options.log_prob_threshold
|
1536 |
-
):
|
1537 |
-
needs_fallback = False # silence
|
1538 |
-
|
1539 |
-
if not needs_fallback:
|
1540 |
-
break
|
1541 |
-
else:
|
1542 |
-
# all failed, select the result with the highest average log probability
|
1543 |
-
decode_result = max(
|
1544 |
-
below_cr_threshold_results or all_results, key=lambda x: x[1]
|
1545 |
-
)
|
1546 |
-
# to pass final temperature for prompt_reset_on_temperature
|
1547 |
-
decode_result = (
|
1548 |
-
decode_result[0],
|
1549 |
-
decode_result[1],
|
1550 |
-
temperature,
|
1551 |
-
decode_result[3],
|
1552 |
-
)
|
1553 |
-
|
1554 |
-
return decode_result
|
1555 |
-
|
1556 |
-
def get_prompt(
|
1557 |
-
self,
|
1558 |
-
tokenizer: Tokenizer,
|
1559 |
-
previous_tokens: List[int],
|
1560 |
-
without_timestamps: bool = False,
|
1561 |
-
prefix: Optional[str] = None,
|
1562 |
-
hotwords: Optional[str] = None,
|
1563 |
-
) -> List[int]:
|
1564 |
-
prompt = []
|
1565 |
-
|
1566 |
-
if previous_tokens or (hotwords and not prefix):
|
1567 |
-
prompt.append(tokenizer.sot_prev)
|
1568 |
-
if hotwords and not prefix:
|
1569 |
-
hotwords_tokens = tokenizer.encode(" " + hotwords.strip())
|
1570 |
-
if len(hotwords_tokens) >= self.max_length // 2:
|
1571 |
-
hotwords_tokens = hotwords_tokens[: self.max_length // 2 - 1]
|
1572 |
-
prompt.extend(hotwords_tokens)
|
1573 |
-
if previous_tokens:
|
1574 |
-
prompt.extend(previous_tokens[-(self.max_length // 2 - 1) :])
|
1575 |
-
|
1576 |
-
prompt.extend(tokenizer.sot_sequence)
|
1577 |
-
|
1578 |
-
if without_timestamps:
|
1579 |
-
prompt.append(tokenizer.no_timestamps)
|
1580 |
-
|
1581 |
-
if prefix:
|
1582 |
-
prefix_tokens = tokenizer.encode(" " + prefix.strip())
|
1583 |
-
if len(prefix_tokens) >= self.max_length // 2:
|
1584 |
-
prefix_tokens = prefix_tokens[: self.max_length // 2 - 1]
|
1585 |
-
if not without_timestamps:
|
1586 |
-
prompt.append(tokenizer.timestamp_begin)
|
1587 |
-
prompt.extend(prefix_tokens)
|
1588 |
-
|
1589 |
-
return prompt
|
1590 |
-
|
1591 |
-
def add_word_timestamps(
|
1592 |
-
self,
|
1593 |
-
segments: List[dict],
|
1594 |
-
tokenizer: Tokenizer,
|
1595 |
-
encoder_output: ctranslate2.StorageView,
|
1596 |
-
num_frames: int,
|
1597 |
-
prepend_punctuations: str,
|
1598 |
-
append_punctuations: str,
|
1599 |
-
last_speech_timestamp: float,
|
1600 |
-
) -> float:
|
1601 |
-
if len(segments) == 0:
|
1602 |
-
return
|
1603 |
-
|
1604 |
-
text_tokens = []
|
1605 |
-
text_tokens_per_segment = []
|
1606 |
-
for segment in segments:
|
1607 |
-
segment_tokens = [
|
1608 |
-
[token for token in subsegment["tokens"] if token < tokenizer.eot]
|
1609 |
-
for subsegment in segment
|
1610 |
-
]
|
1611 |
-
text_tokens.append(list(itertools.chain.from_iterable(segment_tokens)))
|
1612 |
-
text_tokens_per_segment.append(segment_tokens)
|
1613 |
-
|
1614 |
-
alignments = self.find_alignment(
|
1615 |
-
tokenizer, text_tokens, encoder_output, num_frames
|
1616 |
-
)
|
1617 |
-
median_max_durations = []
|
1618 |
-
for alignment in alignments:
|
1619 |
-
word_durations = np.array(
|
1620 |
-
[word["end"] - word["start"] for word in alignment]
|
1621 |
-
)
|
1622 |
-
word_durations = word_durations[word_durations.nonzero()]
|
1623 |
-
median_duration = (
|
1624 |
-
np.median(word_durations) if len(word_durations) > 0 else 0.0
|
1625 |
-
)
|
1626 |
-
median_duration = min(0.7, float(median_duration))
|
1627 |
-
max_duration = median_duration * 2
|
1628 |
-
|
1629 |
-
# hack: truncate long words at sentence boundaries.
|
1630 |
-
# a better segmentation algorithm based on VAD should be able to replace this.
|
1631 |
-
if len(word_durations) > 0:
|
1632 |
-
sentence_end_marks = ".。!!??"
|
1633 |
-
# ensure words at sentence boundaries
|
1634 |
-
# are not longer than twice the median word duration.
|
1635 |
-
for i in range(1, len(alignment)):
|
1636 |
-
if alignment[i]["end"] - alignment[i]["start"] > max_duration:
|
1637 |
-
if alignment[i]["word"] in sentence_end_marks:
|
1638 |
-
alignment[i]["end"] = alignment[i]["start"] + max_duration
|
1639 |
-
elif alignment[i - 1]["word"] in sentence_end_marks:
|
1640 |
-
alignment[i]["start"] = alignment[i]["end"] - max_duration
|
1641 |
-
|
1642 |
-
merge_punctuations(alignment, prepend_punctuations, append_punctuations)
|
1643 |
-
median_max_durations.append((median_duration, max_duration))
|
1644 |
-
|
1645 |
-
for segment_idx, segment in enumerate(segments):
|
1646 |
-
word_index = 0
|
1647 |
-
time_offset = segment[0]["start"]
|
1648 |
-
median_duration, max_duration = median_max_durations[segment_idx]
|
1649 |
-
for subsegment_idx, subsegment in enumerate(segment):
|
1650 |
-
saved_tokens = 0
|
1651 |
-
words = []
|
1652 |
-
|
1653 |
-
while word_index < len(alignments[segment_idx]) and saved_tokens < len(
|
1654 |
-
text_tokens_per_segment[segment_idx][subsegment_idx]
|
1655 |
-
):
|
1656 |
-
timing = alignments[segment_idx][word_index]
|
1657 |
-
|
1658 |
-
if timing["word"]:
|
1659 |
-
words.append(
|
1660 |
-
dict(
|
1661 |
-
word=timing["word"],
|
1662 |
-
start=round(time_offset + timing["start"], 2),
|
1663 |
-
end=round(time_offset + timing["end"], 2),
|
1664 |
-
probability=timing["probability"],
|
1665 |
-
)
|
1666 |
-
)
|
1667 |
-
|
1668 |
-
saved_tokens += len(timing["tokens"])
|
1669 |
-
word_index += 1
|
1670 |
-
|
1671 |
-
# hack: truncate long words at segment boundaries.
|
1672 |
-
# a better segmentation algorithm based on VAD should be able to replace this.
|
1673 |
-
if len(words) > 0:
|
1674 |
-
# ensure the first and second word after a pause is not longer than
|
1675 |
-
# twice the median word duration.
|
1676 |
-
if words[0][
|
1677 |
-
"end"
|
1678 |
-
] - last_speech_timestamp > median_duration * 4 and (
|
1679 |
-
words[0]["end"] - words[0]["start"] > max_duration
|
1680 |
-
or (
|
1681 |
-
len(words) > 1
|
1682 |
-
and words[1]["end"] - words[0]["start"] > max_duration * 2
|
1683 |
-
)
|
1684 |
-
):
|
1685 |
-
if (
|
1686 |
-
len(words) > 1
|
1687 |
-
and words[1]["end"] - words[1]["start"] > max_duration
|
1688 |
-
):
|
1689 |
-
boundary = max(
|
1690 |
-
words[1]["end"] / 2, words[1]["end"] - max_duration
|
1691 |
-
)
|
1692 |
-
words[0]["end"] = words[1]["start"] = boundary
|
1693 |
-
words[0]["start"] = max(0, words[0]["end"] - max_duration)
|
1694 |
-
|
1695 |
-
# prefer the segment-level start timestamp if the first word is too long.
|
1696 |
-
if (
|
1697 |
-
subsegment["start"] < words[0]["end"]
|
1698 |
-
and subsegment["start"] - 0.5 > words[0]["start"]
|
1699 |
-
):
|
1700 |
-
words[0]["start"] = max(
|
1701 |
-
0,
|
1702 |
-
min(words[0]["end"] - median_duration, subsegment["start"]),
|
1703 |
-
)
|
1704 |
-
else:
|
1705 |
-
subsegment["start"] = words[0]["start"]
|
1706 |
-
|
1707 |
-
# prefer the segment-level end timestamp if the last word is too long.
|
1708 |
-
if (
|
1709 |
-
subsegment["end"] > words[-1]["start"]
|
1710 |
-
and subsegment["end"] + 0.5 < words[-1]["end"]
|
1711 |
-
):
|
1712 |
-
words[-1]["end"] = max(
|
1713 |
-
words[-1]["start"] + median_duration, subsegment["end"]
|
1714 |
-
)
|
1715 |
-
else:
|
1716 |
-
subsegment["end"] = words[-1]["end"]
|
1717 |
-
|
1718 |
-
last_speech_timestamp = subsegment["end"]
|
1719 |
-
segments[segment_idx][subsegment_idx]["words"] = words
|
1720 |
-
return last_speech_timestamp
|
1721 |
-
|
1722 |
-
def find_alignment(
|
1723 |
-
self,
|
1724 |
-
tokenizer: Tokenizer,
|
1725 |
-
text_tokens: List[int],
|
1726 |
-
encoder_output: ctranslate2.StorageView,
|
1727 |
-
num_frames: int,
|
1728 |
-
median_filter_width: int = 7,
|
1729 |
-
) -> List[dict]:
|
1730 |
-
if len(text_tokens) == 0:
|
1731 |
-
return []
|
1732 |
-
|
1733 |
-
results = self.model.align(
|
1734 |
-
encoder_output,
|
1735 |
-
tokenizer.sot_sequence,
|
1736 |
-
text_tokens,
|
1737 |
-
num_frames,
|
1738 |
-
median_filter_width=median_filter_width,
|
1739 |
-
)
|
1740 |
-
return_list = []
|
1741 |
-
for result, text_token in zip(results, text_tokens):
|
1742 |
-
text_token_probs = result.text_token_probs
|
1743 |
-
alignments = result.alignments
|
1744 |
-
text_indices = np.array([pair[0] for pair in alignments])
|
1745 |
-
time_indices = np.array([pair[1] for pair in alignments])
|
1746 |
-
|
1747 |
-
words, word_tokens = tokenizer.split_to_word_tokens(
|
1748 |
-
text_token + [tokenizer.eot]
|
1749 |
-
)
|
1750 |
-
if len(word_tokens) <= 1:
|
1751 |
-
# return on eot only
|
1752 |
-
# >>> np.pad([], (1, 0))
|
1753 |
-
# array([0.])
|
1754 |
-
# This results in crashes when we lookup jump_times with float, like
|
1755 |
-
# IndexError: arrays used as indices must be of integer (or boolean) type
|
1756 |
-
return []
|
1757 |
-
word_boundaries = np.pad(
|
1758 |
-
np.cumsum([len(t) for t in word_tokens[:-1]]), (1, 0)
|
1759 |
-
)
|
1760 |
-
if len(word_boundaries) <= 1:
|
1761 |
-
return []
|
1762 |
-
|
1763 |
-
jumps = np.pad(np.diff(text_indices), (1, 0), constant_values=1).astype(
|
1764 |
-
bool
|
1765 |
-
)
|
1766 |
-
jump_times = time_indices[jumps] / self.tokens_per_second
|
1767 |
-
start_times = jump_times[word_boundaries[:-1]]
|
1768 |
-
end_times = jump_times[word_boundaries[1:]]
|
1769 |
-
word_probabilities = [
|
1770 |
-
np.mean(text_token_probs[i:j])
|
1771 |
-
for i, j in zip(word_boundaries[:-1], word_boundaries[1:])
|
1772 |
-
]
|
1773 |
-
|
1774 |
-
return_list.append(
|
1775 |
-
[
|
1776 |
-
dict(
|
1777 |
-
word=word,
|
1778 |
-
tokens=tokens,
|
1779 |
-
start=start,
|
1780 |
-
end=end,
|
1781 |
-
probability=probability,
|
1782 |
-
)
|
1783 |
-
for word, tokens, start, end, probability in zip(
|
1784 |
-
words, word_tokens, start_times, end_times, word_probabilities
|
1785 |
-
)
|
1786 |
-
]
|
1787 |
-
)
|
1788 |
-
return return_list
|
1789 |
-
|
1790 |
-
def generate_segment_batched(
|
1791 |
-
self,
|
1792 |
-
features: torch.Tensor,
|
1793 |
-
tokenizer: Tokenizer,
|
1794 |
-
options: dict,
|
1795 |
-
):
|
1796 |
-
batch_size = features.shape[0]
|
1797 |
-
all_tokens = []
|
1798 |
-
prompt_reset_since = 0
|
1799 |
-
|
1800 |
-
if options["initial_prompt"] is not None:
|
1801 |
-
initial_prompt = " " + options["initial_prompt"].strip()
|
1802 |
-
initial_prompt_tokens = tokenizer.encode(initial_prompt)
|
1803 |
-
all_tokens.extend(initial_prompt_tokens)
|
1804 |
-
previous_tokens = all_tokens[prompt_reset_since:]
|
1805 |
-
prompt = self.get_prompt(
|
1806 |
-
tokenizer,
|
1807 |
-
previous_tokens,
|
1808 |
-
without_timestamps=options["without_timestamps"],
|
1809 |
-
prefix=options["prefix"],
|
1810 |
-
)
|
1811 |
-
|
1812 |
-
encoder_output = self.encode(features)
|
1813 |
-
|
1814 |
-
result = self.model.generate(
|
1815 |
-
encoder_output,
|
1816 |
-
[prompt] * batch_size,
|
1817 |
-
beam_size=options["beam_size"],
|
1818 |
-
patience=options["patience"],
|
1819 |
-
length_penalty=options["length_penalty"],
|
1820 |
-
max_length=self.max_length,
|
1821 |
-
suppress_blank=options["suppress_blank"],
|
1822 |
-
suppress_tokens=options["suppress_tokens"],
|
1823 |
-
return_scores=True,
|
1824 |
-
return_no_speech_prob=True,
|
1825 |
-
)
|
1826 |
-
|
1827 |
-
output = []
|
1828 |
-
for res in result:
|
1829 |
-
output.append({})
|
1830 |
-
# return scores
|
1831 |
-
seq_len = len(res.sequences_ids[0])
|
1832 |
-
cum_logprob = res.scores[0] * (seq_len ** options["length_penalty"])
|
1833 |
-
output[-1]["avg_logprob"] = cum_logprob / (seq_len + 1)
|
1834 |
-
|
1835 |
-
# return no speech prob
|
1836 |
-
output[-1]["no_speech_prob"] = res.no_speech_prob
|
1837 |
-
output[-1]["tokens"] = res.sequences_ids[0]
|
1838 |
-
|
1839 |
-
return encoder_output, output
|
1840 |
-
|
1841 |
-
def detect_language(self, audio: torch.Tensor):
|
1842 |
-
to_cpu = self.model.device == "cuda" and len(self.model.device_index) > 1
|
1843 |
-
segment = self.feature_extractor(audio, padding=True, to_cpu=to_cpu)[
|
1844 |
-
:, : self.feature_extractor.nb_max_frames
|
1845 |
-
]
|
1846 |
-
encoder_output = self.encode(segment)
|
1847 |
-
results = self.model.detect_language(encoder_output)
|
1848 |
-
language_token, language_probability = results[0][0]
|
1849 |
-
language = language_token[2:-2]
|
1850 |
-
self.logger.info(
|
1851 |
-
f"Detected language: {language} ({language_probability:.2f}) in first 30s of audio..."
|
1852 |
-
)
|
1853 |
-
all_language_probs = [(token[2:-2], prob) for (token, prob) in results[0]]
|
1854 |
-
return language, language_probability, all_language_probs
|
1855 |
-
|
1856 |
-
def detect_language_multi_segment(
|
1857 |
-
self, audio: Union[str, BinaryIO, torch.Tensor], params: Optional[dict] = None
|
1858 |
-
):
|
1859 |
-
"""
|
1860 |
-
Detect language based on N highly-confident segments of a language.
|
1861 |
-
"""
|
1862 |
-
# The threshold is used to decide if the audio is silence or not.
|
1863 |
-
# The default is 0.02 (2.0%) i.e, if more than 2.0% of the audio is silent,
|
1864 |
-
# the audio is considered as silence.
|
1865 |
-
if not params:
|
1866 |
-
params = {
|
1867 |
-
"multilingual": False,
|
1868 |
-
"speech_percentage_threshold": 0.02,
|
1869 |
-
"language_detection_segments": 4,
|
1870 |
-
"vad_filter": True,
|
1871 |
-
"vad_min_silence_duration": 2500,
|
1872 |
-
"language_threshold": 0.7,
|
1873 |
-
}
|
1874 |
-
|
1875 |
-
if params.get("multilingual", False):
|
1876 |
-
logging.warning(
|
1877 |
-
"lang_id is not supported for multilingual audios, detecting the major language."
|
1878 |
-
)
|
1879 |
-
|
1880 |
-
speech_percentage_threshold = params.get("speech_percentage_threshold", 0.02)
|
1881 |
-
language_threshold = params.get("language_threshold", 0.7)
|
1882 |
-
num_detection_segments = params.get("language_detection_segments", 4)
|
1883 |
-
vad_filter_enabled = params.get("vad_filter", True)
|
1884 |
-
vad_params = dict(
|
1885 |
-
min_silence_duration_ms=params.get("vad_min_silence_duration", 2500)
|
1886 |
-
)
|
1887 |
-
|
1888 |
-
if vad_filter_enabled:
|
1889 |
-
vad_params = VadOptions(**vad_params)
|
1890 |
-
|
1891 |
-
# decode audio if it is not decoded already
|
1892 |
-
sampling_rate = self.feature_extractor.sampling_rate
|
1893 |
-
if not isinstance(audio, torch.Tensor):
|
1894 |
-
audio: torch.Tensor = decode_audio(audio, sampling_rate=sampling_rate)
|
1895 |
-
|
1896 |
-
# calculate duration of audio as number of seconds
|
1897 |
-
# audio.shape[0] is the number of samples in the audio
|
1898 |
-
# sampling_rate is the number of samples per second
|
1899 |
-
# if we divide the number of samples by the number of samples per second,
|
1900 |
-
# we get the duration in seconds
|
1901 |
-
duration = audio.shape[0] / sampling_rate
|
1902 |
-
|
1903 |
-
# Check if vad is enabled, and collect voiced segments
|
1904 |
-
if vad_filter_enabled:
|
1905 |
-
# get chunks of audio that contain speech
|
1906 |
-
speech_chunks = get_speech_timestamps(audio, vad_params)
|
1907 |
-
# merge chunks of audio that contain speech into a single array
|
1908 |
-
audio = collect_chunks(audio, speech_chunks)
|
1909 |
-
|
1910 |
-
# calculate new duration of audio without silence
|
1911 |
-
duration_vad = audio.shape[0] / sampling_rate
|
1912 |
-
|
1913 |
-
logging.debug(
|
1914 |
-
f"Lang ID: VAD filter removed {duration - duration_vad} sec of audio"
|
1915 |
-
)
|
1916 |
-
|
1917 |
-
# if the audio after VAD is less than 2% of the original audio, consider it as silence
|
1918 |
-
if duration_vad / duration < speech_percentage_threshold:
|
1919 |
-
return {"language_code": None, "language_confidence": 1.0}
|
1920 |
-
|
1921 |
-
# update duration to be the duration after VAD
|
1922 |
-
duration = duration_vad
|
1923 |
-
|
1924 |
-
# if the duration of the audio is less than 1 second, consider it as silence
|
1925 |
-
if duration < 1.0:
|
1926 |
-
return {"language_code": None, "language_confidence": 1.0}
|
1927 |
-
|
1928 |
-
# number of feature frames in 30 seconds of audio is 3000
|
1929 |
-
nb_max_frames = self.feature_extractor.nb_max_frames
|
1930 |
-
|
1931 |
-
# extract features from audio with padding (default)
|
1932 |
-
to_cpu = self.model.device == "cuda" and len(self.model.device_index) > 1
|
1933 |
-
features = self.feature_extractor(audio, to_cpu=to_cpu)
|
1934 |
-
|
1935 |
-
# number of segments in the audio
|
1936 |
-
num_segments = features.shape[-1] // nb_max_frames
|
1937 |
-
# more number of segments than possible with the duration of file
|
1938 |
-
if num_detection_segments > num_segments:
|
1939 |
-
logging.warning(
|
1940 |
-
f"Lang ID: Can not have more segments, setting {num_segments} segments."
|
1941 |
-
)
|
1942 |
-
num_detection_segments = num_segments
|
1943 |
-
|
1944 |
-
# create a list of indices to randomly select segments from
|
1945 |
-
indices = list(range(num_detection_segments))
|
1946 |
-
|
1947 |
-
# fix seed to get deterministic results
|
1948 |
-
random.seed(0)
|
1949 |
-
random.shuffle(indices)
|
1950 |
-
|
1951 |
-
detected_languages = []
|
1952 |
-
all_language_probabilities = defaultdict(list)
|
1953 |
-
confident_language_probabilities = defaultdict(list)
|
1954 |
-
num_confident_segments_per_language = defaultdict(int)
|
1955 |
-
|
1956 |
-
# Iterate over the randomly selected indices of the segments.
|
1957 |
-
#
|
1958 |
-
# For each segment, extract features and detect language.
|
1959 |
-
#
|
1960 |
-
# If the language is confident, add it to the list of confident segments for that language.
|
1961 |
-
#
|
1962 |
-
# If the number of confident segments for a language
|
1963 |
-
# is greater than or equal to the number of detection segments,
|
1964 |
-
# return the language and the average probability of the language.
|
1965 |
-
#
|
1966 |
-
# If we are unable to get sufficient number of confident predcitions,
|
1967 |
-
# return the most frequently detected language with maximum probability.
|
1968 |
-
#
|
1969 |
-
# We need to get sufficient number of confident predictions per language, not in total.
|
1970 |
-
|
1971 |
-
for i in indices:
|
1972 |
-
segment_features = features[:, i * nb_max_frames : (i + 1) * nb_max_frames]
|
1973 |
-
try:
|
1974 |
-
encoder_output = self.encode(segment_features)
|
1975 |
-
results = self.model.detect_language(encoder_output)[0]
|
1976 |
-
|
1977 |
-
except ValueError as e: # or RuntimeError
|
1978 |
-
logging.error(f"Inference error:{e}")
|
1979 |
-
|
1980 |
-
# results is the list of classes (languages) and their probabilities (descending),
|
1981 |
-
# for eg: [('<|de|>', 0.482177734375),('<|en|>', 0.283447265625),...]
|
1982 |
-
|
1983 |
-
# take top language token and probability
|
1984 |
-
# and parse language token to strip out markers
|
1985 |
-
# for eg: '<|de|>' -> 'de'
|
1986 |
-
|
1987 |
-
language_token = results[0][0]
|
1988 |
-
language = language_token[2:-2]
|
1989 |
-
|
1990 |
-
language_probability = results[0][1]
|
1991 |
-
|
1992 |
-
detected_languages.append(language)
|
1993 |
-
all_language_probabilities[language].append(language_probability)
|
1994 |
-
|
1995 |
-
# only consider if the language prediction is confident
|
1996 |
-
if language_probability > language_threshold:
|
1997 |
-
num_confident_segments_per_language[language] += 1
|
1998 |
-
|
1999 |
-
# Add language and probability to the list of languages when it is confident
|
2000 |
-
confident_language_probabilities[language].append(language_probability)
|
2001 |
-
|
2002 |
-
# return the language when sufficient number of confident segments is achieved
|
2003 |
-
if (
|
2004 |
-
num_confident_segments_per_language[language]
|
2005 |
-
>= num_detection_segments
|
2006 |
-
):
|
2007 |
-
# Considering the average probability of only confident segments
|
2008 |
-
mean = sum(confident_language_probabilities[language]) / len(
|
2009 |
-
confident_language_probabilities[language]
|
2010 |
-
)
|
2011 |
-
return {
|
2012 |
-
"language_code": language,
|
2013 |
-
"language_confidence": mean,
|
2014 |
-
}
|
2015 |
-
|
2016 |
-
# if we are unable to get sufficient number of confident predictions,
|
2017 |
-
# return the most frequently detected language.
|
2018 |
-
# if there is a tie, return the one with maximum average probability.
|
2019 |
-
counter = Counter(detected_languages)
|
2020 |
-
|
2021 |
-
# Define the key function to select frequent language with attached probabilities
|
2022 |
-
def key_func(language):
|
2023 |
-
# Calculate the frequency of the language
|
2024 |
-
frequency = counter[language]
|
2025 |
-
|
2026 |
-
# Calculate the average probability of the language
|
2027 |
-
prob_avg = sum(all_language_probabilities[language]) / len(
|
2028 |
-
all_language_probabilities[language]
|
2029 |
-
)
|
2030 |
-
|
2031 |
-
return frequency, prob_avg
|
2032 |
-
|
2033 |
-
if detected_languages:
|
2034 |
-
# Use the key function to find the language with maximum frequency and probability
|
2035 |
-
max_language = max(detected_languages, key=key_func)
|
2036 |
-
max_probability = sum(all_language_probabilities[max_language]) / len(
|
2037 |
-
all_language_probabilities[max_language]
|
2038 |
-
)
|
2039 |
-
|
2040 |
-
# Do additional checks for silence for non-confident case
|
2041 |
-
# calculate RMS amplitude and DC offset
|
2042 |
-
dc_offset = audio.mean()
|
2043 |
-
audio_minus_dc_offset = audio - dc_offset
|
2044 |
-
is_silent = (
|
2045 |
-
torch.all(audio.abs() < 0.01)
|
2046 |
-
or torch.sqrt(torch.mean(audio_minus_dc_offset**2)) < 0.01
|
2047 |
-
)
|
2048 |
-
|
2049 |
-
if is_silent:
|
2050 |
-
return {"language_code": None, "language_confidence": 1.0}
|
2051 |
-
|
2052 |
-
return {
|
2053 |
-
"language_code": max_language,
|
2054 |
-
"language_confidence": max_probability,
|
2055 |
-
}
|
2056 |
-
|
2057 |
-
# Language is not detected for any segment and none of prev conditions met
|
2058 |
-
return {"language_code": None, "language_confidence": 1.0}
|
2059 |
-
|
2060 |
-
|
2061 |
-
def restore_speech_timestamps(
|
2062 |
-
segments: Iterable[Segment],
|
2063 |
-
speech_chunks: List[dict],
|
2064 |
-
sampling_rate: int,
|
2065 |
-
) -> Iterable[Segment]:
|
2066 |
-
ts_map = SpeechTimestampsMap(speech_chunks, sampling_rate)
|
2067 |
-
|
2068 |
-
for segment in segments:
|
2069 |
-
if segment.words:
|
2070 |
-
words = []
|
2071 |
-
for word in segment.words:
|
2072 |
-
# Ensure the word start and end times are resolved to the same chunk.
|
2073 |
-
middle = (word.start + word.end) / 2
|
2074 |
-
chunk_index = ts_map.get_chunk_index(middle)
|
2075 |
-
word = word._replace(
|
2076 |
-
start=ts_map.get_original_time(word.start, chunk_index),
|
2077 |
-
end=ts_map.get_original_time(word.end, chunk_index),
|
2078 |
-
)
|
2079 |
-
words.append(word)
|
2080 |
-
|
2081 |
-
segment = segment._replace(
|
2082 |
-
start=words[0].start,
|
2083 |
-
end=words[-1].end,
|
2084 |
-
words=words,
|
2085 |
-
)
|
2086 |
-
|
2087 |
-
else:
|
2088 |
-
segment = segment._replace(
|
2089 |
-
start=ts_map.get_original_time(segment.start),
|
2090 |
-
end=ts_map.get_original_time(segment.end),
|
2091 |
-
)
|
2092 |
-
|
2093 |
-
yield segment
|
2094 |
-
|
2095 |
-
|
2096 |
-
def get_ctranslate2_storage(segment: torch.Tensor) -> ctranslate2.StorageView:
|
2097 |
-
segment = segment.contiguous()
|
2098 |
-
segment = ctranslate2.StorageView.from_array(
|
2099 |
-
segment if segment.is_cuda else segment.numpy()
|
2100 |
-
) # torch cpu tensors don't implement __array_interface__
|
2101 |
-
# https://github.com/pytorch/pytorch/issues/51156
|
2102 |
-
return segment
|
2103 |
-
|
2104 |
-
|
2105 |
-
def get_compression_ratio(text: str) -> float:
|
2106 |
-
text_bytes = text.encode("utf-8")
|
2107 |
-
return len(text_bytes) / len(zlib.compress(text_bytes))
|
2108 |
-
|
2109 |
-
|
2110 |
-
def get_suppressed_tokens(
|
2111 |
-
tokenizer: Tokenizer,
|
2112 |
-
suppress_tokens: Tuple[int],
|
2113 |
-
) -> Optional[List[int]]:
|
2114 |
-
if -1 in suppress_tokens:
|
2115 |
-
suppress_tokens = [t for t in suppress_tokens if t >= 0]
|
2116 |
-
suppress_tokens.extend(tokenizer.non_speech_tokens)
|
2117 |
-
elif suppress_tokens is None or len(suppress_tokens) == 0:
|
2118 |
-
suppress_tokens = [] # interpret empty string as an empty list
|
2119 |
-
else:
|
2120 |
-
assert isinstance(suppress_tokens, list), "suppress_tokens must be a list"
|
2121 |
-
|
2122 |
-
suppress_tokens.extend(
|
2123 |
-
[
|
2124 |
-
tokenizer.transcribe,
|
2125 |
-
tokenizer.translate,
|
2126 |
-
tokenizer.sot,
|
2127 |
-
tokenizer.sot_prev,
|
2128 |
-
tokenizer.sot_lm,
|
2129 |
-
]
|
2130 |
-
)
|
2131 |
-
|
2132 |
-
return tuple(sorted(set(suppress_tokens)))
|
2133 |
-
|
2134 |
-
|
2135 |
-
def merge_punctuations(alignment: List[dict], prepended: str, appended: str) -> None:
|
2136 |
-
# merge prepended punctuations
|
2137 |
-
i = len(alignment) - 2
|
2138 |
-
j = len(alignment) - 1
|
2139 |
-
while i >= 0:
|
2140 |
-
previous = alignment[i]
|
2141 |
-
following = alignment[j]
|
2142 |
-
if previous["word"].startswith(" ") and previous["word"].strip() in prepended:
|
2143 |
-
# prepend it to the following word
|
2144 |
-
following["word"] = previous["word"] + following["word"]
|
2145 |
-
if "tokens" in alignment[0].keys():
|
2146 |
-
following["tokens"] = previous["tokens"] + following["tokens"]
|
2147 |
-
previous["tokens"] = []
|
2148 |
-
previous["word"] = ""
|
2149 |
-
|
2150 |
-
else:
|
2151 |
-
j = i
|
2152 |
-
i -= 1
|
2153 |
-
|
2154 |
-
# merge appended punctuations
|
2155 |
-
i = 0
|
2156 |
-
j = 1
|
2157 |
-
while j < len(alignment):
|
2158 |
-
previous = alignment[i]
|
2159 |
-
following = alignment[j]
|
2160 |
-
if not previous["word"].endswith(" ") and following["word"] in appended:
|
2161 |
-
# append it to the previous word
|
2162 |
-
previous["word"] = previous["word"] + following["word"]
|
2163 |
-
if "tokens" in alignment[0].keys():
|
2164 |
-
previous["tokens"] = previous["tokens"] + following["tokens"]
|
2165 |
-
following["tokens"] = []
|
2166 |
-
following["word"] = ""
|
2167 |
-
|
2168 |
-
else:
|
2169 |
-
i = j
|
2170 |
-
j += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/utils.py
DELETED
@@ -1,157 +0,0 @@
|
|
1 |
-
import logging
|
2 |
-
import os
|
3 |
-
import re
|
4 |
-
|
5 |
-
from typing import List, Optional
|
6 |
-
|
7 |
-
import huggingface_hub
|
8 |
-
import requests
|
9 |
-
|
10 |
-
from tqdm.auto import tqdm
|
11 |
-
|
12 |
-
_MODELS = {
|
13 |
-
"tiny.en": "Systran/faster-whisper-tiny.en",
|
14 |
-
"tiny": "Systran/faster-whisper-tiny",
|
15 |
-
"base.en": "Systran/faster-whisper-base.en",
|
16 |
-
"base": "Systran/faster-whisper-base",
|
17 |
-
"small.en": "Systran/faster-whisper-small.en",
|
18 |
-
"small": "Systran/faster-whisper-small",
|
19 |
-
"medium.en": "Systran/faster-whisper-medium.en",
|
20 |
-
"medium": "Systran/faster-whisper-medium",
|
21 |
-
"large-v1": "Systran/faster-whisper-large-v1",
|
22 |
-
"large-v2": "Systran/faster-whisper-large-v2",
|
23 |
-
"large-v3": "Systran/faster-whisper-large-v3",
|
24 |
-
"large": "Systran/faster-whisper-large-v3",
|
25 |
-
"distil-large-v2": "Systran/faster-distil-whisper-large-v2",
|
26 |
-
"distil-medium.en": "Systran/faster-distil-whisper-medium.en",
|
27 |
-
"distil-small.en": "Systran/faster-distil-whisper-small.en",
|
28 |
-
"distil-large-v3": "Systran/faster-distil-whisper-large-v3",
|
29 |
-
}
|
30 |
-
|
31 |
-
|
32 |
-
def available_models() -> List[str]:
|
33 |
-
"""Returns the names of available models."""
|
34 |
-
return list(_MODELS.keys())
|
35 |
-
|
36 |
-
|
37 |
-
def get_assets_path():
|
38 |
-
"""Returns the path to the assets directory."""
|
39 |
-
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets")
|
40 |
-
|
41 |
-
|
42 |
-
def get_logger():
|
43 |
-
"""Returns the module logger."""
|
44 |
-
return logging.getLogger("faster_whisper")
|
45 |
-
|
46 |
-
|
47 |
-
def download_model(
|
48 |
-
size_or_id: str,
|
49 |
-
output_dir: Optional[str] = None,
|
50 |
-
local_files_only: bool = False,
|
51 |
-
cache_dir: Optional[str] = None,
|
52 |
-
):
|
53 |
-
"""Downloads a CTranslate2 Whisper model from the Hugging Face Hub.
|
54 |
-
|
55 |
-
Args:
|
56 |
-
size_or_id: Size of the model to download from https://huggingface.co/Systran
|
57 |
-
(tiny, tiny.en, base, base.en, small, small.en, distil-small.en, medium, medium.en,
|
58 |
-
distil-medium.en, large-v1, large-v2, large-v3, large, distil-large-v2,
|
59 |
-
distil-large-v3), or a CTranslate2-converted model ID from the Hugging Face Hub
|
60 |
-
(e.g. Systran/faster-whisper-large-v3).
|
61 |
-
output_dir: Directory where the model should be saved. If not set, the model is saved in
|
62 |
-
the cache directory.
|
63 |
-
local_files_only: If True, avoid downloading the file and return the path to the local
|
64 |
-
cached file if it exists.
|
65 |
-
cache_dir: Path to the folder where cached files are stored.
|
66 |
-
|
67 |
-
Returns:
|
68 |
-
The path to the downloaded model.
|
69 |
-
|
70 |
-
Raises:
|
71 |
-
ValueError: if the model size is invalid.
|
72 |
-
"""
|
73 |
-
if re.match(r".*/.*", size_or_id):
|
74 |
-
repo_id = size_or_id
|
75 |
-
else:
|
76 |
-
repo_id = _MODELS.get(size_or_id)
|
77 |
-
if repo_id is None:
|
78 |
-
raise ValueError(
|
79 |
-
"Invalid model size '%s', expected one of: %s"
|
80 |
-
% (size_or_id, ", ".join(_MODELS.keys()))
|
81 |
-
)
|
82 |
-
|
83 |
-
allow_patterns = [
|
84 |
-
"config.json",
|
85 |
-
"preprocessor_config.json",
|
86 |
-
"model.bin",
|
87 |
-
"tokenizer.json",
|
88 |
-
"vocabulary.*",
|
89 |
-
]
|
90 |
-
|
91 |
-
kwargs = {
|
92 |
-
"local_files_only": local_files_only,
|
93 |
-
"allow_patterns": allow_patterns,
|
94 |
-
"tqdm_class": disabled_tqdm,
|
95 |
-
}
|
96 |
-
|
97 |
-
if output_dir is not None:
|
98 |
-
kwargs["local_dir"] = output_dir
|
99 |
-
kwargs["local_dir_use_symlinks"] = False
|
100 |
-
|
101 |
-
if cache_dir is not None:
|
102 |
-
kwargs["cache_dir"] = cache_dir
|
103 |
-
|
104 |
-
try:
|
105 |
-
return huggingface_hub.snapshot_download(repo_id, **kwargs)
|
106 |
-
except (
|
107 |
-
huggingface_hub.utils.HfHubHTTPError,
|
108 |
-
requests.exceptions.ConnectionError,
|
109 |
-
) as exception:
|
110 |
-
logger = get_logger()
|
111 |
-
logger.warning(
|
112 |
-
"An error occured while synchronizing the model %s from the Hugging Face Hub:\n%s",
|
113 |
-
repo_id,
|
114 |
-
exception,
|
115 |
-
)
|
116 |
-
logger.warning(
|
117 |
-
"Trying to load the model directly from the local cache, if it exists."
|
118 |
-
)
|
119 |
-
|
120 |
-
kwargs["local_files_only"] = True
|
121 |
-
return huggingface_hub.snapshot_download(repo_id, **kwargs)
|
122 |
-
|
123 |
-
|
124 |
-
def format_timestamp(
|
125 |
-
seconds: float,
|
126 |
-
always_include_hours: bool = False,
|
127 |
-
decimal_marker: str = ".",
|
128 |
-
) -> str:
|
129 |
-
assert seconds >= 0, "non-negative timestamp expected"
|
130 |
-
milliseconds = round(seconds * 1000.0)
|
131 |
-
|
132 |
-
hours = milliseconds // 3_600_000
|
133 |
-
milliseconds -= hours * 3_600_000
|
134 |
-
|
135 |
-
minutes = milliseconds // 60_000
|
136 |
-
milliseconds -= minutes * 60_000
|
137 |
-
|
138 |
-
seconds = milliseconds // 1_000
|
139 |
-
milliseconds -= seconds * 1_000
|
140 |
-
|
141 |
-
hours_marker = f"{hours:02d}:" if always_include_hours or hours > 0 else ""
|
142 |
-
return (
|
143 |
-
f"{hours_marker}{minutes:02d}:{seconds:02d}{decimal_marker}{milliseconds:03d}"
|
144 |
-
)
|
145 |
-
|
146 |
-
|
147 |
-
class disabled_tqdm(tqdm):
|
148 |
-
def __init__(self, *args, **kwargs):
|
149 |
-
kwargs["disable"] = True
|
150 |
-
super().__init__(*args, **kwargs)
|
151 |
-
|
152 |
-
|
153 |
-
def get_end(segments: List[dict]) -> Optional[float]:
|
154 |
-
return next(
|
155 |
-
(w["end"] for s in reversed(segments) for w in reversed(s["words"])),
|
156 |
-
segments[-1]["end"] if segments else None,
|
157 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/vad.py
DELETED
@@ -1,596 +0,0 @@
|
|
1 |
-
import bisect
|
2 |
-
import functools
|
3 |
-
import os
|
4 |
-
|
5 |
-
from abc import ABC
|
6 |
-
from collections.abc import Callable
|
7 |
-
from typing import List, NamedTuple, Optional, Union
|
8 |
-
|
9 |
-
import numpy as np
|
10 |
-
import torch
|
11 |
-
|
12 |
-
from pyannote.audio.core.io import AudioFile
|
13 |
-
from pyannote.audio.pipelines import VoiceActivityDetection
|
14 |
-
from pyannote.audio.pipelines.utils import PipelineModel
|
15 |
-
from pyannote.core import Annotation, Segment, SlidingWindowFeature
|
16 |
-
|
17 |
-
from faster_whisper.utils import get_assets_path
|
18 |
-
|
19 |
-
|
20 |
-
# The code below is adapted from https://github.com/snakers4/silero-vad.
|
21 |
-
class VadOptions(NamedTuple):
|
22 |
-
"""VAD options.
|
23 |
-
|
24 |
-
Attributes:
|
25 |
-
threshold: Speech threshold. Silero VAD outputs speech probabilities for each audio chunk,
|
26 |
-
probabilities ABOVE this value are considered as SPEECH. It is better to tune this
|
27 |
-
parameter for each dataset separately, but "lazy" 0.5 is pretty good for most datasets.
|
28 |
-
min_speech_duration_ms: Final speech chunks shorter min_speech_duration_ms are thrown out.
|
29 |
-
max_speech_duration_s: Maximum duration of speech chunks in seconds. Chunks longer
|
30 |
-
than max_speech_duration_s will be split at the timestamp of the last silence that
|
31 |
-
lasts more than 100ms (if any), to prevent aggressive cutting. Otherwise, they will be
|
32 |
-
split aggressively just before max_speech_duration_s.
|
33 |
-
min_silence_duration_ms: In the end of each speech chunk wait for min_silence_duration_ms
|
34 |
-
before separating it
|
35 |
-
speech_pad_ms: Final speech chunks are padded by speech_pad_ms each side
|
36 |
-
"""
|
37 |
-
|
38 |
-
threshold: float = 0.5
|
39 |
-
min_speech_duration_ms: int = 250
|
40 |
-
max_speech_duration_s: float = float("inf")
|
41 |
-
min_silence_duration_ms: int = 2000
|
42 |
-
speech_pad_ms: int = 400
|
43 |
-
|
44 |
-
|
45 |
-
def get_speech_timestamps(
|
46 |
-
audio: torch.Tensor,
|
47 |
-
vad_options: Optional[VadOptions] = None,
|
48 |
-
**kwargs,
|
49 |
-
) -> List[dict]:
|
50 |
-
"""This method is used for splitting long audios into speech chunks using silero VAD.
|
51 |
-
|
52 |
-
Args:
|
53 |
-
audio: One dimensional float array.
|
54 |
-
vad_options: Options for VAD processing.
|
55 |
-
kwargs: VAD options passed as keyword arguments for backward compatibility.
|
56 |
-
|
57 |
-
Returns:
|
58 |
-
List of dicts containing begin and end samples of each speech chunk.
|
59 |
-
"""
|
60 |
-
if vad_options is None:
|
61 |
-
vad_options = VadOptions(**kwargs)
|
62 |
-
|
63 |
-
threshold = vad_options.threshold
|
64 |
-
min_speech_duration_ms = vad_options.min_speech_duration_ms
|
65 |
-
max_speech_duration_s = vad_options.max_speech_duration_s
|
66 |
-
min_silence_duration_ms = vad_options.min_silence_duration_ms
|
67 |
-
window_size_samples = 512
|
68 |
-
speech_pad_ms = vad_options.speech_pad_ms
|
69 |
-
sampling_rate = 16000
|
70 |
-
min_speech_samples = sampling_rate * min_speech_duration_ms / 1000
|
71 |
-
speech_pad_samples = sampling_rate * speech_pad_ms / 1000
|
72 |
-
max_speech_samples = (
|
73 |
-
sampling_rate * max_speech_duration_s
|
74 |
-
- window_size_samples
|
75 |
-
- 2 * speech_pad_samples
|
76 |
-
)
|
77 |
-
min_silence_samples = sampling_rate * min_silence_duration_ms / 1000
|
78 |
-
min_silence_samples_at_max_speech = sampling_rate * 98 / 1000
|
79 |
-
|
80 |
-
audio_length_samples = len(audio)
|
81 |
-
|
82 |
-
model = get_vad_model()
|
83 |
-
state, context = model.get_initial_states(batch_size=1)
|
84 |
-
|
85 |
-
speech_probs = []
|
86 |
-
for current_start_sample in range(0, audio_length_samples, window_size_samples):
|
87 |
-
chunk = audio[current_start_sample : current_start_sample + window_size_samples]
|
88 |
-
if len(chunk) < window_size_samples:
|
89 |
-
chunk = np.pad(chunk, (0, int(window_size_samples - len(chunk))))
|
90 |
-
speech_prob, state, context = model(chunk, state, context, sampling_rate)
|
91 |
-
speech_probs.append(speech_prob)
|
92 |
-
|
93 |
-
triggered = False
|
94 |
-
speeches = []
|
95 |
-
current_speech = {}
|
96 |
-
neg_threshold = threshold - 0.15
|
97 |
-
|
98 |
-
# to save potential segment end (and tolerate some silence)
|
99 |
-
temp_end = 0
|
100 |
-
# to save potential segment limits in case of maximum segment size reached
|
101 |
-
prev_end = next_start = 0
|
102 |
-
|
103 |
-
for i, speech_prob in enumerate(speech_probs):
|
104 |
-
if (speech_prob >= threshold) and temp_end:
|
105 |
-
temp_end = 0
|
106 |
-
if next_start < prev_end:
|
107 |
-
next_start = window_size_samples * i
|
108 |
-
|
109 |
-
if (speech_prob >= threshold) and not triggered:
|
110 |
-
triggered = True
|
111 |
-
current_speech["start"] = window_size_samples * i
|
112 |
-
continue
|
113 |
-
|
114 |
-
if (
|
115 |
-
triggered
|
116 |
-
and (window_size_samples * i) - current_speech["start"] > max_speech_samples
|
117 |
-
):
|
118 |
-
if prev_end:
|
119 |
-
current_speech["end"] = prev_end
|
120 |
-
speeches.append(current_speech)
|
121 |
-
current_speech = {}
|
122 |
-
# previously reached silence (< neg_thres) and is still not speech (< thres)
|
123 |
-
if next_start < prev_end:
|
124 |
-
triggered = False
|
125 |
-
else:
|
126 |
-
current_speech["start"] = next_start
|
127 |
-
prev_end = next_start = temp_end = 0
|
128 |
-
else:
|
129 |
-
current_speech["end"] = window_size_samples * i
|
130 |
-
speeches.append(current_speech)
|
131 |
-
current_speech = {}
|
132 |
-
prev_end = next_start = temp_end = 0
|
133 |
-
triggered = False
|
134 |
-
continue
|
135 |
-
|
136 |
-
if (speech_prob < neg_threshold) and triggered:
|
137 |
-
if not temp_end:
|
138 |
-
temp_end = window_size_samples * i
|
139 |
-
# condition to avoid cutting in very short silence
|
140 |
-
if (window_size_samples * i) - temp_end > min_silence_samples_at_max_speech:
|
141 |
-
prev_end = temp_end
|
142 |
-
if (window_size_samples * i) - temp_end < min_silence_samples:
|
143 |
-
continue
|
144 |
-
else:
|
145 |
-
current_speech["end"] = temp_end
|
146 |
-
if (
|
147 |
-
current_speech["end"] - current_speech["start"]
|
148 |
-
) > min_speech_samples:
|
149 |
-
speeches.append(current_speech)
|
150 |
-
current_speech = {}
|
151 |
-
prev_end = next_start = temp_end = 0
|
152 |
-
triggered = False
|
153 |
-
continue
|
154 |
-
|
155 |
-
if (
|
156 |
-
current_speech
|
157 |
-
and (audio_length_samples - current_speech["start"]) > min_speech_samples
|
158 |
-
):
|
159 |
-
current_speech["end"] = audio_length_samples
|
160 |
-
speeches.append(current_speech)
|
161 |
-
|
162 |
-
for i, speech in enumerate(speeches):
|
163 |
-
if i == 0:
|
164 |
-
speech["start"] = int(max(0, speech["start"] - speech_pad_samples))
|
165 |
-
if i != len(speeches) - 1:
|
166 |
-
silence_duration = speeches[i + 1]["start"] - speech["end"]
|
167 |
-
if silence_duration < 2 * speech_pad_samples:
|
168 |
-
speech["end"] += int(silence_duration // 2)
|
169 |
-
speeches[i + 1]["start"] = int(
|
170 |
-
max(0, speeches[i + 1]["start"] - silence_duration // 2)
|
171 |
-
)
|
172 |
-
else:
|
173 |
-
speech["end"] = int(
|
174 |
-
min(audio_length_samples, speech["end"] + speech_pad_samples)
|
175 |
-
)
|
176 |
-
speeches[i + 1]["start"] = int(
|
177 |
-
max(0, speeches[i + 1]["start"] - speech_pad_samples)
|
178 |
-
)
|
179 |
-
else:
|
180 |
-
speech["end"] = int(
|
181 |
-
min(audio_length_samples, speech["end"] + speech_pad_samples)
|
182 |
-
)
|
183 |
-
|
184 |
-
return speeches
|
185 |
-
|
186 |
-
|
187 |
-
def collect_chunks(audio: torch.Tensor, chunks: List[dict]) -> torch.Tensor:
|
188 |
-
"""Collects and concatenates audio chunks."""
|
189 |
-
if not chunks:
|
190 |
-
return torch.tensor([], dtype=torch.float32)
|
191 |
-
|
192 |
-
return torch.cat([audio[chunk["start"] : chunk["end"]] for chunk in chunks])
|
193 |
-
|
194 |
-
|
195 |
-
class SpeechTimestampsMap:
|
196 |
-
"""Helper class to restore original speech timestamps."""
|
197 |
-
|
198 |
-
def __init__(self, chunks: List[dict], sampling_rate: int, time_precision: int = 2):
|
199 |
-
self.sampling_rate = sampling_rate
|
200 |
-
self.time_precision = time_precision
|
201 |
-
self.chunk_end_sample = []
|
202 |
-
self.total_silence_before = []
|
203 |
-
|
204 |
-
previous_end = 0
|
205 |
-
silent_samples = 0
|
206 |
-
|
207 |
-
for chunk in chunks:
|
208 |
-
silent_samples += chunk["start"] - previous_end
|
209 |
-
previous_end = chunk["end"]
|
210 |
-
|
211 |
-
self.chunk_end_sample.append(chunk["end"] - silent_samples)
|
212 |
-
self.total_silence_before.append(silent_samples / sampling_rate)
|
213 |
-
|
214 |
-
def get_original_time(
|
215 |
-
self,
|
216 |
-
time: float,
|
217 |
-
chunk_index: Optional[int] = None,
|
218 |
-
) -> float:
|
219 |
-
if chunk_index is None:
|
220 |
-
chunk_index = self.get_chunk_index(time)
|
221 |
-
|
222 |
-
total_silence_before = self.total_silence_before[chunk_index]
|
223 |
-
return round(total_silence_before + time, self.time_precision)
|
224 |
-
|
225 |
-
def get_chunk_index(self, time: float) -> int:
|
226 |
-
sample = int(time * self.sampling_rate)
|
227 |
-
return min(
|
228 |
-
bisect.bisect(self.chunk_end_sample, sample),
|
229 |
-
len(self.chunk_end_sample) - 1,
|
230 |
-
)
|
231 |
-
|
232 |
-
|
233 |
-
@functools.lru_cache
|
234 |
-
def get_vad_model():
|
235 |
-
"""Returns the VAD model instance."""
|
236 |
-
path = os.path.join(get_assets_path(), "silero_vad.onnx")
|
237 |
-
return SileroVADModel(path)
|
238 |
-
|
239 |
-
|
240 |
-
class SileroVADModel:
|
241 |
-
def __init__(self, path):
|
242 |
-
try:
|
243 |
-
import onnxruntime
|
244 |
-
except ImportError as e:
|
245 |
-
raise RuntimeError(
|
246 |
-
"Applying the VAD filter requires the onnxruntime package"
|
247 |
-
) from e
|
248 |
-
|
249 |
-
opts = onnxruntime.SessionOptions()
|
250 |
-
opts.inter_op_num_threads = 1
|
251 |
-
opts.intra_op_num_threads = 1
|
252 |
-
opts.log_severity_level = 4
|
253 |
-
|
254 |
-
self.session = onnxruntime.InferenceSession(
|
255 |
-
path,
|
256 |
-
providers=["CPUExecutionProvider"],
|
257 |
-
sess_options=opts,
|
258 |
-
)
|
259 |
-
|
260 |
-
def get_initial_states(self, batch_size: int):
|
261 |
-
state = np.zeros((2, batch_size, 128), dtype=np.float32)
|
262 |
-
context = np.zeros((batch_size, 64), dtype=np.float32)
|
263 |
-
return state, context
|
264 |
-
|
265 |
-
def __call__(self, x, state, context, sr: int):
|
266 |
-
if len(x.shape) == 1:
|
267 |
-
x = np.expand_dims(x, 0)
|
268 |
-
if len(x.shape) > 2:
|
269 |
-
raise ValueError(
|
270 |
-
f"Too many dimensions for input audio chunk {len(x.shape)}"
|
271 |
-
)
|
272 |
-
if sr / x.shape[1] > 31.25:
|
273 |
-
raise ValueError("Input audio chunk is too short")
|
274 |
-
|
275 |
-
x = np.concatenate([context, x], axis=1)
|
276 |
-
|
277 |
-
ort_inputs = {
|
278 |
-
"input": x,
|
279 |
-
"state": state,
|
280 |
-
"sr": np.array(sr, dtype="int64"),
|
281 |
-
}
|
282 |
-
|
283 |
-
out, state = self.session.run(None, ort_inputs)
|
284 |
-
context = x[..., -64:]
|
285 |
-
|
286 |
-
return out, state, context
|
287 |
-
|
288 |
-
|
289 |
-
# BSD 2-Clause License
|
290 |
-
|
291 |
-
# Copyright (c) 2024, Max Bain
|
292 |
-
|
293 |
-
# Redistribution and use in source and binary forms, with or without
|
294 |
-
# modification, are permitted provided that the following conditions are met:
|
295 |
-
|
296 |
-
# 1. Redistributions of source code must retain the above copyright notice, this
|
297 |
-
# list of conditions and the following disclaimer.
|
298 |
-
|
299 |
-
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
300 |
-
# this list of conditions and the following disclaimer in the documentation
|
301 |
-
# and/or other materials provided with the distribution.
|
302 |
-
|
303 |
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
304 |
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
305 |
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
306 |
-
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
307 |
-
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
308 |
-
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
309 |
-
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
310 |
-
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
311 |
-
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
312 |
-
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
313 |
-
|
314 |
-
|
315 |
-
# The code below is copied from whisper-x (https://github.com/m-bain/whisperX)
|
316 |
-
# and adapted for faster_whisper.
|
317 |
-
class SegmentX:
|
318 |
-
def __init__(self, start, end, speaker=None):
|
319 |
-
self.start = start
|
320 |
-
self.end = end
|
321 |
-
self.speaker = speaker
|
322 |
-
|
323 |
-
|
324 |
-
class VoiceActivitySegmentation(VoiceActivityDetection, ABC):
|
325 |
-
"""Pipeline wrapper class for Voice Activity Segmentation based on VAD scores."""
|
326 |
-
|
327 |
-
def __init__(
|
328 |
-
self,
|
329 |
-
segmentation: PipelineModel = "pyannote/segmentation",
|
330 |
-
device: Optional[Union[str, torch.device]] = None,
|
331 |
-
fscore: bool = False,
|
332 |
-
use_auth_token: Optional[str] = None,
|
333 |
-
**inference_kwargs,
|
334 |
-
):
|
335 |
-
"""Initialize the pipeline with the model name and the optional device.
|
336 |
-
|
337 |
-
Args:
|
338 |
-
dict parameters of VoiceActivityDetection class from pyannote:
|
339 |
-
segmentation (PipelineModel): Loaded model name.
|
340 |
-
device (torch.device or None): Device to perform the segmentation.
|
341 |
-
fscore (bool): Flag indicating whether to compute F-score during inference.
|
342 |
-
use_auth_token (str or None): Optional authentication token for model access.
|
343 |
-
inference_kwargs (dict): Additional arguments from VoiceActivityDetection pipeline.
|
344 |
-
"""
|
345 |
-
super().__init__(
|
346 |
-
segmentation=segmentation,
|
347 |
-
device=device,
|
348 |
-
fscore=fscore,
|
349 |
-
use_auth_token=use_auth_token,
|
350 |
-
**inference_kwargs,
|
351 |
-
)
|
352 |
-
|
353 |
-
def apply(
|
354 |
-
self, file: AudioFile, hook: Optional[Callable] = None
|
355 |
-
) -> SlidingWindowFeature:
|
356 |
-
"""Apply voice activity detection on the audio file.
|
357 |
-
|
358 |
-
Args:
|
359 |
-
file (AudioFile): Processed file.
|
360 |
-
hook (callable): Hook called with signature: hook("step_name", step_artefact, file=file)
|
361 |
-
|
362 |
-
Returns:
|
363 |
-
segmentations (SlidingWindowFeature): Voice activity segmentation.
|
364 |
-
"""
|
365 |
-
# setup hook (e.g. for debugging purposes)
|
366 |
-
hook = self.setup_hook(file, hook=hook)
|
367 |
-
|
368 |
-
# apply segmentation model if needed
|
369 |
-
# output shape is (num_chunks, num_frames, 1)
|
370 |
-
if self.training:
|
371 |
-
if self.CACHED_SEGMENTATION in file:
|
372 |
-
segmentations = file[self.CACHED_SEGMENTATION]
|
373 |
-
else:
|
374 |
-
segmentations = self._segmentation(file)
|
375 |
-
file[self.CACHED_SEGMENTATION] = segmentations
|
376 |
-
else:
|
377 |
-
segmentations: SlidingWindowFeature = self._segmentation(file)
|
378 |
-
|
379 |
-
return segmentations
|
380 |
-
|
381 |
-
|
382 |
-
class BinarizeVadScores:
|
383 |
-
"""Binarize detection scores using hysteresis thresholding.
|
384 |
-
|
385 |
-
Reference:
|
386 |
-
Gregory Gelly and Jean-Luc Gauvain. "Minimum Word Error Training of
|
387 |
-
RNN-based Voice Activity Detection", InterSpeech 2015.
|
388 |
-
|
389 |
-
Modified by Max Bain to include WhisperX's min-cut operation
|
390 |
-
https://arxiv.org/abs/2303.00747
|
391 |
-
|
392 |
-
"""
|
393 |
-
|
394 |
-
def __init__(
|
395 |
-
self,
|
396 |
-
onset: float = 0.5,
|
397 |
-
offset: Optional[float] = None,
|
398 |
-
min_duration_on: float = 0.0,
|
399 |
-
min_duration_off: float = 0.0,
|
400 |
-
pad_onset: float = 0.0,
|
401 |
-
pad_offset: float = 0.0,
|
402 |
-
max_duration: float = float("inf"),
|
403 |
-
):
|
404 |
-
"""Initializes the parameters for Binarizing the VAD scores.
|
405 |
-
|
406 |
-
Args:
|
407 |
-
onset (float, optional):
|
408 |
-
Onset threshold. Defaults to 0.5.
|
409 |
-
offset (float, optional):
|
410 |
-
Offset threshold. Defaults to `onset`.
|
411 |
-
min_duration_on (float, optional):
|
412 |
-
Remove active regions shorter than that many seconds. Defaults to 0s.
|
413 |
-
min_duration_off (float, optional):
|
414 |
-
Fill inactive regions shorter than that many seconds. Defaults to 0s.
|
415 |
-
pad_onset (float, optional):
|
416 |
-
Extend active regions by moving their start time by that many seconds.
|
417 |
-
Defaults to 0s.
|
418 |
-
pad_offset (float, optional):
|
419 |
-
Extend active regions by moving their end time by that many seconds.
|
420 |
-
Defaults to 0s.
|
421 |
-
max_duration (float):
|
422 |
-
The maximum length of an active segment.
|
423 |
-
"""
|
424 |
-
super().__init__()
|
425 |
-
|
426 |
-
self.onset = onset
|
427 |
-
self.offset = offset or onset
|
428 |
-
|
429 |
-
self.pad_onset = pad_onset
|
430 |
-
self.pad_offset = pad_offset
|
431 |
-
|
432 |
-
self.min_duration_on = min_duration_on
|
433 |
-
self.min_duration_off = min_duration_off
|
434 |
-
|
435 |
-
self.max_duration = max_duration
|
436 |
-
|
437 |
-
def __get_active_regions(self, scores: SlidingWindowFeature) -> Annotation:
|
438 |
-
"""Extract active regions from VAD scores.
|
439 |
-
|
440 |
-
Args:
|
441 |
-
scores (SlidingWindowFeature): Detection scores.
|
442 |
-
|
443 |
-
Returns:
|
444 |
-
active (Annotation): Active regions.
|
445 |
-
"""
|
446 |
-
num_frames, num_classes = scores.data.shape
|
447 |
-
frames = scores.sliding_window
|
448 |
-
timestamps = [frames[i].middle for i in range(num_frames)]
|
449 |
-
# annotation meant to store 'active' regions
|
450 |
-
active = Annotation()
|
451 |
-
for k, k_scores in enumerate(scores.data.T):
|
452 |
-
label = k if scores.labels is None else scores.labels[k]
|
453 |
-
|
454 |
-
# initial state
|
455 |
-
start = timestamps[0]
|
456 |
-
is_active = k_scores[0] > self.onset
|
457 |
-
curr_scores = [k_scores[0]]
|
458 |
-
curr_timestamps = [start]
|
459 |
-
t = start
|
460 |
-
# optionally add `strict=False` for python 3.10 or later
|
461 |
-
for t, y in zip(timestamps[1:], k_scores[1:]):
|
462 |
-
# currently active
|
463 |
-
if is_active:
|
464 |
-
curr_duration = t - start
|
465 |
-
if curr_duration > self.max_duration:
|
466 |
-
search_after = len(curr_scores) // 2
|
467 |
-
# divide segment
|
468 |
-
min_score_div_idx = search_after + np.argmin(
|
469 |
-
curr_scores[search_after:]
|
470 |
-
)
|
471 |
-
min_score_t = curr_timestamps[min_score_div_idx]
|
472 |
-
region = Segment(
|
473 |
-
start - self.pad_onset, min_score_t + self.pad_offset
|
474 |
-
)
|
475 |
-
active[region, k] = label
|
476 |
-
start = curr_timestamps[min_score_div_idx]
|
477 |
-
curr_scores = curr_scores[min_score_div_idx + 1 :]
|
478 |
-
curr_timestamps = curr_timestamps[min_score_div_idx + 1 :]
|
479 |
-
# switching from active to inactive
|
480 |
-
elif y < self.offset:
|
481 |
-
region = Segment(start - self.pad_onset, t + self.pad_offset)
|
482 |
-
active[region, k] = label
|
483 |
-
start = t
|
484 |
-
is_active = False
|
485 |
-
curr_scores = []
|
486 |
-
curr_timestamps = []
|
487 |
-
curr_scores.append(y)
|
488 |
-
curr_timestamps.append(t)
|
489 |
-
# currently inactive
|
490 |
-
else:
|
491 |
-
# switching from inactive to active
|
492 |
-
if y > self.onset:
|
493 |
-
start = t
|
494 |
-
is_active = True
|
495 |
-
|
496 |
-
# if active at the end, add final region
|
497 |
-
if is_active:
|
498 |
-
region = Segment(start - self.pad_onset, t + self.pad_offset)
|
499 |
-
active[region, k] = label
|
500 |
-
|
501 |
-
return active
|
502 |
-
|
503 |
-
def __call__(self, scores: SlidingWindowFeature) -> Annotation:
|
504 |
-
"""Binarize detection scores.
|
505 |
-
|
506 |
-
Args:
|
507 |
-
scores (SlidingWindowFeature): Detection scores.
|
508 |
-
|
509 |
-
Returns:
|
510 |
-
active (Annotation): Binarized scores.
|
511 |
-
"""
|
512 |
-
active = self.__get_active_regions(scores)
|
513 |
-
# because of padding, some active regions might be overlapping: merge them.
|
514 |
-
# also: fill same speaker gaps shorter than min_duration_off
|
515 |
-
if self.pad_offset > 0.0 or self.pad_onset > 0.0 or self.min_duration_off > 0.0:
|
516 |
-
if self.max_duration < float("inf"):
|
517 |
-
raise NotImplementedError("This would break current max_duration param")
|
518 |
-
active = active.support(collar=self.min_duration_off)
|
519 |
-
|
520 |
-
# remove tracks shorter than min_duration_on
|
521 |
-
if self.min_duration_on > 0:
|
522 |
-
for segment, track in list(active.itertracks()):
|
523 |
-
if segment.duration < self.min_duration_on:
|
524 |
-
del active[segment, track]
|
525 |
-
|
526 |
-
return active
|
527 |
-
|
528 |
-
|
529 |
-
def merge_chunks(
|
530 |
-
segments,
|
531 |
-
chunk_length,
|
532 |
-
onset: float = 0.5,
|
533 |
-
offset: Optional[float] = None,
|
534 |
-
edge_padding: float = 0.1,
|
535 |
-
):
|
536 |
-
"""
|
537 |
-
Merge operation described in whisper-x paper
|
538 |
-
"""
|
539 |
-
curr_end = 0
|
540 |
-
merged_segments = []
|
541 |
-
seg_idxs = []
|
542 |
-
speaker_idxs = []
|
543 |
-
|
544 |
-
assert chunk_length > 0
|
545 |
-
binarize = BinarizeVadScores(max_duration=chunk_length, onset=onset, offset=offset)
|
546 |
-
segments = binarize(segments)
|
547 |
-
segments_list = []
|
548 |
-
for speech_turn in segments.get_timeline():
|
549 |
-
segments_list.append(
|
550 |
-
SegmentX(
|
551 |
-
max(0.0, speech_turn.start - edge_padding),
|
552 |
-
speech_turn.end + edge_padding,
|
553 |
-
"UNKNOWN",
|
554 |
-
)
|
555 |
-
) # 100ms edge padding to account for edge errors
|
556 |
-
|
557 |
-
if len(segments_list) == 0:
|
558 |
-
print("No active speech found in audio")
|
559 |
-
return []
|
560 |
-
|
561 |
-
# Make sur the starting point is the start of the segment.
|
562 |
-
curr_start = segments_list[0].start
|
563 |
-
|
564 |
-
for idx, seg in enumerate(segments_list):
|
565 |
-
# if any segment start timing is less than previous segment end timing,
|
566 |
-
# reset the edge padding. Similarly for end timing.
|
567 |
-
if idx > 0:
|
568 |
-
if seg.start < segments_list[idx - 1].end:
|
569 |
-
seg.start += edge_padding
|
570 |
-
if idx < len(segments_list) - 1:
|
571 |
-
if seg.end > segments_list[idx + 1].start:
|
572 |
-
seg.end -= edge_padding
|
573 |
-
|
574 |
-
if seg.end - curr_start > chunk_length and curr_end - curr_start > 0:
|
575 |
-
merged_segments.append(
|
576 |
-
{
|
577 |
-
"start": curr_start,
|
578 |
-
"end": curr_end,
|
579 |
-
"segments": seg_idxs,
|
580 |
-
}
|
581 |
-
)
|
582 |
-
curr_start = seg.start
|
583 |
-
seg_idxs = []
|
584 |
-
speaker_idxs = []
|
585 |
-
curr_end = seg.end
|
586 |
-
seg_idxs.append((seg.start, seg.end))
|
587 |
-
speaker_idxs.append(seg.speaker)
|
588 |
-
# add final
|
589 |
-
merged_segments.append(
|
590 |
-
{
|
591 |
-
"start": curr_start,
|
592 |
-
"end": curr_end,
|
593 |
-
"segments": seg_idxs,
|
594 |
-
}
|
595 |
-
)
|
596 |
-
return merged_segments
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/build/lib/faster_whisper/version.py
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
"""Version information."""
|
2 |
-
|
3 |
-
__version__ = "1.0.3"
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/docker/Dockerfile
DELETED
@@ -1,6 +0,0 @@
|
|
1 |
-
FROM nvidia/cuda:12.2.2-cudnn8-runtime-ubuntu22.04
|
2 |
-
WORKDIR /root
|
3 |
-
RUN apt-get update -y && apt-get install -y python3-pip
|
4 |
-
COPY infer.py jfk.flac ./
|
5 |
-
RUN pip3 install faster-whisper
|
6 |
-
CMD ["python3", "infer.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/docker/infer.py
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
from faster_whisper import WhisperModel
|
2 |
-
|
3 |
-
jfk_path = "jfk.flac"
|
4 |
-
model = WhisperModel("tiny", device="cuda")
|
5 |
-
segments, info = model.transcribe(jfk_path, word_timestamps=True)
|
6 |
-
for segment in segments:
|
7 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/docker/jfk.flac
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:63a4b1e4c1dc655ac70961ffbf518acd249df237e5a0152faae9a4a836949715
|
3 |
-
size 1152693
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/PKG-INFO
DELETED
@@ -1,347 +0,0 @@
|
|
1 |
-
Metadata-Version: 2.1
|
2 |
-
Name: faster-whisper
|
3 |
-
Version: 1.0.3
|
4 |
-
Summary: Faster Whisper transcription with CTranslate2
|
5 |
-
Home-page: https://github.com/SYSTRAN/faster-whisper
|
6 |
-
Author: Guillaume Klein
|
7 |
-
License: MIT
|
8 |
-
Keywords: openai whisper speech ctranslate2 inference quantization transformer
|
9 |
-
Platform: UNKNOWN
|
10 |
-
Classifier: Development Status :: 4 - Beta
|
11 |
-
Classifier: Intended Audience :: Developers
|
12 |
-
Classifier: Intended Audience :: Science/Research
|
13 |
-
Classifier: License :: OSI Approved :: MIT License
|
14 |
-
Classifier: Programming Language :: Python :: 3
|
15 |
-
Classifier: Programming Language :: Python :: 3 :: Only
|
16 |
-
Classifier: Programming Language :: Python :: 3.8
|
17 |
-
Classifier: Programming Language :: Python :: 3.9
|
18 |
-
Classifier: Programming Language :: Python :: 3.10
|
19 |
-
Classifier: Programming Language :: Python :: 3.11
|
20 |
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
21 |
-
Requires-Python: >=3.8
|
22 |
-
Description-Content-Type: text/markdown
|
23 |
-
Provides-Extra: conversion
|
24 |
-
Provides-Extra: dev
|
25 |
-
License-File: LICENSE
|
26 |
-
|
27 |
-
[](https://github.com/SYSTRAN/faster-whisper/actions?query=workflow%3ACI) [](https://badge.fury.io/py/faster-whisper)
|
28 |
-
|
29 |
-
# Faster Whisper transcription with CTranslate2
|
30 |
-
|
31 |
-
**faster-whisper** is a reimplementation of OpenAI's Whisper model using [CTranslate2](https://github.com/OpenNMT/CTranslate2/), which is a fast inference engine for Transformer models.
|
32 |
-
|
33 |
-
This implementation is up to 4 times faster than [openai/whisper](https://github.com/openai/whisper) for the same accuracy while using less memory. The efficiency can be further improved with 8-bit quantization on both CPU and GPU.
|
34 |
-
|
35 |
-
## Benchmark
|
36 |
-
|
37 |
-
### Whisper
|
38 |
-
|
39 |
-
For reference, here's the time and memory usage that are required to transcribe [**13 minutes**](https://www.youtube.com/watch?v=0u7tTptBo9I) of audio using different implementations:
|
40 |
-
|
41 |
-
* [openai/whisper](https://github.com/openai/whisper)@[6dea21fd](https://github.com/openai/whisper/commit/6dea21fd7f7253bfe450f1e2512a0fe47ee2d258)
|
42 |
-
* [whisper.cpp](https://github.com/ggerganov/whisper.cpp)@[3b010f9](https://github.com/ggerganov/whisper.cpp/commit/3b010f9bed9a6068609e9faf52383aea792b0362)
|
43 |
-
* [faster-whisper](https://github.com/SYSTRAN/faster-whisper)@[cce6b53e](https://github.com/SYSTRAN/faster-whisper/commit/cce6b53e4554f71172dad188c45f10fb100f6e3e)
|
44 |
-
|
45 |
-
### Large-v2 model on GPU
|
46 |
-
|
47 |
-
| Implementation | Precision | Beam size | Time | Max. GPU memory | Max. CPU memory |
|
48 |
-
| --- | --- | --- | --- | --- | --- |
|
49 |
-
| openai/whisper | fp16 | 5 | 4m30s | 11325MB | 9439MB |
|
50 |
-
| faster-whisper | fp16 | 5 | 54s | 4755MB | 3244MB |
|
51 |
-
| faster-whisper | int8 | 5 | 59s | 3091MB | 3117MB |
|
52 |
-
|
53 |
-
*Executed with CUDA 11.7.1 on a NVIDIA Tesla V100S.*
|
54 |
-
|
55 |
-
### Small model on CPU
|
56 |
-
|
57 |
-
| Implementation | Precision | Beam size | Time | Max. memory |
|
58 |
-
| --- | --- | --- | --- | --- |
|
59 |
-
| openai/whisper | fp32 | 5 | 10m31s | 3101MB |
|
60 |
-
| whisper.cpp | fp32 | 5 | 17m42s | 1581MB |
|
61 |
-
| whisper.cpp | fp16 | 5 | 12m39s | 873MB |
|
62 |
-
| faster-whisper | fp32 | 5 | 2m44s | 1675MB |
|
63 |
-
| faster-whisper | int8 | 5 | 2m04s | 995MB |
|
64 |
-
|
65 |
-
*Executed with 8 threads on a Intel(R) Xeon(R) Gold 6226R.*
|
66 |
-
|
67 |
-
|
68 |
-
### Distil-whisper
|
69 |
-
|
70 |
-
| Implementation | Precision | Beam size | Time | Gigaspeech WER |
|
71 |
-
| --- | --- | --- | --- | --- |
|
72 |
-
| distil-whisper/distil-large-v2 | fp16 | 4 |- | 10.36 |
|
73 |
-
| [faster-distil-large-v2](https://huggingface.co/Systran/faster-distil-whisper-large-v2) | fp16 | 5 | - | 10.28 |
|
74 |
-
| distil-whisper/distil-medium.en | fp16 | 4 | - | 11.21 |
|
75 |
-
| [faster-distil-medium.en](https://huggingface.co/Systran/faster-distil-whisper-medium.en) | fp16 | 5 | - | 11.21 |
|
76 |
-
|
77 |
-
*Executed with CUDA 11.4 on a NVIDIA 3090.*
|
78 |
-
|
79 |
-
<details>
|
80 |
-
<summary>testing details (click to expand)</summary>
|
81 |
-
|
82 |
-
For `distil-whisper/distil-large-v2`, the WER is tested with code sample from [link](https://huggingface.co/distil-whisper/distil-large-v2#evaluation). for `faster-distil-whisper`, the WER is tested with setting:
|
83 |
-
```python
|
84 |
-
from faster_whisper import WhisperModel
|
85 |
-
|
86 |
-
model_size = "distil-large-v2"
|
87 |
-
# model_size = "distil-medium.en"
|
88 |
-
# Run on GPU with FP16
|
89 |
-
model = WhisperModel(model_size, device="cuda", compute_type="float16")
|
90 |
-
segments, info = model.transcribe("audio.mp3", beam_size=5, language="en")
|
91 |
-
```
|
92 |
-
</details>
|
93 |
-
|
94 |
-
## Requirements
|
95 |
-
|
96 |
-
* Python 3.8 or greater
|
97 |
-
|
98 |
-
|
99 |
-
### GPU
|
100 |
-
|
101 |
-
GPU execution requires the following NVIDIA libraries to be installed:
|
102 |
-
|
103 |
-
* [cuBLAS for CUDA 12](https://developer.nvidia.com/cublas)
|
104 |
-
* [cuDNN 8 for CUDA 12](https://developer.nvidia.com/cudnn)
|
105 |
-
|
106 |
-
**Note**: Latest versions of `ctranslate2` support CUDA 12 only. For CUDA 11, the current workaround is downgrading to the `3.24.0` version of `ctranslate2` (This can be done with `pip install --force-reinstall ctranslate2==3.24.0` or specifying the version in a `requirements.txt`).
|
107 |
-
|
108 |
-
There are multiple ways to install the NVIDIA libraries mentioned above. The recommended way is described in the official NVIDIA documentation, but we also suggest other installation methods below.
|
109 |
-
|
110 |
-
<details>
|
111 |
-
<summary>Other installation methods (click to expand)</summary>
|
112 |
-
|
113 |
-
|
114 |
-
**Note:** For all these methods below, keep in mind the above note regarding CUDA versions. Depending on your setup, you may need to install the _CUDA 11_ versions of libraries that correspond to the CUDA 12 libraries listed in the instructions below.
|
115 |
-
|
116 |
-
#### Use Docker
|
117 |
-
|
118 |
-
The libraries (cuBLAS, cuDNN) are installed in these official NVIDIA CUDA Docker images: `nvidia/cuda:12.0.0-runtime-ubuntu20.04` or `nvidia/cuda:12.0.0-runtime-ubuntu22.04`.
|
119 |
-
|
120 |
-
#### Install with `pip` (Linux only)
|
121 |
-
|
122 |
-
On Linux these libraries can be installed with `pip`. Note that `LD_LIBRARY_PATH` must be set before launching Python.
|
123 |
-
|
124 |
-
```bash
|
125 |
-
pip install nvidia-cublas-cu12 nvidia-cudnn-cu12
|
126 |
-
|
127 |
-
export LD_LIBRARY_PATH=`python3 -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))'`
|
128 |
-
```
|
129 |
-
|
130 |
-
**Note**: Version 9+ of `nvidia-cudnn-cu12` appears to cause issues due its reliance on cuDNN 9 (Faster-Whisper does not currently support cuDNN 9). Ensure your version of the Python package is for cuDNN 8.
|
131 |
-
|
132 |
-
#### Download the libraries from Purfview's repository (Windows & Linux)
|
133 |
-
|
134 |
-
Purfview's [whisper-standalone-win](https://github.com/Purfview/whisper-standalone-win) provides the required NVIDIA libraries for Windows & Linux in a [single archive](https://github.com/Purfview/whisper-standalone-win/releases/tag/libs). Decompress the archive and place the libraries in a directory included in the `PATH`.
|
135 |
-
|
136 |
-
</details>
|
137 |
-
|
138 |
-
## Installation
|
139 |
-
|
140 |
-
The module can be installed from [PyPI](https://pypi.org/project/faster-whisper/):
|
141 |
-
|
142 |
-
```bash
|
143 |
-
pip install faster-whisper
|
144 |
-
```
|
145 |
-
|
146 |
-
<details>
|
147 |
-
<summary>Other installation methods (click to expand)</summary>
|
148 |
-
|
149 |
-
### Install the master branch
|
150 |
-
|
151 |
-
```bash
|
152 |
-
pip install --force-reinstall "faster-whisper @ https://github.com/SYSTRAN/faster-whisper/archive/refs/heads/master.tar.gz"
|
153 |
-
```
|
154 |
-
|
155 |
-
### Install a specific commit
|
156 |
-
|
157 |
-
```bash
|
158 |
-
pip install --force-reinstall "faster-whisper @ https://github.com/SYSTRAN/faster-whisper/archive/a4f1cc8f11433e454c3934442b5e1a4ed5e865c3.tar.gz"
|
159 |
-
```
|
160 |
-
|
161 |
-
</details>
|
162 |
-
|
163 |
-
## Usage
|
164 |
-
|
165 |
-
### Faster-whisper
|
166 |
-
|
167 |
-
```python
|
168 |
-
from faster_whisper import WhisperModel
|
169 |
-
|
170 |
-
model_size = "large-v3"
|
171 |
-
|
172 |
-
# Run on GPU with FP16
|
173 |
-
model = WhisperModel(model_size, device="cuda", compute_type="float16")
|
174 |
-
|
175 |
-
# or run on GPU with INT8
|
176 |
-
# model = WhisperModel(model_size, device="cuda", compute_type="int8_float16")
|
177 |
-
# or run on CPU with INT8
|
178 |
-
# model = WhisperModel(model_size, device="cpu", compute_type="int8")
|
179 |
-
|
180 |
-
segments, info = model.transcribe("audio.mp3", beam_size=5)
|
181 |
-
|
182 |
-
print("Detected language '%s' with probability %f" % (info.language, info.language_probability))
|
183 |
-
|
184 |
-
for segment in segments:
|
185 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
186 |
-
```
|
187 |
-
|
188 |
-
**Warning:** `segments` is a *generator* so the transcription only starts when you iterate over it. The transcription can be run to completion by gathering the segments in a list or a `for` loop:
|
189 |
-
|
190 |
-
```python
|
191 |
-
segments, _ = model.transcribe("audio.mp3")
|
192 |
-
segments = list(segments) # The transcription will actually run here.
|
193 |
-
```
|
194 |
-
|
195 |
-
### multi-segment language detection
|
196 |
-
|
197 |
-
To directly use the model for improved language detection, the following code snippet can be used:
|
198 |
-
|
199 |
-
```python
|
200 |
-
from faster_whisper import WhisperModel
|
201 |
-
model = WhisperModel("medium", device="cuda", compute_type="float16")
|
202 |
-
language_info = model.detect_language_multi_segment("audio.mp3")
|
203 |
-
```
|
204 |
-
|
205 |
-
### Batched faster-whisper
|
206 |
-
|
207 |
-
|
208 |
-
The batched version of faster-whisper is inspired by [whisper-x](https://github.com/m-bain/whisperX) licensed under the BSD-2 Clause license and integrates its VAD model to this library. We modify this implementation and also replaced the feature extraction with a faster torch-based implementation. Batched version improves the speed upto 10-12x compared to openAI implementation and 3-4x compared to the sequential faster_whisper version. It works by transcribing semantically meaningful audio chunks as batches leading to faster inference.
|
209 |
-
|
210 |
-
The following code snippet illustrates how to run inference with batched version on an example audio file. Please also refer to the test scripts of batched faster whisper.
|
211 |
-
|
212 |
-
```python
|
213 |
-
from faster_whisper import WhisperModel, BatchedInferencePipeline
|
214 |
-
|
215 |
-
model = WhisperModel("medium", device="cuda", compute_type="float16")
|
216 |
-
batched_model = BatchedInferencePipeline(model=model)
|
217 |
-
segments, info = batched_model.transcribe("audio.mp3", batch_size=16)
|
218 |
-
|
219 |
-
for segment in segments:
|
220 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
221 |
-
```
|
222 |
-
|
223 |
-
### Faster Distil-Whisper
|
224 |
-
|
225 |
-
The Distil-Whisper checkpoints are compatible with the Faster-Whisper package. In particular, the latest [distil-large-v3](https://huggingface.co/distil-whisper/distil-large-v3)
|
226 |
-
checkpoint is intrinsically designed to work with the Faster-Whisper transcription algorithm. The following code snippet
|
227 |
-
demonstrates how to run inference with distil-large-v3 on a specified audio file:
|
228 |
-
|
229 |
-
```python
|
230 |
-
from faster_whisper import WhisperModel
|
231 |
-
|
232 |
-
model_size = "distil-large-v3"
|
233 |
-
|
234 |
-
model = WhisperModel(model_size, device="cuda", compute_type="float16")
|
235 |
-
segments, info = model.transcribe("audio.mp3", beam_size=5, language="en", condition_on_previous_text=False)
|
236 |
-
|
237 |
-
for segment in segments:
|
238 |
-
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
239 |
-
```
|
240 |
-
|
241 |
-
For more information about the distil-large-v3 model, refer to the original [model card](https://huggingface.co/distil-whisper/distil-large-v3).
|
242 |
-
|
243 |
-
### Word-level timestamps
|
244 |
-
|
245 |
-
```python
|
246 |
-
segments, _ = model.transcribe("audio.mp3", word_timestamps=True)
|
247 |
-
|
248 |
-
for segment in segments:
|
249 |
-
for word in segment.words:
|
250 |
-
print("[%.2fs -> %.2fs] %s" % (word.start, word.end, word.word))
|
251 |
-
```
|
252 |
-
|
253 |
-
### VAD filter
|
254 |
-
|
255 |
-
The library integrates the [Silero VAD](https://github.com/snakers4/silero-vad) model to filter out parts of the audio without speech:
|
256 |
-
|
257 |
-
```python
|
258 |
-
segments, _ = model.transcribe("audio.mp3", vad_filter=True)
|
259 |
-
```
|
260 |
-
|
261 |
-
The default behavior is conservative and only removes silence longer than 2 seconds. See the available VAD parameters and default values in the [source code](https://github.com/SYSTRAN/faster-whisper/blob/master/faster_whisper/vad.py). They can be customized with the dictionary argument `vad_parameters`:
|
262 |
-
|
263 |
-
```python
|
264 |
-
segments, _ = model.transcribe(
|
265 |
-
"audio.mp3",
|
266 |
-
vad_filter=True,
|
267 |
-
vad_parameters=dict(min_silence_duration_ms=500),
|
268 |
-
)
|
269 |
-
```
|
270 |
-
|
271 |
-
### Logging
|
272 |
-
|
273 |
-
The library logging level can be configured like this:
|
274 |
-
|
275 |
-
```python
|
276 |
-
import logging
|
277 |
-
|
278 |
-
logging.basicConfig()
|
279 |
-
logging.getLogger("faster_whisper").setLevel(logging.DEBUG)
|
280 |
-
```
|
281 |
-
|
282 |
-
### Going further
|
283 |
-
|
284 |
-
See more model and transcription options in the [`WhisperModel`](https://github.com/SYSTRAN/faster-whisper/blob/master/faster_whisper/transcribe.py) class implementation.
|
285 |
-
|
286 |
-
## Community integrations
|
287 |
-
|
288 |
-
Here is a non exhaustive list of open-source projects using faster-whisper. Feel free to add your project to the list!
|
289 |
-
|
290 |
-
|
291 |
-
* [faster-whisper-server](https://github.com/fedirz/faster-whisper-server) is an OpenAI compatible server using `faster-whisper`. It's easily deployable with Docker, works with OpenAI SDKs/CLI, supports streaming, and live transcription.
|
292 |
-
* [WhisperX](https://github.com/m-bain/whisperX) is an award-winning Python library that offers speaker diarization and accurate word-level timestamps using wav2vec2 alignment
|
293 |
-
* [whisper-ctranslate2](https://github.com/Softcatala/whisper-ctranslate2) is a command line client based on faster-whisper and compatible with the original client from openai/whisper.
|
294 |
-
* [whisper-diarize](https://github.com/MahmoudAshraf97/whisper-diarization) is a speaker diarization tool that is based on faster-whisper and NVIDIA NeMo.
|
295 |
-
* [whisper-standalone-win](https://github.com/Purfview/whisper-standalone-win) Standalone CLI executables of faster-whisper for Windows, Linux & macOS.
|
296 |
-
* [asr-sd-pipeline](https://github.com/hedrergudene/asr-sd-pipeline) provides a scalable, modular, end to end multi-speaker speech to text solution implemented using AzureML pipelines.
|
297 |
-
* [Open-Lyrics](https://github.com/zh-plus/Open-Lyrics) is a Python library that transcribes voice files using faster-whisper, and translates/polishes the resulting text into `.lrc` files in the desired language using OpenAI-GPT.
|
298 |
-
* [wscribe](https://github.com/geekodour/wscribe) is a flexible transcript generation tool supporting faster-whisper, it can export word level transcript and the exported transcript then can be edited with [wscribe-editor](https://github.com/geekodour/wscribe-editor)
|
299 |
-
* [aTrain](https://github.com/BANDAS-Center/aTrain) is a graphical user interface implementation of faster-whisper developed at the BANDAS-Center at the University of Graz for transcription and diarization in Windows ([Windows Store App](https://apps.microsoft.com/detail/atrain/9N15Q44SZNS2)) and Linux.
|
300 |
-
* [Whisper-Streaming](https://github.com/ufal/whisper_streaming) implements real-time mode for offline Whisper-like speech-to-text models with faster-whisper as the most recommended back-end. It implements a streaming policy with self-adaptive latency based on the actual source complexity, and demonstrates the state of the art.
|
301 |
-
* [WhisperLive](https://github.com/collabora/WhisperLive) is a nearly-live implementation of OpenAI's Whisper which uses faster-whisper as the backend to transcribe audio in real-time.
|
302 |
-
* [Faster-Whisper-Transcriber](https://github.com/BBC-Esq/ctranslate2-faster-whisper-transcriber) is a simple but reliable voice transcriber that provides a user-friendly interface.
|
303 |
-
|
304 |
-
## Model conversion
|
305 |
-
|
306 |
-
When loading a model from its size such as `WhisperModel("large-v3")`, the corresponding CTranslate2 model is automatically downloaded from the [Hugging Face Hub](https://huggingface.co/Systran).
|
307 |
-
|
308 |
-
We also provide a script to convert any Whisper models compatible with the Transformers library. They could be the original OpenAI models or user fine-tuned models.
|
309 |
-
|
310 |
-
For example the command below converts the [original "large-v3" Whisper model](https://huggingface.co/openai/whisper-large-v3) and saves the weights in FP16:
|
311 |
-
|
312 |
-
```bash
|
313 |
-
pip install transformers[torch]>=4.23
|
314 |
-
|
315 |
-
ct2-transformers-converter --model openai/whisper-large-v3 --output_dir whisper-large-v3-ct2
|
316 |
-
--copy_files tokenizer.json preprocessor_config.json --quantization float16
|
317 |
-
```
|
318 |
-
|
319 |
-
* The option `--model` accepts a model name on the Hub or a path to a model directory.
|
320 |
-
* If the option `--copy_files tokenizer.json` is not used, the tokenizer configuration is automatically downloaded when the model is loaded later.
|
321 |
-
|
322 |
-
Models can also be converted from the code. See the [conversion API](https://opennmt.net/CTranslate2/python/ctranslate2.converters.TransformersConverter.html).
|
323 |
-
|
324 |
-
### Load a converted model
|
325 |
-
|
326 |
-
1. Directly load the model from a local directory:
|
327 |
-
```python
|
328 |
-
model = faster_whisper.WhisperModel("whisper-large-v3-ct2")
|
329 |
-
```
|
330 |
-
|
331 |
-
2. [Upload your model to the Hugging Face Hub](https://huggingface.co/docs/transformers/model_sharing#upload-with-the-web-interface) and load it from its name:
|
332 |
-
```python
|
333 |
-
model = faster_whisper.WhisperModel("username/whisper-large-v3-ct2")
|
334 |
-
```
|
335 |
-
|
336 |
-
## Comparing performance against other implementations
|
337 |
-
|
338 |
-
If you are comparing the performance against other Whisper implementations, you should make sure to run the comparison with similar settings. In particular:
|
339 |
-
|
340 |
-
* Verify that the same transcription options are used, especially the same beam size. For example in openai/whisper, `model.transcribe` uses a default beam size of 1 but here we use a default beam size of 5.
|
341 |
-
* When running on CPU, make sure to set the same number of threads. Many frameworks will read the environment variable `OMP_NUM_THREADS`, which can be set when running your script:
|
342 |
-
|
343 |
-
```bash
|
344 |
-
OMP_NUM_THREADS=4 python3 my_script.py
|
345 |
-
```
|
346 |
-
|
347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/SOURCES.txt
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
LICENSE
|
2 |
-
MANIFEST.in
|
3 |
-
README.md
|
4 |
-
requirements.conversion.txt
|
5 |
-
requirements.txt
|
6 |
-
setup.cfg
|
7 |
-
setup.py
|
8 |
-
faster_whisper/__init__.py
|
9 |
-
faster_whisper/audio.py
|
10 |
-
faster_whisper/feature_extractor.py
|
11 |
-
faster_whisper/tokenizer.py
|
12 |
-
faster_whisper/transcribe.py
|
13 |
-
faster_whisper/utils.py
|
14 |
-
faster_whisper/vad.py
|
15 |
-
faster_whisper/version.py
|
16 |
-
faster_whisper.egg-info/PKG-INFO
|
17 |
-
faster_whisper.egg-info/SOURCES.txt
|
18 |
-
faster_whisper.egg-info/dependency_links.txt
|
19 |
-
faster_whisper.egg-info/requires.txt
|
20 |
-
faster_whisper.egg-info/top_level.txt
|
21 |
-
faster_whisper/assets/__init__.py
|
22 |
-
faster_whisper/assets/pyannote_vad_model.bin
|
23 |
-
faster_whisper/assets/silero_vad.onnx
|
24 |
-
tests/test_transcribe.py
|
25 |
-
tests/test_utils.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/dependency_links.txt
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/requires.txt
DELETED
@@ -1,17 +0,0 @@
|
|
1 |
-
ctranslate2<5,>=4.0
|
2 |
-
huggingface_hub>=0.13
|
3 |
-
onnxruntime<2,>=1.14
|
4 |
-
pyannote-audio
|
5 |
-
tokenizers<1,>=0.13
|
6 |
-
torch
|
7 |
-
torchaudio
|
8 |
-
tqdm
|
9 |
-
|
10 |
-
[conversion]
|
11 |
-
transformers[torch]>=4.23
|
12 |
-
|
13 |
-
[dev]
|
14 |
-
black==23.*
|
15 |
-
flake8==6.*
|
16 |
-
isort==5.*
|
17 |
-
pytest==7.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper.egg-info/top_level.txt
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
faster_whisper
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__init__.py
DELETED
@@ -1,14 +0,0 @@
|
|
1 |
-
from faster_whisper.audio import decode_audio
|
2 |
-
from faster_whisper.transcribe import BatchedInferencePipeline, WhisperModel
|
3 |
-
from faster_whisper.utils import available_models, download_model, format_timestamp
|
4 |
-
from faster_whisper.version import __version__
|
5 |
-
|
6 |
-
__all__ = [
|
7 |
-
"available_models",
|
8 |
-
"decode_audio",
|
9 |
-
"WhisperModel",
|
10 |
-
"BatchedInferencePipeline",
|
11 |
-
"download_model",
|
12 |
-
"format_timestamp",
|
13 |
-
"__version__",
|
14 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/__init__.cpython-310.pyc
DELETED
Binary file (572 Bytes)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/audio.cpython-310.pyc
DELETED
Binary file (1.59 kB)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/feature_extractor.cpython-310.pyc
DELETED
Binary file (2.73 kB)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/tokenizer.cpython-310.pyc
DELETED
Binary file (6.78 kB)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/transcribe.cpython-310.pyc
DELETED
Binary file (53.3 kB)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/utils.cpython-310.pyc
DELETED
Binary file (5.13 kB)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/vad.cpython-310.pyc
DELETED
Binary file (15.2 kB)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/__pycache__/version.cpython-310.pyc
DELETED
Binary file (248 Bytes)
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/assets/__init__.py
DELETED
File without changes
|
whisper_pipeline/faster-whisper-main/faster_whisper/assets/pyannote_vad_model.bin
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:0b5b3216d60a2d32fc086b47ea8c67589aaeb26b7e07fcbe620d6d0b83e209ea
|
3 |
-
size 17719103
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/assets/silero_vad.onnx
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:6b99cbfd39246b6706f98ec13c7c50c6b299181f2474fa05cbc8046acc274396
|
3 |
-
size 2313101
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/audio.py
DELETED
@@ -1,58 +0,0 @@
|
|
1 |
-
from typing import BinaryIO, Union
|
2 |
-
|
3 |
-
import torch
|
4 |
-
import torchaudio
|
5 |
-
|
6 |
-
|
7 |
-
def decode_audio(
|
8 |
-
input_file: Union[str, BinaryIO],
|
9 |
-
sampling_rate: int = 16000,
|
10 |
-
split_stereo: bool = False,
|
11 |
-
):
|
12 |
-
"""Decodes the audio.
|
13 |
-
|
14 |
-
Args:
|
15 |
-
input_file: Path to the input file or a file-like object.
|
16 |
-
sampling_rate: Resample the audio to this sample rate.
|
17 |
-
split_stereo: Return separate left and right channels.
|
18 |
-
|
19 |
-
Returns:
|
20 |
-
A float32 Torch Tensor.
|
21 |
-
|
22 |
-
If `split_stereo` is enabled, the function returns a 2-tuple with the
|
23 |
-
separated left and right channels.
|
24 |
-
"""
|
25 |
-
|
26 |
-
waveform, audio_sf = torchaudio.load(input_file) # waveform: channels X T
|
27 |
-
|
28 |
-
if audio_sf != sampling_rate:
|
29 |
-
waveform = torchaudio.functional.resample(
|
30 |
-
waveform, orig_freq=audio_sf, new_freq=sampling_rate
|
31 |
-
)
|
32 |
-
if split_stereo:
|
33 |
-
return waveform[0], waveform[1]
|
34 |
-
|
35 |
-
return waveform.mean(0)
|
36 |
-
|
37 |
-
|
38 |
-
def pad_or_trim(array, length: int, *, axis: int = -1):
|
39 |
-
"""
|
40 |
-
Pad or trim the audio array to N_SAMPLES, as expected by the encoder.
|
41 |
-
"""
|
42 |
-
axis = axis % array.ndim
|
43 |
-
if array.shape[axis] > length:
|
44 |
-
idx = [Ellipsis] * axis + [slice(length)] + [Ellipsis] * (array.ndim - axis - 1)
|
45 |
-
return array[idx]
|
46 |
-
|
47 |
-
if array.shape[axis] < length:
|
48 |
-
pad_widths = (
|
49 |
-
[
|
50 |
-
0,
|
51 |
-
]
|
52 |
-
* array.ndim
|
53 |
-
* 2
|
54 |
-
)
|
55 |
-
pad_widths[2 * axis] = length - array.shape[axis]
|
56 |
-
array = torch.nn.functional.pad(array, tuple(pad_widths[::-1]))
|
57 |
-
|
58 |
-
return array
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whisper_pipeline/faster-whisper-main/faster_whisper/feature_extractor.py
DELETED
@@ -1,114 +0,0 @@
|
|
1 |
-
import torch
|
2 |
-
|
3 |
-
|
4 |
-
# Adapted from https://github.com/huggingface/transformers/blob/main/src/transformers/models/whisper/feature_extraction_whisper.py # noqa: E501
|
5 |
-
class FeatureExtractor:
|
6 |
-
def __init__(
|
7 |
-
self,
|
8 |
-
device: str = "auto",
|
9 |
-
feature_size=80,
|
10 |
-
sampling_rate=16000,
|
11 |
-
hop_length=160,
|
12 |
-
chunk_length=30,
|
13 |
-
n_fft=400,
|
14 |
-
):
|
15 |
-
if device == "auto":
|
16 |
-
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
17 |
-
else:
|
18 |
-
self.device = device
|
19 |
-
self.n_fft = n_fft
|
20 |
-
self.hop_length = hop_length
|
21 |
-
self.chunk_length = chunk_length
|
22 |
-
self.n_samples = chunk_length * sampling_rate
|
23 |
-
self.nb_max_frames = self.n_samples // hop_length
|
24 |
-
self.time_per_frame = hop_length / sampling_rate
|
25 |
-
self.sampling_rate = sampling_rate
|
26 |
-
self.mel_filters = self.get_mel_filters(
|
27 |
-
sampling_rate, n_fft, n_mels=feature_size
|
28 |
-
)
|
29 |
-
|
30 |
-
@staticmethod
|
31 |
-
def get_mel_filters(sr, n_fft, n_mels=128):
|
32 |
-
"""
|
33 |
-
Implementation of librosa.filters.mel in Pytorch
|
34 |
-
"""
|
35 |
-
# Initialize the weights
|
36 |
-
n_mels = int(n_mels)
|
37 |
-
|
38 |
-
# Center freqs of each FFT bin
|
39 |
-
fftfreqs = torch.fft.rfftfreq(n=n_fft, d=1.0 / sr)
|
40 |
-
|
41 |
-
# 'Center freqs' of mel bands - uniformly spaced between limits
|
42 |
-
min_mel = 0.0
|
43 |
-
max_mel = 45.245640471924965
|
44 |
-
|
45 |
-
mels = torch.linspace(min_mel, max_mel, n_mels + 2)
|
46 |
-
|
47 |
-
# Fill in the linear scale
|
48 |
-
f_min = 0.0
|
49 |
-
f_sp = 200.0 / 3
|
50 |
-
freqs = f_min + f_sp * mels
|
51 |
-
|
52 |
-
# And now the nonlinear scale
|
53 |
-
min_log_hz = 1000.0 # beginning of log region (Hz)
|
54 |
-
min_log_mel = (min_log_hz - f_min) / f_sp # same (Mels)
|
55 |
-
logstep = torch.log(torch.tensor(6.4)) / 27.0 # step size for log region
|
56 |
-
|
57 |
-
# If we have vector data, vectorize
|
58 |
-
log_t = mels >= min_log_mel
|
59 |
-
freqs[log_t] = min_log_hz * torch.exp(logstep * (mels[log_t] - min_log_mel))
|
60 |
-
|
61 |
-
mel_f = freqs
|
62 |
-
|
63 |
-
fdiff = torch.diff(mel_f)
|
64 |
-
ramps = mel_f.view(-1, 1) - fftfreqs.view(1, -1)
|
65 |
-
|
66 |
-
lower = -ramps[:-2] / fdiff[:-1].unsqueeze(1)
|
67 |
-
upper = ramps[2:] / fdiff[1:].unsqueeze(1)
|
68 |
-
|
69 |
-
# Intersect them with each other and zero, vectorized across all i
|
70 |
-
weights = torch.maximum(torch.zeros_like(lower), torch.minimum(lower, upper))
|
71 |
-
|
72 |
-
# Slaney-style mel is scaled to be approx constant energy per channel
|
73 |
-
enorm = 2.0 / (mel_f[2 : n_mels + 2] - mel_f[:n_mels])
|
74 |
-
weights *= enorm.unsqueeze(1)
|
75 |
-
|
76 |
-
return weights
|
77 |
-
|
78 |
-
def __call__(self, waveform, padding=True, chunk_length=None, to_cpu=False):
|
79 |
-
"""
|
80 |
-
Compute the log-Mel spectrogram of the provided audio.
|
81 |
-
"""
|
82 |
-
|
83 |
-
if chunk_length is not None:
|
84 |
-
self.n_samples = chunk_length * self.sampling_rate
|
85 |
-
self.nb_max_frames = self.n_samples // self.hop_length
|
86 |
-
|
87 |
-
if waveform.dtype is not torch.float32:
|
88 |
-
waveform = waveform.to(torch.float32)
|
89 |
-
|
90 |
-
waveform = (
|
91 |
-
waveform.to(self.device)
|
92 |
-
if self.device == "cuda" and not waveform.is_cuda
|
93 |
-
else waveform
|
94 |
-
)
|
95 |
-
|
96 |
-
if padding:
|
97 |
-
waveform = torch.nn.functional.pad(waveform, (0, self.n_samples))
|
98 |
-
|
99 |
-
window = torch.hann_window(self.n_fft).to(waveform.device)
|
100 |
-
|
101 |
-
stft = torch.stft(
|
102 |
-
waveform, self.n_fft, self.hop_length, window=window, return_complex=True
|
103 |
-
)
|
104 |
-
magnitudes = stft[..., :-1].abs() ** 2
|
105 |
-
|
106 |
-
mel_spec = self.mel_filters.to(waveform.device) @ magnitudes
|
107 |
-
|
108 |
-
log_spec = torch.clamp(mel_spec, min=1e-10).log10()
|
109 |
-
log_spec = torch.maximum(log_spec, log_spec.max() - 8.0)
|
110 |
-
log_spec = (log_spec + 4.0) / 4.0
|
111 |
-
|
112 |
-
# When the model is running on multiple GPUs, the output should be moved
|
113 |
-
# to the CPU since we don't know which GPU will handle the next job.
|
114 |
-
return log_spec.cpu() if to_cpu else log_spec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|