diff --git a/CHANGELOG.md b/CHANGELOG.md index 481e1fc98fca90092797330ed71c738ecff28fe2..2e73bfe8e860d3a11beb10082bbf99027700f00e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,25 @@ Unreleased - Added detection confidence to langauge detection endpoint - Set mel generation to adjust n_dims automatically to match the loaded model +### Added + + - Refactor classes, Add comments, implement abstract methods, and add factory method for engine selection + +### Changed + +- Upgraded + - [SYSTRAN/faster-whisper](https://github.com/SYSTRAN/faster-whisper) to [v1.1.0](https://github.com/SYSTRAN/faster-whisper/releases/tag/v1.1.0) + - uvicorn to v0.34.0 + - tqdm to v4.67.1 + - python-multipart to v0.0.20 + - fastapi to v0.115.6 + - pytest to v8.3.4 + - ruff to v0.8.3 + - black to v24.10.0 + - mkdocs to v1.6.1 + - mkdocs-material to v9.5.49 + - pymdown-extensions to v10.12 + [1.6.0] (2024-10-06) -------------------- diff --git a/app/asr_models/asr_model.py b/app/asr_models/asr_model.py new file mode 100644 index 0000000000000000000000000000000000000000..fc2205083ed48e5e88267aeb3afa1de2d2e61051 --- /dev/null +++ b/app/asr_models/asr_model.py @@ -0,0 +1,71 @@ +import gc +import time +from abc import ABC, abstractmethod +from threading import Lock +from typing import Union + +import torch + +from app.config import CONFIG + + +class ASRModel(ABC): + """ + Abstract base class for ASR (Automatic Speech Recognition) models. + """ + model = None + model_lock = Lock() + last_activity_time = time.time() + + def __init__(self): + pass + + @abstractmethod + def load_model(self): + """ + Loads the model from the specified path. + """ + pass + + @abstractmethod + def transcribe(self, + audio, + task: Union[str, None], + language: Union[str, None], + initial_prompt: Union[str, None], + vad_filter: Union[bool, None], + word_timestamps: Union[bool, None] + ): + """ + Perform transcription on the given audio file. + """ + pass + + @abstractmethod + def language_detection(self, audio): + """ + Perform language detection on the given audio file. + """ + pass + + def monitor_idleness(self): + """ + Monitors the idleness of the ASR model and releases the model if it has been idle for too long. + """ + if CONFIG.MODEL_IDLE_TIMEOUT <= 0: return + while True: + time.sleep(15) + if time.time() - self.last_activity_time > CONFIG.MODEL_IDLE_TIMEOUT: + with self.model_lock: + self.release_model() + break + + def release_model(self): + """ + Unloads the model from memory and clears any cached GPU memory. + """ + del self.model + torch.cuda.empty_cache() + gc.collect() + self.model = None + print("Model unloaded due to timeout") diff --git a/app/asr_models/faster_whisper_engine.py b/app/asr_models/faster_whisper_engine.py new file mode 100644 index 0000000000000000000000000000000000000000..a0b8620281373058002b0d893d5a00ddbe4b2ef4 --- /dev/null +++ b/app/asr_models/faster_whisper_engine.py @@ -0,0 +1,96 @@ +import time +from io import StringIO +from threading import Thread +from typing import BinaryIO, Union + +import whisper +from faster_whisper import WhisperModel + +from app.asr_models.asr_model import ASRModel +from app.config import CONFIG +from app.utils import ResultWriter, WriteJSON, WriteSRT, WriteTSV, WriteTXT, WriteVTT + + +class FasterWhisperASR(ASRModel): + + def load_model(self): + + self.model = WhisperModel( + model_size_or_path=CONFIG.MODEL_NAME, + device=CONFIG.DEVICE, + compute_type=CONFIG.MODEL_QUANTIZATION, + download_root=CONFIG.MODEL_PATH + ) + + Thread(target=self.monitor_idleness, daemon=True).start() + + def transcribe( + self, + audio, + task: Union[str, None], + language: Union[str, None], + initial_prompt: Union[str, None], + vad_filter: Union[bool, None], + word_timestamps: Union[bool, None], + output, + ): + self.last_activity_time = time.time() + + with self.model_lock: + if self.model is None: self.load_model() + + options_dict = {"task": task} + if language: + options_dict["language"] = language + if initial_prompt: + options_dict["initial_prompt"] = initial_prompt + if vad_filter: + options_dict["vad_filter"] = True + if word_timestamps: + options_dict["word_timestamps"] = True + with self.model_lock: + segments = [] + text = "" + segment_generator, info = self.model.transcribe(audio, beam_size=5, **options_dict) + for segment in segment_generator: + segments.append(segment) + text = text + segment.text + result = {"language": options_dict.get("language", info.language), "segments": segments, "text": text} + + output_file = StringIO() + self.write_result(result, output_file, output) + output_file.seek(0) + + return output_file + + def language_detection(self, audio): + + self.last_activity_time = time.time() + + with self.model_lock: + if self.model is None: self.load_model() + + # load audio and pad/trim it to fit 30 seconds + audio = whisper.pad_or_trim(audio) + + # detect the spoken language + with self.model_lock: + segments, info = self.model.transcribe(audio, beam_size=5) + detected_lang_code = info.language + detected_language_confidence = info.language_probability + + return detected_lang_code, detected_language_confidence + + def write_result(self, result: dict, file: BinaryIO, output: Union[str, None]): + if output == "srt": + WriteSRT(ResultWriter).write_result(result, file=file) + elif output == "vtt": + WriteVTT(ResultWriter).write_result(result, file=file) + elif output == "tsv": + WriteTSV(ResultWriter).write_result(result, file=file) + elif output == "json": + WriteJSON(ResultWriter).write_result(result, file=file) + elif output == "txt": + WriteTXT(ResultWriter).write_result(result, file=file) + else: + return "Please select an output method!" diff --git a/app/asr_models/openai_whisper_engine.py b/app/asr_models/openai_whisper_engine.py new file mode 100644 index 0000000000000000000000000000000000000000..c6c37dc9346ec4380d12507259f87b41fdea695e --- /dev/null +++ b/app/asr_models/openai_whisper_engine.py @@ -0,0 +1,95 @@ +import time +from io import StringIO +from threading import Thread +from typing import BinaryIO, Union + +import torch +import whisper +from whisper.utils import ResultWriter, WriteJSON, WriteSRT, WriteTSV, WriteTXT, WriteVTT + +from app.asr_models.asr_model import ASRModel +from app.config import CONFIG + + +class OpenAIWhisperASR(ASRModel): + + def load_model(self): + + if torch.cuda.is_available(): + self.model = whisper.load_model( + name=CONFIG.MODEL_NAME, + download_root=CONFIG.MODEL_PATH + ).cuda() + else: + self.model = whisper.load_model( + name=CONFIG.MODEL_NAME, + download_root=CONFIG.MODEL_PATH + ) + + Thread(target=self.monitor_idleness, daemon=True).start() + + def transcribe( + self, + audio, + task: Union[str, None], + language: Union[str, None], + initial_prompt: Union[str, None], + vad_filter: Union[bool, None], + word_timestamps: Union[bool, None], + output, + ): + self.last_activity_time = time.time() + + with self.model_lock: + if self.model is None: self.load_model() + + options_dict = {"task": task} + if language: + options_dict["language"] = language + if initial_prompt: + options_dict["initial_prompt"] = initial_prompt + if word_timestamps: + options_dict["word_timestamps"] = word_timestamps + with self.model_lock: + result = self.model.transcribe(audio, **options_dict) + + output_file = StringIO() + self.write_result(result, output_file, output) + output_file.seek(0) + + return output_file + + def language_detection(self, audio): + + self.last_activity_time = time.time() + + with self.model_lock: + if self.model is None: self.load_model() + + # load audio and pad/trim it to fit 30 seconds + audio = whisper.pad_or_trim(audio) + + # make log-Mel spectrogram and move to the same device as the model + mel = whisper.log_mel_spectrogram(audio, self.model.dims.n_mels).to(self.model.device) + + # detect the spoken language + with self.model_lock: + _, probs = self.model.detect_language(mel) + detected_lang_code = max(probs, key=probs.get) + + return detected_lang_code, probs[max(probs)] + + def write_result(self, result: dict, file: BinaryIO, output: Union[str, None]): + options = {"max_line_width": 1000, "max_line_count": 10, "highlight_words": False} + if output == "srt": + WriteSRT(ResultWriter).write_result(result, file=file, options=options) + elif output == "vtt": + WriteVTT(ResultWriter).write_result(result, file=file, options=options) + elif output == "tsv": + WriteTSV(ResultWriter).write_result(result, file=file, options=options) + elif output == "json": + WriteJSON(ResultWriter).write_result(result, file=file, options=options) + elif output == "txt": + WriteTXT(ResultWriter).write_result(result, file=file, options=options) + else: + return "Please select an output method!" diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000000000000000000000000000000000000..64202697feb58f622ddf3f8a0270cc38ba851262 --- /dev/null +++ b/app/config.py @@ -0,0 +1,37 @@ +import os + +import torch + + +class CONFIG: + """ + Configuration class for ASR models. + Reads environment variables for runtime configuration, with sensible defaults. + """ + # Determine the ASR engine ('faster_whisper' or 'openai_whisper') + ASR_ENGINE = os.getenv("ASR_ENGINE", "openai_whisper") + + # Determine the computation device (GPU or CPU) + DEVICE = os.getenv("ASR_DEVICE", "cuda" if torch.cuda.is_available() else "cpu") + + # Model name to use (e.g., "base", "small", etc.) + MODEL_NAME = os.getenv("ASR_MODEL", "base") + + # Path to the model directory + MODEL_PATH = os.getenv("ASR_MODEL_PATH", os.path.join(os.path.expanduser("~"), ".cache", "whisper")) + + # Model quantization level. Defines the precision for model weights: + # 'float32' - 32-bit floating-point precision (higher precision, slower inference) + # 'float16' - 16-bit floating-point precision (lower precision, faster inference) + # 'int8' - 8-bit integer precision (lowest precision, fastest inference) + # Defaults to 'float32' for GPU availability, 'int8' for CPU. + MODEL_QUANTIZATION = os.getenv("ASR_QUANTIZATION", "float32" if torch.cuda.is_available() else "int8") + if MODEL_QUANTIZATION not in {"float32", "float16", "int8"}: + raise ValueError("Invalid MODEL_QUANTIZATION. Choose 'float32', 'float16', or 'int8'.") + + # Idle timeout in seconds. If set to a non-zero value, the model will be unloaded + # after being idle for this many seconds. A value of 0 means the model will never be unloaded. + MODEL_IDLE_TIMEOUT = int(os.getenv("MODEL_IDLE_TIMEOUT", 0)) + + # Default sample rate for audio input. 16 kHz is commonly used in speech-to-text tasks. + SAMPLE_RATE = int(os.getenv("SAMPLE_RATE", 16000)) diff --git a/app/factory/asr_model_factory.py b/app/factory/asr_model_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..0f97fb53ae0a5ca20caec09dc40c388efcdb863a --- /dev/null +++ b/app/factory/asr_model_factory.py @@ -0,0 +1,15 @@ +from app.asr_models.asr_model import ASRModel +from app.asr_models.faster_whisper_engine import FasterWhisperASR +from app.asr_models.openai_whisper_engine import OpenAIWhisperASR +from app.config import CONFIG + + +class ASRModelFactory: + @staticmethod + def create_asr_model() -> ASRModel: + if CONFIG.ASR_ENGINE == "openai_whisper": + return OpenAIWhisperASR() + elif CONFIG.ASR_ENGINE == "faster_whisper": + return FasterWhisperASR() + else: + raise ValueError(f"Unsupported ASR engine: {CONFIG.ASR_ENGINE}") diff --git a/app/faster_whisper/core.py b/app/faster_whisper/core.py deleted file mode 100644 index 5871d3bb87188112c4c7dec2a9dded3001921af9..0000000000000000000000000000000000000000 --- a/app/faster_whisper/core.py +++ /dev/null @@ -1,132 +0,0 @@ -import os -from io import StringIO -from threading import Lock, Thread -from typing import BinaryIO, Union -import time -import gc - -import torch -import whisper -from faster_whisper import WhisperModel - -from .utils import ResultWriter, WriteJSON, WriteSRT, WriteTSV, WriteTXT, WriteVTT - -model_name = os.getenv("ASR_MODEL", "base") -model_path = os.getenv("ASR_MODEL_PATH", os.path.join(os.path.expanduser("~"), ".cache", "whisper")) -model = None -model_lock = Lock() - -# More about available quantization levels is here: -# https://opennmt.net/CTranslate2/quantization.html - -last_activity_time = time.time() -idle_timeout = int(os.getenv("IDLE_TIMEOUT", 0)) # default to being disabled - -def monitor_idleness(): - global model - if(idle_timeout <= 0): return - while True: - time.sleep(15) - if time.time() - last_activity_time > idle_timeout: - with model_lock: - release_model() - break - -def load_model(): - global model, device, model_quantization - - if torch.cuda.is_available(): - device = "cuda" - model_quantization = os.getenv("ASR_QUANTIZATION", "float32") - else: - device = "cpu" - model_quantization = os.getenv("ASR_QUANTIZATION", "int8") - - model = WhisperModel( - model_size_or_path=model_name, device=device, compute_type=model_quantization, download_root=model_path - ) - - Thread(target=monitor_idleness, daemon=True).start() - -load_model() - -def release_model(): - global model - del model - torch.cuda.empty_cache() - gc.collect() - model = None - print("Model unloaded due to timeout") - -def transcribe( - audio, - task: Union[str, None], - language: Union[str, None], - initial_prompt: Union[str, None], - vad_filter: Union[bool, None], - word_timestamps: Union[bool, None], - output, -): - global last_activity_time - last_activity_time = time.time() - - with model_lock: - if(model is None): load_model() - - options_dict = {"task": task} - if language: - options_dict["language"] = language - if initial_prompt: - options_dict["initial_prompt"] = initial_prompt - if vad_filter: - options_dict["vad_filter"] = True - if word_timestamps: - options_dict["word_timestamps"] = True - with model_lock: - segments = [] - text = "" - segment_generator, info = model.transcribe(audio, beam_size=5, **options_dict) - for segment in segment_generator: - segments.append(segment) - text = text + segment.text - result = {"language": options_dict.get("language", info.language), "segments": segments, "text": text} - - output_file = StringIO() - write_result(result, output_file, output) - output_file.seek(0) - - return output_file - - -def language_detection(audio): - global last_activity_time - last_activity_time = time.time() - - with model_lock: - if(model is None): load_model() - - # load audio and pad/trim it to fit 30 seconds - audio = whisper.pad_or_trim(audio) - - # detect the spoken language - with model_lock: - segments, info = model.transcribe(audio, beam_size=5) - detected_lang_code = info.language - detected_language_confidence = info.language_probability - - return detected_lang_code, detected_language_confidence - - -def write_result(result: dict, file: BinaryIO, output: Union[str, None]): - if output == "srt": - WriteSRT(ResultWriter).write_result(result, file=file) - elif output == "vtt": - WriteVTT(ResultWriter).write_result(result, file=file) - elif output == "tsv": - WriteTSV(ResultWriter).write_result(result, file=file) - elif output == "json": - WriteJSON(ResultWriter).write_result(result, file=file) - elif output == "txt": - WriteTXT(ResultWriter).write_result(result, file=file) - else: - return "Please select an output method!" diff --git a/app/openai_whisper/core.py b/app/openai_whisper/core.py deleted file mode 100644 index 1380e8423fbd06b73f7534c4ed8b9a7c3ce91af6..0000000000000000000000000000000000000000 --- a/app/openai_whisper/core.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -from io import StringIO -from threading import Lock, Thread -from typing import BinaryIO, Union -import time -import gc - -import torch -import whisper -from whisper.utils import ResultWriter, WriteJSON, WriteSRT, WriteTSV, WriteTXT, WriteVTT - -model_name = os.getenv("ASR_MODEL", "base") -model_path = os.getenv("ASR_MODEL_PATH", os.path.join(os.path.expanduser("~"), ".cache", "whisper")) -model = None -model_lock = Lock() - -last_activity_time = time.time() -idle_timeout = int(os.getenv("IDLE_TIMEOUT", 0)) # default to being disabled - -def monitor_idleness(): - global model - if(idle_timeout <= 0): return - while True: - time.sleep(15) # check every minute - if time.time() - last_activity_time > idle_timeout: - with model_lock: - release_model() - break - -def load_model(): - global model - - if torch.cuda.is_available(): - model = whisper.load_model(model_name, download_root=model_path).cuda() - else: - model = whisper.load_model(model_name, download_root=model_path) - - Thread(target=monitor_idleness, daemon=True).start() - -load_model() - -def release_model(): - global model - del model - torch.cuda.empty_cache() - gc.collect() - model = None - print("Model unloaded due to timeout") - -def transcribe( - audio, - task: Union[str, None], - language: Union[str, None], - initial_prompt: Union[str, None], - vad_filter: Union[bool, None], - word_timestamps: Union[bool, None], - output, -): - global last_activity_time - last_activity_time = time.time() - - with model_lock: - if(model is None): load_model() - - options_dict = {"task": task} - if language: - options_dict["language"] = language - if initial_prompt: - options_dict["initial_prompt"] = initial_prompt - if word_timestamps: - options_dict["word_timestamps"] = word_timestamps - with model_lock: - result = model.transcribe(audio, **options_dict) - - output_file = StringIO() - write_result(result, output_file, output) - output_file.seek(0) - - return output_file - - -def language_detection(audio): - global last_activity_time - last_activity_time = time.time() - - with model_lock: - if(model is None): load_model() - - # load audio and pad/trim it to fit 30 seconds - audio = whisper.pad_or_trim(audio) - - # make log-Mel spectrogram and move to the same device as the model - mel = whisper.log_mel_spectrogram(audio, model.dims.n_mels).to(model.device) - - # detect the spoken language - with model_lock: - _, probs = model.detect_language(mel) - detected_lang_code = max(probs, key=probs.get) - - return detected_lang_code, probs[max(probs)] - - -def write_result(result: dict, file: BinaryIO, output: Union[str, None]): - options = {"max_line_width": 1000, "max_line_count": 10, "highlight_words": False} - if output == "srt": - WriteSRT(ResultWriter).write_result(result, file=file, options=options) - elif output == "vtt": - WriteVTT(ResultWriter).write_result(result, file=file, options=options) - elif output == "tsv": - WriteTSV(ResultWriter).write_result(result, file=file, options=options) - elif output == "json": - WriteJSON(ResultWriter).write_result(result, file=file, options=options) - elif output == "txt": - WriteTXT(ResultWriter).write_result(result, file=file, options=options) - else: - return "Please select an output method!" diff --git a/app/faster_whisper/utils.py b/app/utils.py similarity index 67% rename from app/faster_whisper/utils.py rename to app/utils.py index 034c63937a304372999f0b9b60793e04208e5d5e..0b51281a0793aef2063ad475fd8a42b05ab94481 100644 --- a/app/faster_whisper/utils.py +++ b/app/utils.py @@ -1,9 +1,13 @@ import json import os -from typing import TextIO +from typing import TextIO, BinaryIO +import ffmpeg +import numpy as np from faster_whisper.utils import format_timestamp +from app.config import CONFIG + class ResultWriter: extension: str @@ -85,3 +89,36 @@ class WriteJSON(ResultWriter): def write_result(self, result: dict, file: TextIO): json.dump(result, file) + + +def load_audio(file: BinaryIO, encode=True, sr: int = CONFIG.SAMPLE_RATE): + """ + Open an audio file object and read as mono waveform, resampling as necessary. + Modified from https://github.com/openai/whisper/blob/main/whisper/audio.py to accept a file object + Parameters + ---------- + file: BinaryIO + The audio file like object + encode: Boolean + If true, encode audio stream to WAV before sending to whisper + sr: int + The sample rate to resample the audio if necessary + Returns + ------- + A NumPy array containing the audio waveform, in float32 dtype. + """ + if encode: + try: + # This launches a subprocess to decode audio while down-mixing and resampling as necessary. + # Requires the ffmpeg CLI and `ffmpeg-python` package to be installed. + out, _ = ( + ffmpeg.input("pipe:", threads=0) + .output("-", format="s16le", acodec="pcm_s16le", ac=1, ar=sr) + .run(cmd="ffmpeg", capture_stdout=True, capture_stderr=True, input=file.read()) + ) + except ffmpeg.Error as e: + raise RuntimeError(f"Failed to load audio: {e.stderr.decode()}") from e + else: + out = file.read() + + return np.frombuffer(out, np.int16).flatten().astype(np.float32) / 32768.0 diff --git a/app/webservice.py b/app/webservice.py index fce652cd1a98fefc006459b068ecc7bd8bc70581..7158645a467702a07baf8b997ce825712863ccf1 100644 --- a/app/webservice.py +++ b/app/webservice.py @@ -1,12 +1,10 @@ import importlib.metadata import os from os import path -from typing import Annotated, BinaryIO, Optional, Union +from typing import Annotated, Optional, Union from urllib.parse import quote import click -import ffmpeg -import numpy as np import uvicorn from fastapi import FastAPI, File, Query, UploadFile, applications from fastapi.openapi.docs import get_swagger_ui_html @@ -14,13 +12,13 @@ from fastapi.responses import RedirectResponse, StreamingResponse from fastapi.staticfiles import StaticFiles from whisper import tokenizer -ASR_ENGINE = os.getenv("ASR_ENGINE", "openai_whisper") -if ASR_ENGINE == "faster_whisper": - from app.faster_whisper.core import language_detection, transcribe -else: - from app.openai_whisper.core import language_detection, transcribe +from app.config import CONFIG +from app.factory.asr_model_factory import ASRModelFactory +from app.utils import load_audio + +asr_model = ASRModelFactory.create_asr_model() +asr_model.load_model() -SAMPLE_RATE = 16000 LANGUAGE_CODES = sorted(tokenizer.LANGUAGES.keys()) projectMetadata = importlib.metadata.metadata("whisper-asr-webservice") @@ -37,6 +35,7 @@ assets_path = os.getcwd() + "/swagger-ui-assets" if path.exists(assets_path + "/swagger-ui.css") and path.exists(assets_path + "/swagger-ui-bundle.js"): app.mount("/assets", StaticFiles(directory=assets_path), name="static") + def swagger_monkey_patch(*args, **kwargs): return get_swagger_ui_html( *args, @@ -46,6 +45,7 @@ if path.exists(assets_path + "/swagger-ui.css") and path.exists(assets_path + "/ swagger_js_url="/assets/swagger-ui-bundle.js", ) + applications.get_swagger_ui_html = swagger_monkey_patch @@ -56,29 +56,29 @@ async def index(): @app.post("/asr", tags=["Endpoints"]) async def asr( - audio_file: UploadFile = File(...), # noqa: B008 - encode: bool = Query(default=True, description="Encode audio first through ffmpeg"), - task: Union[str, None] = Query(default="transcribe", enum=["transcribe", "translate"]), - language: Union[str, None] = Query(default=None, enum=LANGUAGE_CODES), - initial_prompt: Union[str, None] = Query(default=None), - vad_filter: Annotated[ - bool | None, - Query( - description="Enable the voice activity detection (VAD) to filter out parts of the audio without speech", - include_in_schema=(True if ASR_ENGINE == "faster_whisper" else False), - ), - ] = False, - word_timestamps: bool = Query(default=False, description="Word level timestamps"), - output: Union[str, None] = Query(default="txt", enum=["txt", "vtt", "srt", "tsv", "json"]), + audio_file: UploadFile = File(...), # noqa: B008 + encode: bool = Query(default=True, description="Encode audio first through ffmpeg"), + task: Union[str, None] = Query(default="transcribe", enum=["transcribe", "translate"]), + language: Union[str, None] = Query(default=None, enum=LANGUAGE_CODES), + initial_prompt: Union[str, None] = Query(default=None), + vad_filter: Annotated[ + bool | None, + Query( + description="Enable the voice activity detection (VAD) to filter out parts of the audio without speech", + include_in_schema=(True if CONFIG.ASR_ENGINE == "faster_whisper" else False), + ), + ] = False, + word_timestamps: bool = Query(default=False, description="Word level timestamps"), + output: Union[str, None] = Query(default="txt", enum=["txt", "vtt", "srt", "tsv", "json"]), ): - result = transcribe( + result = asr_model.transcribe( load_audio(audio_file.file, encode), task, language, initial_prompt, vad_filter, word_timestamps, output ) return StreamingResponse( result, media_type="text/plain", headers={ - "Asr-Engine": ASR_ENGINE, + "Asr-Engine": CONFIG.ASR_ENGINE, "Content-Disposition": f'attachment; filename="{quote(audio_file.filename)}.{output}"', }, ) @@ -86,44 +86,13 @@ async def asr( @app.post("/detect-language", tags=["Endpoints"]) async def detect_language( - audio_file: UploadFile = File(...), # noqa: B008 - encode: bool = Query(default=True, description="Encode audio first through FFmpeg"), + audio_file: UploadFile = File(...), # noqa: B008 + encode: bool = Query(default=True, description="Encode audio first through FFmpeg"), ): - detected_lang_code, confidence = language_detection(load_audio(audio_file.file, encode)) - return {"detected_language": tokenizer.LANGUAGES[detected_lang_code], "language_code": detected_lang_code, "confidence": confidence} - - -def load_audio(file: BinaryIO, encode=True, sr: int = SAMPLE_RATE): - """ - Open an audio file object and read as mono waveform, resampling as necessary. - Modified from https://github.com/openai/whisper/blob/main/whisper/audio.py to accept a file object - Parameters - ---------- - file: BinaryIO - The audio file like object - encode: Boolean - If true, encode audio stream to WAV before sending to whisper - sr: int - The sample rate to resample the audio if necessary - Returns - ------- - A NumPy array containing the audio waveform, in float32 dtype. - """ - if encode: - try: - # This launches a subprocess to decode audio while down-mixing and resampling as necessary. - # Requires the ffmpeg CLI and `ffmpeg-python` package to be installed. - out, _ = ( - ffmpeg.input("pipe:", threads=0) - .output("-", format="s16le", acodec="pcm_s16le", ac=1, ar=sr) - .run(cmd="ffmpeg", capture_stdout=True, capture_stderr=True, input=file.read()) - ) - except ffmpeg.Error as e: - raise RuntimeError(f"Failed to load audio: {e.stderr.decode()}") from e - else: - out = file.read() - - return np.frombuffer(out, np.int16).flatten().astype(np.float32) / 32768.0 + detected_lang_code, confidence = asr_model.language_detection(load_audio(audio_file.file, encode)) + return {"detected_language": tokenizer.LANGUAGES[detected_lang_code], "language_code": detected_lang_code, + "confidence": confidence} + @click.command() @click.option( @@ -142,10 +111,11 @@ def load_audio(file: BinaryIO, encode=True, sr: int = SAMPLE_RATE): ) @click.version_option(version=projectMetadata["Version"]) def start( - host: str, - port: Optional[int] = None + host: str, + port: Optional[int] = None ): uvicorn.run(app, host=host, port=port) + if __name__ == "__main__": start() diff --git a/docs/environmental-variables.md b/docs/environmental-variables.md index 91bc35b95a8e7c651a2bb9d6aa337f3de579d708..d786927b8f861b806517443e7110eae85f7e7e33 100644 --- a/docs/environmental-variables.md +++ b/docs/environmental-variables.md @@ -1,13 +1,13 @@ ### Configuring the `Engine` === ":octicons-file-code-16: `openai_whisper`" - ```sh - export ASR_ENGINE=openai_whisper - ``` +```sh +export ASR_ENGINE=openai_whisper +``` === ":octicons-file-code-16: `faster_whisper`" - ```sh - export ASR_ENGINE=faster_whisper - ``` +```sh +export ASR_ENGINE=faster_whisper +``` ### Configuring the `Model` @@ -15,9 +15,10 @@ export ASR_MODEL=base ``` -Available ASR_MODELs are `tiny`, `base`, `small`, `medium`, `large`, `large-v1`, `large-v2`, `large-v3`, `turbo`(only OpenAI Whisper) and `large-v3-turbo`(only OpenAI Whisper). +Available ASR_MODELs are `tiny`, `base`, `small`, `medium`, `large`, `large-v1`, `large-v2`, `large-v3`, `turbo` and `large-v3-turbo`. -For English-only applications, the `.en` models tend to perform better, especially for the `tiny.en` and `base.en` models. We observed that the difference becomes less significant for the `small.en` and `medium.en` models. +For English-only applications, the `.en` models tend to perform better, especially for the `tiny.en` and `base.en` +models. We observed that the difference becomes less significant for the `small.en` and `medium.en` models. ### Configuring the `Model Path` @@ -28,7 +29,18 @@ export ASR_MODEL_PATH=/data/whisper ### Configuring the `Model Unloading Timeout` ```sh -export IDLE_TIMEOUT=300 +export MODEL_IDLE_TIMEOUT=300 ``` -Defaults to 0. After no activity for this period (in seconds), unload the model until it is requested again. Setting `0` disables the timeout, keeping the model loaded indefinitely. +Defaults to `0`. After no activity for this period (in seconds), unload the model until it is requested again. Setting +`0` disables the timeout, keeping the model loaded indefinitely. + +### Configuring the `SAMPLE_RATE` + +```sh +export SAMPLE_RATE=16000 +``` + +Defaults to `16000`. Default sample rate for audio input. `16 kHz` is commonly used in `speech-to-text` tasks. + + diff --git a/poetry.lock b/poetry.lock index 914b15207643bd18cea413c5bedf0a8434d7ddb4..2b4f5de337cf20c909f94cdf546b56d8cf497ecc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -21,34 +21,6 @@ doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd- test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (<0.22)"] -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - -[[package]] -name = "attrs" -version = "23.1.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] - [[package]] name = "av" version = "11.0.0" @@ -119,33 +91,33 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" -version = "24.4.2" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -159,7 +131,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -355,18 +327,18 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.115.0" +version = "0.115.6" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.0-py3-none-any.whl", hash = "sha256:17ea427674467486e997206a5ab25760f6b09e069f099b96f5b55a32fb6f1631"}, - {file = "fastapi-0.115.0.tar.gz", hash = "sha256:f93b4ca3529a8ebc6fc3fcf710e5efa8de3df9b41570958abf1d97d843138004"}, + {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, + {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.39.0" +starlette = ">=0.40.0,<0.42.0" typing-extensions = ">=4.8.0" [package.extras] @@ -375,21 +347,22 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt [[package]] name = "faster-whisper" -version = "1.0.3" +version = "1.1.0" description = "Faster Whisper transcription with CTranslate2" optional = false python-versions = ">=3.8" files = [ - {file = "faster-whisper-1.0.3.tar.gz", hash = "sha256:1a145db86450b56aaa623c8df7d4ef86e8a1159900f60533e2890e98e8453a17"}, - {file = "faster_whisper-1.0.3-py3-none-any.whl", hash = "sha256:364d0e378ab232ed26f39656e5c98548b38045224e206b20f7d8c90e2745b9d3"}, + {file = "faster-whisper-1.1.0.tar.gz", hash = "sha256:cea4bba5d4527173fdbacafa56f2ffb17dd322688f6c3fdf5fd7b6b6c193ce17"}, + {file = "faster_whisper-1.1.0-py3-none-any.whl", hash = "sha256:0f2d025676bbff1e46c4108b6f9a82578d6e33826c174af2990e45b33fab6182"}, ] [package.dependencies] -av = ">=11.0,<13" +av = ">=11" ctranslate2 = ">=4.0,<5" huggingface-hub = ">=0.13" onnxruntime = ">=1.14,<2" tokenizers = ">=0.13,<1" +tqdm = "*" [package.extras] conversion = ["transformers[torch] (>=4.23)"] @@ -478,56 +451,58 @@ files = [ [[package]] name = "httptools" -version = "0.5.0" +version = "0.6.4" description = "A collection of framework independent HTTP protocol utils." optional = false -python-versions = ">=3.5.0" -files = [ - {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, - {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, - {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, - {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"}, - {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, - {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, - {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, - {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, - {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, - {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, - {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"}, - {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, - {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, - {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, - {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, - {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, - {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"}, - {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, - {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, - {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, - {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, - {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, - {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"}, - {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, - {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, - {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, - {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, - {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, - {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, - {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"}, - {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, - {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, - {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, - {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, - {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, - {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, - {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"}, - {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, - {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, - {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, - {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, +python-versions = ">=3.8.0" +files = [ + {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, + {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"}, + {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"}, + {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"}, + {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"}, + {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"}, + {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"}, + {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"}, + {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"}, ] [package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] +test = ["Cython (>=0.29.24)"] [[package]] name = "huggingface-hub" @@ -739,13 +714,13 @@ files = [ [[package]] name = "mkdocs" -version = "1.6.0" +version = "1.6.1" description = "Project documentation with Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, - {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, ] [package.dependencies] @@ -785,13 +760,13 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.5.27" +version = "9.5.49" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.27-py3-none-any.whl", hash = "sha256:af8cc263fafa98bb79e9e15a8c966204abf15164987569bd1175fd66a7705182"}, - {file = "mkdocs_material-9.5.27.tar.gz", hash = "sha256:a7d4a35f6d4a62b0c43a0cfe7e987da0980c13587b5bc3c26e690ad494427ec0"}, + {file = "mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e"}, + {file = "mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d"}, ] [package.dependencies] @@ -1052,13 +1027,13 @@ type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1087,17 +1062,6 @@ files = [ {file = "protobuf-4.22.3.tar.gz", hash = "sha256:23452f2fdea754a8251d0fc88c0317735ae47217e0d27bf330a30eec2848811a"}, ] -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - [[package]] name = "pydantic" version = "1.10.7" @@ -1166,13 +1130,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.8.1" +version = "10.12" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.8.1-py3-none-any.whl", hash = "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb"}, - {file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"}, + {file = "pymdown_extensions-10.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77"}, + {file = "pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"}, ] [package.dependencies] @@ -1195,27 +1159,25 @@ files = [ [[package]] name = "pytest" -version = "6.2.5" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "python-dateutil" @@ -1247,13 +1209,13 @@ cli = ["click (>=5.0)"] [[package]] name = "python-multipart" -version = "0.0.12" +version = "0.0.20" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_multipart-0.0.12-py3-none-any.whl", hash = "sha256:43dcf96cf65888a9cd3423544dd0d75ac10f7aa0c3c28a175bbcd00c9ce1aebf"}, - {file = "python_multipart-0.0.12.tar.gz", hash = "sha256:045e1f98d719c1ce085ed7f7e1ef9d8ccc8c02ba02b5566d5f7521410ced58cb"}, + {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, + {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, ] [[package]] @@ -1411,29 +1373,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.5.0" +version = "0.8.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"}, - {file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"}, - {file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"}, - {file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"}, - {file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"}, - {file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"}, - {file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"}, - {file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"}, - {file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"}, + {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"}, + {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"}, + {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"}, + {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"}, + {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"}, + {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"}, + {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, ] [[package]] @@ -1476,13 +1438,13 @@ files = [ [[package]] name = "starlette" -version = "0.37.2" +version = "0.41.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, + {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, + {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, ] [package.dependencies] @@ -1604,17 +1566,6 @@ dev = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -1728,20 +1679,21 @@ url = "https://download.pytorch.org/whl/cpu/torch-1.13.1%2Bcpu-cp310-cp310-win_a [[package]] name = "tqdm" -version = "4.66.5" +version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] @@ -1775,20 +1727,20 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.31.0" +version = "0.34.0" description = "The lightning-fast ASGI server." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "uvicorn-0.31.0-py3-none-any.whl", hash = "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced"}, - {file = "uvicorn-0.31.0.tar.gz", hash = "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906"}, + {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, + {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} @@ -1797,7 +1749,7 @@ watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standar websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -2003,4 +1955,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "3a007512112802c1d81dea2788a1e5523f701cf84ba97a163e7ac1882707e119" +content-hash = "ecee871583f0f6a382aa54e3d56027ca899b86784b1ba5a119930f52a3beb7c3" diff --git a/pyproject.toml b/pyproject.toml index a362775ec32cecbda731eb076174bbc9b81a5877..230382e0573c6e264ea16d28e02c6bb46a5d2806 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,15 +18,15 @@ priority = "explicit" [tool.poetry.dependencies] python = "^3.10" -uvicorn = { extras = ["standard"], version = "^0.31.0" } -tqdm = "^4.66.5" -python-multipart = "^0.0.12" +uvicorn = { extras = ["standard"], version = "^0.34.0" } +tqdm = "^4.67.1" +python-multipart = "^0.0.20" ffmpeg-python = "^0.2.0" -fastapi = "^0.115.0" +fastapi = "^0.115.6" llvmlite = "^0.43.0" numba = "^0.60.0" openai-whisper = "^20240930" -faster-whisper = "^1.0.3" +faster-whisper = "^1.1.0" torch = [ { markers = "sys_platform == 'darwin' and platform_machine == 'arm64'", url = "https://download.pytorch.org/whl/cpu/torch-1.13.1-cp310-none-macosx_11_0_arm64.whl" }, { markers = "sys_platform == 'linux' and platform_machine == 'arm64'", url = "https://download.pytorch.org/whl/cpu/torch-1.13.1-cp310-none-macosx_11_0_arm64.whl" }, @@ -37,12 +37,12 @@ torch = [ ] [tool.poetry.dev-dependencies] -pytest = "^6.2.5" -ruff = "^0.5.0" -black = "^24.4.2" -mkdocs = "^1.6.0" -mkdocs-material = "^9.5.27" -pymdown-extensions = "^10.8.1" +pytest = "^8.3.4" +ruff = "^0.8.3" +black = "^24.10.0" +mkdocs = "^1.6.1" +mkdocs-material = "^9.5.49" +pymdown-extensions = "^10.12" [build-system] requires = ["poetry-core>=1.0.0"]