Compare commits
10
Commits
ca2e41e0f5
...
d8b92d5025
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8b92d5025 | ||
|
|
0af80fde24 | ||
|
|
7c8d283103 | ||
|
|
4c06fbdc8d | ||
|
|
39c0e89c67 | ||
|
|
c533da9d86 | ||
|
|
0a7b20f408 | ||
|
|
2c6f7aee8e | ||
|
|
3b1e90a54d | ||
|
|
5b468c87f7 |
@@ -0,0 +1,17 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
logs/
|
||||||
|
history/
|
||||||
|
models/
|
||||||
|
tmp/
|
||||||
|
client.png
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
|
PIP_NO_CACHE_DIR=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg \
|
||||||
|
sox \
|
||||||
|
libmagic1 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install --upgrade pip \
|
||||||
|
&& pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN mkdir -p /app/logs /app/history
|
||||||
|
|
||||||
|
EXPOSE 5042
|
||||||
|
|
||||||
|
CMD ["python", "server.py", "--config", "config.docker.json"]
|
||||||
@@ -63,6 +63,56 @@ pip install -r requirements.txt
|
|||||||
python server.py
|
python server.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
The repository now includes a container setup for running the API in Docker.
|
||||||
|
|
||||||
|
### What gets mounted
|
||||||
|
|
||||||
|
- `./config.docker.json` -> `/app/config.docker.json`
|
||||||
|
- `./logs` -> `/app/logs`
|
||||||
|
- `./history` -> `/app/history`
|
||||||
|
- `./models` -> `/models`
|
||||||
|
|
||||||
|
Place your Whisper model inside `./models/whisper` or update `model_path` in `config.docker.json`.
|
||||||
|
|
||||||
|
### Build and run with Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
The API will be available at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:5042
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build and run with plain Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t whisper-api-server .
|
||||||
|
docker run --rm -p 5042:5042 \
|
||||||
|
-v "$(pwd)/config.docker.json:/app/config.docker.json:ro" \
|
||||||
|
-v "$(pwd)/logs:/app/logs" \
|
||||||
|
-v "$(pwd)/history:/app/history" \
|
||||||
|
-v "$(pwd)/models:/models" \
|
||||||
|
whisper-api-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### GPU note
|
||||||
|
|
||||||
|
The container image installs the same Python dependencies as the local setup, including CUDA-oriented PyTorch wheels on Linux x86_64. To actually use NVIDIA GPU acceleration at runtime, start the container with GPU access enabled in your Docker environment, for example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm --gpus all -p 5042:5042 \
|
||||||
|
-v "$(pwd)/config.docker.json:/app/config.docker.json:ro" \
|
||||||
|
-v "$(pwd)/logs:/app/logs" \
|
||||||
|
-v "$(pwd)/history:/app/history" \
|
||||||
|
-v "$(pwd)/models:/models" \
|
||||||
|
whisper-api-server
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The service is configured through the `config.json` file:
|
The service is configured through the `config.json` file:
|
||||||
@@ -73,14 +123,26 @@ The service is configured through the `config.json` file:
|
|||||||
"model_path": "/path/to/whisper/model",
|
"model_path": "/path/to/whisper/model",
|
||||||
"language": "russian",
|
"language": "russian",
|
||||||
"enable_history": true,
|
"enable_history": true,
|
||||||
|
"max_history_days": 30,
|
||||||
"chunk_length_s": 28,
|
"chunk_length_s": 28,
|
||||||
"batch_size": 8,
|
"batch_size": 6,
|
||||||
"max_new_tokens": 384,
|
"max_new_tokens": 384,
|
||||||
"temperature": 0.01,
|
"temperature": 0.01,
|
||||||
"return_timestamps": false,
|
"return_timestamps": false,
|
||||||
"audio_rate": 8000,
|
"audio_rate": 16000,
|
||||||
"norm_level": "-0.55",
|
"norm_level": "-0.55",
|
||||||
"compand_params": "0.3,1 -90,-90,-70,-50,-40,-15,0,0 -7 0 0.15"
|
"compand_params": "0.3,1 -90,-90,-70,-50,-40,-15,0,0 -7 0 0.15",
|
||||||
|
"device_id": 0,
|
||||||
|
"file_validation": {
|
||||||
|
"max_file_size_mb": 500,
|
||||||
|
"allowed_extensions": [".wav", ".mp3", ".ogg", ".flac", ".m4a", ".oga", ".aac", ".webm"],
|
||||||
|
"allowed_mime_types": ["audio/wav", "audio/mpeg", "audio/ogg", "audio/flac", "audio/mp4", "audio/x-m4a", "audio/aac", "audio/webm"]
|
||||||
|
},
|
||||||
|
"log_level": "INFO",
|
||||||
|
"log_file": "logs/whisper_api.log",
|
||||||
|
"request_logging": {
|
||||||
|
"exclude_endpoints": ["/health", "/static"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -92,6 +154,7 @@ The service is configured through the `config.json` file:
|
|||||||
| `model_path` | Path to the Whisper model directory |
|
| `model_path` | Path to the Whisper model directory |
|
||||||
| `language` | Language for transcription (e.g., "russian", "english") |
|
| `language` | Language for transcription (e.g., "russian", "english") |
|
||||||
| `enable_history` | Whether to save transcription history (true/false) |
|
| `enable_history` | Whether to save transcription history (true/false) |
|
||||||
|
| `max_history_days` | Number of days to keep transcription history before rotation |
|
||||||
| `chunk_length_s` | Length of audio chunks for processing (in seconds) |
|
| `chunk_length_s` | Length of audio chunks for processing (in seconds) |
|
||||||
| `batch_size` | Batch size for processing |
|
| `batch_size` | Batch size for processing |
|
||||||
| `max_new_tokens` | Maximum new tokens for the model output |
|
| `max_new_tokens` | Maximum new tokens for the model output |
|
||||||
@@ -100,6 +163,13 @@ The service is configured through the `config.json` file:
|
|||||||
| `audio_rate` | Audio sampling rate in Hz |
|
| `audio_rate` | Audio sampling rate in Hz |
|
||||||
| `norm_level` | Normalization level for audio preprocessing |
|
| `norm_level` | Normalization level for audio preprocessing |
|
||||||
| `compand_params` | Parameters for audio compression/expansion |
|
| `compand_params` | Parameters for audio compression/expansion |
|
||||||
|
| `device_id` | CUDA device index to use for inference |
|
||||||
|
| `file_validation.max_file_size_mb` | Maximum allowed file size in megabytes |
|
||||||
|
| `file_validation.allowed_extensions` | List of accepted audio file extensions |
|
||||||
|
| `file_validation.allowed_mime_types` | List of accepted MIME types |
|
||||||
|
| `log_level` | Logging level (DEBUG, INFO, WARNING, ERROR) |
|
||||||
|
| `log_file` | Path to the log file |
|
||||||
|
| `request_logging.exclude_endpoints` | Endpoints excluded from request logging |
|
||||||
|
|
||||||
## Web interface
|
## Web interface
|
||||||
|
|
||||||
@@ -159,14 +229,33 @@ curl -X POST http://localhost:5042/v1/audio/transcriptions/base64 \
|
|||||||
-d '{"file":"base64_encoded_audio_data"}'
|
-d '{"file":"base64_encoded_audio_data"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Transcribe a local file on the server
|
### Transcribe asynchronously
|
||||||
|
|
||||||
|
Submit a file for background transcription and receive a task ID:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:5042/local/transcriptions \
|
curl -X POST http://localhost:5042/v1/audio/transcriptions/async \
|
||||||
-H "Content-Type: application/json" \
|
-F file=@audio.mp3
|
||||||
-d '{"file_path":"/path/to/audio.mp3"}'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
{"task_id": "abc123..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get async task status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:5042/v1/tasks/<task_id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Response when completed:
|
||||||
|
```json
|
||||||
|
{"task_id": "abc123...", "status": "completed", "result": {...}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible statuses: `pending`, `completed`, `failed`.
|
||||||
|
|
||||||
### Request with additional parameters
|
### Request with additional parameters
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -215,45 +304,22 @@ curl -X POST http://localhost:5042/v1/audio/transcriptions \
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project structure
|
|
||||||
|
|
||||||
The project consists of the following components:
|
|
||||||
|
|
||||||
- `server.py`: Entry point that initializes and starts the service
|
|
||||||
- `server.sh`: Bash script for launching the server with optional conda environment update
|
|
||||||
- `config.json`: Service configuration file
|
|
||||||
- `app/`: Main application module
|
|
||||||
- `__init__.py`: Contains the `WhisperServiceAPI` class for service initialization
|
|
||||||
- `routes.py`: API route definitions
|
|
||||||
- `history.py`: Saving transcription history
|
|
||||||
- `core/`: Core logic
|
|
||||||
- `transcriber.py`: `WhisperTranscriber` class for speech recognition
|
|
||||||
- `transcription_service.py`: Manages the transcription workflow
|
|
||||||
- `audio/`: Audio processing
|
|
||||||
- `processor.py`: `AudioProcessor` class for audio preprocessing
|
|
||||||
- `sources.py`: Audio source handlers (upload, URL, base64)
|
|
||||||
- `utils.py`: Audio utilities (loading, duration)
|
|
||||||
- `infrastructure/`: Supporting modules
|
|
||||||
- `log.py`: Logging configuration
|
|
||||||
- `validation.py`: File validation
|
|
||||||
- `storage.py`: Temp file management
|
|
||||||
- `async_tasks.py`: Async task manager
|
|
||||||
- `static/`: Web interface files
|
|
||||||
|
|
||||||
## Advanced usage
|
## Advanced usage
|
||||||
|
|
||||||
### Using with different models
|
### Using with different models
|
||||||
|
|
||||||
You can use any Whisper model by changing the `model_path` in the configuration:
|
You can use any Whisper model by changing the `model_path` in the configuration:
|
||||||
|
|
||||||
1. Download a model from Hugging Face (e.g., `openai/whisper-large-v3`)
|
1. Download a model from Hugging Face
|
||||||
2. Update the `model_path` in `config.json`
|
2. Update the `model_path` in `config.json`
|
||||||
3. Restart the service
|
3. Restart the service
|
||||||
|
|
||||||
|
The recommended model for Russian speech recognition is [whisper-large-v3-russian-ties-podlodka-v1.2](https://huggingface.co/Apel-sin/whisper-large-v3-russian-ties-podlodka-v1.2).
|
||||||
|
|
||||||
### Hardware acceleration
|
### Hardware acceleration
|
||||||
|
|
||||||
The service automatically selects the best available compute device:
|
The service automatically selects the best available compute device:
|
||||||
- CUDA GPU (index 1 if available, otherwise index 0)
|
- CUDA GPU (device index configured via `device_id` in `config.json`)
|
||||||
- Apple Silicon MPS (for Mac with M1/M2/M3 chips)
|
- Apple Silicon MPS (for Mac with M1/M2/M3 chips)
|
||||||
- CPU (fallback)
|
- CPU (fallback)
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class AudioProcessor:
|
|||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-hide_banner",
|
"-hide_banner",
|
||||||
"-loglevel", "warning",
|
"-loglevel", "warning",
|
||||||
|
"-y",
|
||||||
"-i", input_path,
|
"-i", input_path,
|
||||||
"-ar", f"{audio_rate}",
|
"-ar", f"{audio_rate}",
|
||||||
"-ac", "1", # Монофонический звук
|
"-ac", "1", # Монофонический звук
|
||||||
|
|||||||
+12
-2
@@ -143,13 +143,23 @@ class WhisperTranscriber:
|
|||||||
use_safetensors=True,
|
use_safetensors=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
use_flash_attn = False
|
||||||
|
if self.device.type == "cuda":
|
||||||
|
# Flash Attention 2 требует архитектуру Ampere или новее (compute capability >= 8.0)
|
||||||
|
capability = torch.cuda.get_device_capability(self.device.index)
|
||||||
|
if capability[0] >= 8:
|
||||||
|
use_flash_attn = True
|
||||||
|
logger.info("GPU поддерживает Flash Attention 2 (compute capability: %d.%d)", *capability)
|
||||||
|
else:
|
||||||
|
logger.info("GPU не поддерживает Flash Attention 2 (compute capability: %d.%d), используется стандартный режим", *capability)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.device.type == "cuda":
|
if use_flash_attn:
|
||||||
model_kwargs["attn_implementation"] = "flash_attention_2"
|
model_kwargs["attn_implementation"] = "flash_attention_2"
|
||||||
self.model = WhisperForConditionalGeneration.from_pretrained(
|
self.model = WhisperForConditionalGeneration.from_pretrained(
|
||||||
self.model_path, **model_kwargs
|
self.model_path, **model_kwargs
|
||||||
).to(self.device)
|
).to(self.device)
|
||||||
if self.device.type == "cuda":
|
if use_flash_attn:
|
||||||
logger.info("Используется Flash Attention 2")
|
logger.info("Используется Flash Attention 2")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Не удалось загрузить модель с Flash Attention: %s", e)
|
logger.warning("Не удалось загрузить модель с Flash Attention: %s", e)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class TranscriptionService:
|
|||||||
Кортеж (JSON-ответ, HTTP-код).
|
Кортеж (JSON-ответ, HTTP-код).
|
||||||
"""
|
"""
|
||||||
params = params or {}
|
params = params or {}
|
||||||
language = params.get('language', self.config.get('language', 'en'))
|
language = params.get('language') or self.config.get('language', 'en')
|
||||||
temperature = max(0.0, min(1.0, float(params.get('temperature', 0.0))))
|
temperature = max(0.0, min(1.0, float(params.get('temperature', 0.0))))
|
||||||
prompt = params.get('prompt', '')
|
prompt = params.get('prompt', '')
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 267 KiB |
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"service_port": 5042,
|
||||||
|
"model_path": "/models/whisper",
|
||||||
|
"language": "russian",
|
||||||
|
"enable_history": true,
|
||||||
|
"max_history_days": 30,
|
||||||
|
"chunk_length_s": 28,
|
||||||
|
"batch_size": 6,
|
||||||
|
"max_new_tokens": 384,
|
||||||
|
"temperature": 0.01,
|
||||||
|
"return_timestamps": false,
|
||||||
|
"audio_rate": 16000,
|
||||||
|
"norm_level": "-0.55",
|
||||||
|
"compand_params": "0.3,1 -90,-90,-70,-50,-40,-15,0,0 -7 0 0.15",
|
||||||
|
"device_id": 0,
|
||||||
|
"file_validation": {
|
||||||
|
"max_file_size_mb": 500,
|
||||||
|
"allowed_extensions": [".wav", ".mp3", ".ogg", ".flac", ".m4a", ".oga", ".aac", ".webm"],
|
||||||
|
"allowed_mime_types": ["audio/wav", "audio/mpeg", "audio/ogg", "audio/flac", "audio/mp4", "audio/x-m4a", "audio/aac", "audio/webm"]
|
||||||
|
},
|
||||||
|
"log_level": "INFO",
|
||||||
|
"log_file": "logs/whisper_api.log",
|
||||||
|
"request_logging": {
|
||||||
|
"exclude_endpoints": ["/health", "/static"]
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"service_port": 5042,
|
"service_port": 5042,
|
||||||
"model_path": "/home/text-generation/models/whisper/podlodka-turbo",
|
"model_path": "/home/text-generation/models/whisper/podlodka-turbo",
|
||||||
"language": "ru",
|
"language": "russian",
|
||||||
"enable_history": true,
|
"enable_history": true,
|
||||||
"max_history_days": 30,
|
"max_history_days": 30,
|
||||||
"chunk_length_s": 28,
|
"chunk_length_s": 28,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
whisper-api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: whisper-api-server
|
||||||
|
ports:
|
||||||
|
- "5042:5042"
|
||||||
|
volumes:
|
||||||
|
- ./config.docker.json:/app/config.docker.json:ro
|
||||||
|
- ./logs:/app/logs
|
||||||
|
- ./history:/app/history
|
||||||
|
- ./models:/models
|
||||||
|
restart: unless-stopped
|
||||||
Reference in New Issue
Block a user