Compare commits

...
10 Commits
Author SHA1 Message Date
Dymas d8b92d5025 Add Docker deployment support 2026-06-06 11:44:30 +02:00
Serge Zaigraeff 0af80fde24 Merge branch 'dev' 2026-03-31 12:07:25 +03:00
Serge Zaigraeff 7c8d283103 README: add recommended model link 2026-03-31 12:07:22 +03:00
Serge Zaigraeff 4c06fbdc8d Merge branch 'dev' 2026-03-31 12:06:23 +03:00
Serge Zaigraeff 39c0e89c67 README: remove project structure section 2026-03-31 12:06:20 +03:00
Serge Zaigraeff c533da9d86 README: add recommended model link, remove project structure section 2026-03-31 12:05:52 +03:00
Serge Zaigraeff 0a7b20f408 Add Flash Attention 2 capability check for older GPUs 2026-03-31 12:01:49 +03:00
Serge Zaigraeff 2c6f7aee8e Update README: add missing config params, async endpoints, fix default values 2026-03-31 11:52:02 +03:00
Serge Zaigraeff 3b1e90a54d Add client.png from main 2026-03-31 11:32:28 +03:00
Serge ZaigraeffandClaude Sonnet 4.6 5b468c87f7 Fix ffmpeg overwrite and empty language fallback
- Add -y flag to ffmpeg to overwrite mkstemp-created empty temp files
  (ffmpeg exits 0 without writing when file exists and stdin is /dev/null)
- Fix language fallback: use `or` instead of dict.get default so empty
  string from client falls back to config value
- Fix language value in config: ru -> russian

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 01:23:34 +03:00
10 changed files with 199 additions and 38 deletions
+17
View File
@@ -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
View File
@@ -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"]
+100 -34
View File
@@ -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)
+1
View File
@@ -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
View File
@@ -143,13 +143,23 @@ class WhisperTranscriber:
use_safetensors=True, use_safetensors=True,
) )
try: use_flash_attn = False
if self.device.type == "cuda": 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:
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)
+1 -1
View File
@@ -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
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

+26
View File
@@ -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
View File
@@ -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,
+14
View File
@@ -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