Обновление. Распознаёт отлично

This commit is contained in:
Mikhail Shardin
2025-09-14 07:26:48 +05:00
parent 1d96b25521
commit 4d2daf510e
2 changed files with 784 additions and 88 deletions

View File

@@ -2,106 +2,470 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
🎙️ [ПЛАН] Распознавание с диаризацией (WhisperX через Docker) 🎙️ 🎙️ Распознавание речи с диаризацией через WhisperX (Docker) 🎙️
Этот файл — заготовка для будущего скрипта, который будет выполнять диаризацию Этот Python-скрипт является оркестратором для пакетной обработки аудиофайлов
(разделение речи по спикерам) с помощью WhisperX. с использованием WhisperX. Он запускает транскрибацию и диаризацию (разделение
по спикерам) в изолированном Docker-контейнере, что решает проблемы
совместимости и обеспечивает стабильную работу на системах с GPU NVIDIA.
ВНИМАНИЕ: Прямая установка WhisperX в локальное Python-окружение вызывает Напоминание: для работы диаризации требуется токен Hugging Face (HF_TOKEN)
конфликты зависимостей, особенно с новыми видеокартами NVIDIA. и принятие лицензий для моделей pyannote.
Поэтому реализация будет основана на **Docker-контейнерах**, что является
более надёжным и воспроизводимым решением.
----------------------------------------------------------------------- Основные задачи:
ПОЧЕМУ DOCKER? - Изоляция зависимостей: Использует готовый Docker-образ, избавляя от ручной
----------------------------------------------------------------------- установки PyTorch, CUDA и других сложных компонентов.
- Поддержка GPU и CPU: Автоматически задействует GPU NVIDIA для максимального
ускорения и может работать в режиме CPU.
- Пакетная обработка: Обрабатывает как отдельные файлы, так и все аудио
в указанной директории (mp3, wav, m4a и др.).
- Централизованный кеш: Сохраняет скачанные модели в общей папке `~/.whisperx/`,
экономя дисковое пространство и время при повторных запусках.
- Гибкая конфигурация: Управляет параметрами (модель, язык, токен) через
внешний файл `config.env`.
- Информативный вывод: Отображает детальный прогресс и итоговую статистику,
включая скорость обработки относительно реального времени.
- Встроенная проверка системы: Команда `--check` позволяет быстро убедиться,
что Docker, GPU и права доступа настроены корректно.
Проблема: Порядок использования:
WhisperX требует определённых версий библиотек (например, PyTorch, torchaudio), 1. Отредактируйте файл `config.env`, указав ваш HF_TOKEN.
которые могут конфликтовать с последними драйверами NVIDIA или другими 2. Поместите аудиофайлы в папку `audio/`.
пакетами в вашей системе. Это классическая "dependency hell". 3. Запустите скрипт:
python3 whisperx_diarization.py
Решение: 4. Результаты (txt, srt, json) появятся в папке `results/`.
NVIDIA предоставляет готовые Docker-контейнеры (через NGC и сообщество),
в которых уже настроено всё необходимое: CUDA, PyTorch и нужные библиотеки.
Это избавляет от ручной настройки и гарантирует, что окружение будет
работать "из коробки". Мы будем использовать готовый образ с WhisperX.
-----------------------------------------------------------------------
ПЛАНИРУЕМЫЙ ФУНКЦИОНАЛ
-----------------------------------------------------------------------
- Распознавание речи с помощью оптимизированной модели Whisper.
- Выравнивание временных меток на уровне слов для высокой точности.
- **Диаризация спикеров** для определения, кто и когда говорит.
- Сохранение результатов в форматах .txt, .srt, .json с разметкой спикеров
(например, "[SPEAKER_01]: Здравствуйте!").
-----------------------------------------------------------------------
ПОРЯДОК ИСПОЛЬЗОВАНИЯ (ПЛАН)
-----------------------------------------------------------------------
Процесс будет включать следующие шаги:
1. **Установка Docker и NVIDIA Container Toolkit.**
Это позволит Docker-контейнерам использовать вашу GPU.
2. **Загрузка готового Docker-образа с WhisperX:**
```bash
docker pull ghcr.io/jim60105/whisperx
```
3. **Запуск контейнера для обработки аудио:**
Этот скрипт (`whisperx_diarization.py`) в будущем станет обёрткой,
автоматизирующей запуск Docker-контейнера для обработки ваших файлов.
Пример команды, которая будет выполняться "под капотом":
```bash
# Токен Hugging Face нужен для скачивания моделей диаризации
export HF_TOKEN=ваш_токен_доступа
docker run --gpus all --rm \
-e HF_TOKEN=$HF_TOKEN \
-v /путь/к/вашим/аудио:/app/audio \
-v /путь/к/результатам:/app/results \
ghcr.io/jim60105/whisperx \
--audio /app/audio/meeting.mp3 \
--output_dir /app/results \
--diarize \
--model large-v3
```
Автор: Михаил Шардин https://shardin.name/ Автор: Михаил Шардин https://shardin.name/
Дата создания: 30.08.2025 Дата создания: 13.09.2025
Версия: 0.2-alpha (План реализации через Docker) Версия: 2.1
Актуальная версия скрипта всегда здесь: https://github.com/empenoso/offline-audio-transcriber Актуальная версия скрипта всегда здесь: https://github.com/empenoso/offline-audio-transcriber
""" """
# import os
# В будущей версии здесь будет код, который формирует и выполняет import sys
# команду `docker run` на основе переданных аргументов (путь к файлам, модель и т.д.). import subprocess
# import argparse
# import subprocess import logging
# import sys from pathlib import Path
# ... from typing import List, Dict, Optional
# import time
from datetime import datetime
import shutil
import itertools
# Определяем базовую директорию относительно местоположения скрипта
SCRIPT_DIR = Path(__file__).parent.resolve()
# Определяем глобальную директорию для кеша моделей в домашней папке пользователя
USER_CACHE_DIR = Path.home() / 'whisperx'
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(SCRIPT_DIR / 'whisperx_diarization.log', encoding='utf-8'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
class Colors:
"""ANSI цвета для консольного вывода"""
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
CYAN = '\033[0;36m'
WHITE = '\033[1;37m'
NC = '\033[0m'
class WhisperXDocker:
"""Класс для работы с WhisperX через Docker"""
def __init__(self, config_path: str = "config.env"):
self.work_dir = SCRIPT_DIR
# ИЗМЕНЕНИЕ: Ссылка на глобальный кеш
self.cache_dir = USER_CACHE_DIR
self.config_path = self.work_dir / config_path
self.config = self._load_config()
self.image_name = "ghcr.io/jim60105/whisperx:latest"
self.use_gpu = self.config.get('DEVICE') == 'cuda'
self._ensure_directories()
def _load_config(self) -> Dict[str, str]:
"""Загружает конфигурацию из файла .env"""
config = {
'HF_TOKEN': '', 'WHISPER_MODEL': 'large-v3', 'LANGUAGE': 'ru',
'BATCH_SIZE': '16', 'DEVICE': 'cuda', 'ENABLE_DIARIZATION': 'true',
'MIN_SPEAKERS': '', 'MAX_SPEAKERS': '', 'COMPUTE_TYPE': 'float16',
'VAD_METHOD': 'pyannote', 'CHUNK_SIZE': '30'
}
if self.config_path.exists():
try:
with open(self.config_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
config[key.strip()] = value.strip().strip('"\'')
except Exception as e:
logger.warning(f"Ошибка загрузки конфигурации: {e}")
logger.info("Создание файла конфигурации по умолчанию...")
self._create_default_config()
else:
logger.info("Файл конфигурации не найден. Создание по умолчанию...")
self._create_default_config()
return config
def _create_default_config(self):
"""Создает файл конфигурации по умолчанию"""
self.config_path.parent.mkdir(parents=True, exist_ok=True)
default_config = """# Конфигурация WhisperX
# HuggingFace токен для диаризации (получите на https://huggingface.co/settings/tokens)
# ВАЖНО: Примите лицензии на:
# https://huggingface.co/pyannote/speaker-diarization-3.1
# https://huggingface.co/pyannote/segmentation-3.0
HF_TOKEN=your_token_here
# Модель Whisper (tiny, base, small, medium, large-v1, large-v2, large-v3)
WHISPER_MODEL=large-v3
# Язык аудио (ru, en, auto для автоопределения)
LANGUAGE=ru
# Размер батча (чем больше - тем быстрее, но больше памяти GPU)
BATCH_SIZE=16
# Устройство для вычислений (cuda или cpu)
DEVICE=cuda
# Включить диаризацию (разделение по спикерам)
ENABLE_DIARIZATION=true
# Минимальное количество спикеров (оставить пустым для автоопределения)
MIN_SPEAKERS=
# Максимальное количество спикеров (оставить пустым для автоопределения)
MAX_SPEAKERS=
# Тип вычислений (float16, float32, int8)
COMPUTE_TYPE=float16
# Метод VAD для обнаружения речи (pyannote, silero)
VAD_METHOD=pyannote
# Размер чанков в секундах
CHUNK_SIZE=30
"""
with open(self.config_path, 'w', encoding='utf-8') as f:
f.write(default_config)
logger.info(f"Создан файл конфигурации: {self.config_path}")
def _ensure_directories(self):
"""Создает необходимые рабочие директории"""
# Создаем локальные папки audio и results
for dir_name in ['audio', 'results']:
p = self.work_dir / dir_name
p.mkdir(parents=True, exist_ok=True)
# Создаем глобальную папку для кеша моделей, если ее нет
self.cache_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"Кеш моделей будет сохраняться в: {self.cache_dir}")
def _run_command(self, cmd: List[str], timeout: int = 45) -> Optional[subprocess.CompletedProcess]:
"""Унифицированная функция для запуска внешних команд"""
try:
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=True, encoding='utf-8')
except FileNotFoundError:
logger.error(f"Команда '{cmd[0]}' не найдена.")
except subprocess.TimeoutExpired:
logger.error(f"Команда '{' '.join(cmd)}' заняла слишком много времени.")
except subprocess.CalledProcessError as e:
logger.error(f"Ошибка выполнения команды. Код: {e.returncode}")
if e.stderr:
logger.error(f"Stderr: {e.stderr.strip()}")
return None
def _check_gpu(self) -> bool:
"""Проверяет доступность GPU через Docker"""
logger.info("Проверка доступа к GPU из Docker...")
cmd = [
'sudo', 'docker', 'run', '--rm', '--gpus', 'all',
'nvidia/cuda:12.4.1-base-ubuntu22.04',
'nvidia-smi', '--query-gpu=name', '--format=csv,noheader'
]
result = self._run_command(cmd)
if result and result.stdout.strip():
logger.info(f"✅ GPU успешно обнаружен: {result.stdout.strip()}")
return True
return False
def _format_time(self, seconds: float) -> str:
"""Форматирует секунды в читаемый вид (ч:м:с)"""
if seconds < 0: return "0.0с"
mins, secs = divmod(seconds, 60)
hours, mins = divmod(mins, 60)
if hours > 0:
return f"{int(hours)}ч {int(mins)}м {int(secs)}с"
elif mins > 0:
return f"{int(mins)}м {int(secs)}с"
else:
return f"{secs:.1f}с"
def _get_audio_duration(self, file_path: Path) -> Optional[float]:
"""Получает длительность аудиофайла через ffprobe"""
if not shutil.which('ffprobe'):
return None
cmd = [
'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
'-of', 'default=noprint_wrappers=1:nokey=1', str(file_path)
]
result = self._run_command(cmd, timeout=15)
try:
return float(result.stdout.strip()) if result and result.stdout.strip() else None
except (ValueError, AttributeError):
return None
def list_audio_files(self, directory: Optional[Path] = None) -> List[Path]:
"""Находит все поддерживаемые аудиофайлы в директории"""
directory = directory or self.work_dir / "audio"
extensions = ['.wav', '.mp3', '.m4a', '.flac', '.ogg', '.aac', '.wma', '.mp4', '.mkv', '.avi']
return sorted([p for p in directory.rglob('*') if p.suffix.lower() in extensions and p.is_file()])
def process_file(self, audio_file: Path, output_dir: Optional[Path] = None) -> bool:
"""Обрабатывает один аудиофайл с помощью WhisperX в Docker"""
output_dir = output_dir or self.work_dir / "results"
file_output_dir = output_dir / audio_file.stem
file_output_dir.mkdir(exist_ok=True)
cmd = ['sudo', 'docker', 'run', '--rm', '--user', f"{os.getuid()}:{os.getgid()}"]
if self.use_gpu:
cmd.extend(['--gpus', 'all'])
cmd.extend([
'-v', f"{audio_file.parent.resolve()}:/audio:ro",
'-v', f"{file_output_dir.resolve()}:/results",
# ИЗМЕНЕНИЕ: Монтируем глобальную директорию кеша в /models внутри контейнера
'-v', f"{self.cache_dir.resolve()}:/models",
'--workdir', '/app',
# ИЗМЕНЕНИЕ: Все пути кеша внутри контейнера теперь указывают на смонтированный том /models
'-e', 'HOME=/models',
'-e', 'HF_HOME=/models/.cache/huggingface',
'-e', 'XDG_CACHE_HOME=/models/.cache',
'-e', 'TORCH_HOME=/models/.cache/torch',
])
hf_token = self.config.get('HF_TOKEN', '').strip()
if hf_token and hf_token != 'your_token_here':
cmd.extend(['-e', f"HF_TOKEN={hf_token}"])
logger.info("✅ HF_TOKEN передан в контейнер")
else:
logger.warning(f"{Colors.YELLOW}⚠️ HF_TOKEN не настроен! Диализация может не работать.{Colors.NC}")
cmd.extend([self.image_name, 'whisperx'])
whisper_args = [
'--output_dir', "/results",
'--model', self.config.get('WHISPER_MODEL', 'large-v3'),
'--language', self.config.get('LANGUAGE', 'ru'),
'--batch_size', self.config.get('BATCH_SIZE', '16'),
'--device', 'cuda' if self.use_gpu else 'cpu',
'--compute_type', self.config.get('COMPUTE_TYPE', 'float16'),
'--output_format', 'all',
'--verbose', 'False'
]
if (self.config.get('ENABLE_DIARIZATION', 'true').lower() == 'true' and hf_token and hf_token != 'your_token_here'):
whisper_args.extend(['--diarize', '--hf_token', hf_token])
for key, name in [('MIN_SPEAKERS', '--min_speakers'), ('MAX_SPEAKERS', '--max_speakers')]:
value = self.config.get(key)
if value and value.isdigit() and int(value) > 0:
whisper_args.extend([name, value])
elif self.config.get('ENABLE_DIARIZATION', 'true').lower() == 'true':
logger.warning("⚠️ Диаризация отключена - нет HF_TOKEN")
whisper_args.append(f"/audio/{audio_file.name}")
cmd.extend(whisper_args)
duration = self._get_audio_duration(audio_file)
logger.info(f"{Colors.CYAN}🎵 Обрабатываем: {audio_file.name}{Colors.NC}")
logger.info(f" 📊 Размер: {audio_file.stat().st_size / (1024*1024):.1f} МБ")
if duration:
logger.info(f" ⏱️ Длительность: {self._format_time(duration)}")
logger.info(f" 📁 Результаты: {file_output_dir}")
start_time = time.time()
logger.info(f"{Colors.YELLOW}🚀 Запуск WhisperX...{Colors.NC}")
try:
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, encoding='utf-8', bufsize=1, universal_newlines=True)
spinner = itertools.cycle(['', '', '', '', '', '', '', ''])
stderr_lines = []
current_status = "Инициализация..."
sys.stdout.write(f" [PROGRESS] {next(spinner)} {current_status}\r")
while process.poll() is None:
line = process.stderr.readline()
if line:
stderr_lines.append(line.strip())
if "Performing VAD" in line or "voice activity detection" in line:
current_status = "1/4 Обнаружение речи (VAD)..."
elif "Performing transcription" in line:
current_status = "2/4 Транскрибация текста..."
elif "Performing alignment" in line:
current_status = "3/4 Выравнивание временных меток..."
elif "Performing diarization" in line:
current_status = f"4/4 Диарізація (може зайняти багато часу)..."
sys.stdout.write(f" [PROGRESS] {next(spinner)} {current_status}\r")
sys.stdout.flush()
time.sleep(0.1)
sys.stdout.write(" " * (len(current_status) + 20) + "\r")
sys.stdout.flush()
process.wait()
if process.returncode == 0:
processing_time = time.time() - start_time
logger.info(f"{Colors.GREEN}✅ Обработка завершена успешно!{Colors.NC}")
logger.info(f" ⏱️ Время обработки: {self._format_time(processing_time)}")
if duration and processing_time > 0:
speed_factor = duration / processing_time
logger.info(f" 🚀 Скорость: {speed_factor:.1f}x от реального времени")
result_files = list(file_output_dir.glob('*'))
if result_files:
logger.info(f" 📄 Создано файлов: {len(result_files)}")
for rf in sorted(result_files):
logger.info(f"{rf.name}")
return True
else:
logger.error(f"{Colors.RED}❌ Ошибка обработки файла {audio_file.name}{Colors.NC}")
logger.error(f" Код возврата Docker: {process.returncode}")
if stderr_lines:
logger.error(" Последние сообщения из лога контейнера:")
for line in stderr_lines[-10:]:
if line.strip():
logger.error(f" [Docker ERR]: {line}")
return False
except Exception as e:
logger.error(f"❌ Критическая ошибка при запуске Docker: {e}")
return False
def process_directory(self, input_dir: Optional[Path] = None):
"""Обрабатывает все аудиофайлы в директории"""
audio_files = self.list_audio_files(input_dir)
if not audio_files:
logger.warning(f"В директории {input_dir or self.work_dir / 'audio'} не найдено аудиофайлов.")
return
logger.info(f"{Colors.CYAN}📁 Найдено {len(audio_files)} аудиофайлов{Colors.NC}")
stats = {"total": len(audio_files), "success": 0, "failed": 0}
start_time = time.time()
for i, audio_file in enumerate(audio_files, 1):
logger.info(f"\n{Colors.WHITE}═══ Файл {i}/{stats['total']} ═══{Colors.NC}")
if self.process_file(audio_file):
stats["success"] += 1
else:
stats["failed"] += 1
logger.info(f"\n{Colors.WHITE}{''*35}{Colors.NC}")
logger.info(f"{Colors.GREEN}🎯 ИТОГИ ОБРАБОТКИ{Colors.NC}")
logger.info(f"{Colors.WHITE}{''*35}{Colors.NC}")
logger.info(f"📊 Всего файлов: {stats['total']}")
logger.info(f"✅ Успешно: {stats['success']}")
logger.info(f"С ошибками: {stats['failed']}")
total_time = time.time() - start_time
logger.info(f"⏱️ Общее время: {self._format_time(total_time)}")
if stats['total'] > 0:
logger.info(f"📈 Среднее время на файл: {self._format_time(total_time / stats['total'])}")
def check_system(self) -> bool:
logger.info(f"{Colors.CYAN}🔍 Проверка системы...{Colors.NC}")
if not self._run_command(['docker', '--version']):
logger.error("❌ Docker не найден. Установите Docker."); return False
logger.info("✅ Docker найден")
if self.use_gpu:
if not self._check_gpu():
logger.warning("⚠️ GPU недоступен через Docker, переключаемся на CPU.")
self.use_gpu = False; self.config['DEVICE'] = 'cpu'
else: logger.info("✅ GPU-ускорение активно")
else: logger.info("⚙️ Режим CPU активен (согласно config.env)")
if not self._run_command(['sudo', 'docker', 'image', 'inspect', self.image_name]):
logger.error(f"❌ Образ WhisperX не найден. Выполните: sudo docker pull {self.image_name}"); return False
logger.info("✅ Образ WhisperX найден")
hf_token = self.config.get('HF_TOKEN', '').strip()
if self.config.get('ENABLE_DIARIZATION', 'true').lower() == 'true':
if not hf_token or hf_token == 'your_token_here':
logger.error(f"❌ HF_TOKEN не настроен в {self.config_path}")
logger.info("💡 Получите токен на https://huggingface.co/settings/tokens и примите лицензии."); return False
else: logger.info("✅ HF_TOKEN настроен")
if not shutil.which('ffprobe'):
logger.warning("⚠️ ffprobe не найден. Длительность аудио не будет отображаться. (sudo apt install ffmpeg)")
#Проверяем права на запись в глобальный кеш, а не в локальную папку
try:
test_file = self.cache_dir / 'test_write.tmp'
test_file.touch(); test_file.unlink()
logger.info(f"✅ Есть права на запись в кеш {self.cache_dir}")
except Exception as e:
logger.error(f"❌ Нет прав на запись в директорию кеша {self.cache_dir}: {e}"); return False
logger.info(f"{Colors.GREEN}✅ Система готова к работе{Colors.NC}")
return True
def main(): def main():
"""Информационное сообщение о статусе скрипта.""" """Главная функция-обработчик CLI"""
print("======================================================================") parser = argparse.ArgumentParser(description='🎙️ Распознавание речи с диаризацией через WhisperX (DOCKER)')
print("🎙️ Скрипт для распознавания с диаризацией (План: WhisperX + Docker)") parser.add_argument('-f', '--file', type=str, help='Путь к конкретному аудиофайлу для обработки')
print("======================================================================") parser.add_argument('-d', '--directory', type=str, help='Путь к директории с аудиофайлами')
print("\n⚠️ ВНИМАНИЕ: Этот скрипт является заготовкой для будущей реализации.") parser.add_argument('--check', action='store_true', help='Проверить готовность системы к работе')
print("\nТекущий план — использовать Docker для решения проблем с зависимостями,") parser.add_argument('--config', type=str, default="config.env", help='Путь к файлу конфигурации относительно скрипта')
print("что обеспечит стабильную работу на системах с GPU NVIDIA.") parser.add_argument('--debug', action='store_true', help='Включить отладочный режим')
print("\nПроцесс будет выглядеть так:") args = parser.parse_args()
print(" 1. Вы запускаете этот скрипт с указанием папки аудио.")
print(" 2. Скрипт автоматически запускает Docker-контейнер с WhisperX.") if args.debug:
print(" 3. Результаты с разметкой по спикерам сохраняются в указанную папку.") logging.getLogger().setLevel(logging.DEBUG)
print("\nСледите за обновлениями в репозитории!")
print("[ссылка на ваш GitHub репозиторий]") print(f"{Colors.CYAN}{''*70}\n🎙️ WHISPERX ДИАРИЗАЦИЯ РЕЧИ (DOCKER)\n{''*70}{Colors.NC}")
print() print(f"Автор скрипта: Михаил Шардин | https://shardin.name/\nДата: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n")
try:
whisperx = WhisperXDocker(config_path=args.config)
if args.check:
whisperx.check_system(); return
if not whisperx.check_system():
logger.error("Система не готова. Исправьте ошибки и повторите."); sys.exit(1)
if args.file:
file_path = Path(args.file).expanduser().resolve()
if not file_path.exists():
logger.error(f"Файл не найден: {file_path}"); sys.exit(1)
whisperx.process_file(file_path)
else:
input_dir = Path(args.directory).expanduser() if args.directory else None
whisperx.process_directory(input_dir=input_dir)
except KeyboardInterrupt:
logger.info(f"\n{Colors.YELLOW}⏹️ Работа прервана пользователем{Colors.NC}"); sys.exit(130)
except Exception as e:
logger.error(f"Критическая непредвиденная ошибка: {e}", exc_info=True); sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -0,0 +1,332 @@
#!/usr/bin/env bash
# 🛠️ Скрипт установки WhisperX с диаризацией (Docker + NVIDIA) 🛠️
#
# Этот Shell-скрипт полностью автоматизирует подготовку системы Ubuntu
# (20.04/22.04/24.04) для работы с WhisperX через Docker с ускорением на GPU
# от NVIDIA. Он устанавливает все компоненты, настраивает их и создает
# готовое к работе окружение.
#
# Напоминание: скрипт следует официальным инструкциям NVIDIA и Docker
# для обеспечения максимальной надежности и совместимости.
#
# Основные задачи:
# - Проверка системы: Определяет дистрибутив и наличие драйверов NVIDIA.
# - Установка Docker: Устанавливает Docker Engine и добавляет пользователя
# в нужную группу для работы без `sudo`.
# - Установка NVIDIA Container Toolkit: Позволяет Docker-контейнерам
# напрямую использовать ресурсы GPU.
# - Тестирование GPU в Docker: Запускает тестовый контейнер для проверки
# корректности настройки.
# - Загрузка образа WhisperX: Скачивает готовый Docker-образ со всеми
# зависимостями.
# - Создание рабочего пространства:
# - Локальные папки `audio/` и `results/`.
# - Глобальный кеш для моделей в `~/whisperx/` для экономии места.
# - Файл конфигурации `config.env` с настройками по умолчанию.
# - Управление правами: Назначает корректные права на папки, чтобы избежать
# конфликтов доступа у Docker-контейнера.
#
# Порядок использования:
# 1. Сделайте скрипт исполняемым: chmod +x whisperx_diarization_setup.sh
# 2. Запустите его: ./whisperx_diarization_setup.sh
# 3. После завершения может потребоваться перезагрузка системы.
# Следить за состоянием GPU: $ watch -n 5 nvidia-smi
#
# Автор: Михаил Шардин https://shardin.name/
# Дата создания: 14.09.2025
# Версия: 2.2
#
# Актуальная версия скрипта всегда здесь: https://github.com/empenoso/offline-audio-transcriber
#
# ===================================================================
## Строгий режим для bash. Прерывает выполнение при любой ошибке.
set -euo pipefail
# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Функции логирования (используем printf для большей надежности)
log() { printf "${BLUE}[INFO]${NC} %s\n" "$1"; }
success() { printf "${GREEN}[SUCCESS]${NC} %s\n" "$1"; }
warning() { printf "${YELLOW}[WARNING]${NC} %s\n" "$1"; }
error() { printf "${RED}[ERROR]${NC} %s\n" "$1" >&2; } # Ошибки выводим в stderr
# --- Функции проверки системы ---
check_distro() {
if ! [ -f /etc/os-release ]; then
error "Не удалось определить операционную систему."
exit 1
fi
. /etc/os-release
if [[ "$ID" != "ubuntu" && "$ID" != "debian" ]]; then
error "Этот скрипт предназначен для Ubuntu/Debian. Обнаружено: $PRETTY_NAME"
exit 1
fi
success "Обнаружена совместимая система: $PRETTY_NAME"
}
check_gpu() {
log "Проверка наличия NVIDIA GPU и драйверов..."
if ! command -v nvidia-smi &> /dev/null; then
error "Команда 'nvidia-smi' не найдена. Установите драйверы NVIDIA."
printf "Рекомендуемые команды:\n"
printf " sudo ubuntu-drivers autoinstall\n"
printf " sudo reboot\n"
exit 1
fi
if ! nvidia-smi &> /dev/null; then
error "'nvidia-smi' не отвечает. Возможно, требуется перезагрузка после установки драйверов."
exit 1
fi
GPU_INFO=$(nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits)
DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits)
success "Найден GPU: $GPU_INFO"
log "Версия драйвера: $DRIVER_VERSION"
}
# --- Функции установки компонентов ---
install_docker() {
if command -v docker &> /dev/null && docker --version &> /dev/null; then
success "Docker уже установлен: $(docker --version)"
else
log "Установка Docker Engine..."
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
success "Docker успешно установлен."
fi
# Добавление пользователя в группу docker, если еще не там
if ! groups "$USER" | grep -q '\bdocker\b'; then
log "Добавление пользователя $USER в группу docker..."
sudo usmod -aG docker "$USER"
warning "Для применения изменений группы docker требуется перезагрузка или перелогин."
log "Вы можете выполнить 'sudo reboot' после завершения установки."
fi
}
install_nvidia_toolkit() {
log "Установка NVIDIA Container Toolkit..."
if command -v nvidia-ctk &> /dev/null; then
success "NVIDIA Container Toolkit уже установлен."
else
log "Настройка репозитория NVIDIA..."
# Этот метод автоматически определяет версию дистрибутива (ubuntu22.04, ubuntu24.04 и т.д.)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null
log "Обновление списка пакетов и установка..."
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
success "NVIDIA Container Toolkit успешно установлен."
fi
log "Конфигурирование Docker для работы с NVIDIA GPU..."
sudo nvidia-ctk runtime configure --runtime=docker
log "Перезапуск Docker daemon для применения конфигурации..."
sudo systemctl restart docker
sleep 3 # Даем демону время на перезапуск
success "Docker настроен для работы с NVIDIA GPU."
}
test_docker_gpu() {
log "Тестирование Docker с поддержкой GPU..."
if ! sudo docker run --rm hello-world > /dev/null 2>&1; then
error "Базовый Docker не работает. Проверьте 'systemctl status docker'"
exit 1
fi
success "Базовый тест Docker пройден."
log "Проверка доступа к GPU из контейнера..."
local cuda_image="nvidia/cuda:12.4.1-base-ubuntu22.04" # Используем актуальный образ
log "Используем тестовый образ: $cuda_image"
if ! sudo docker pull "$cuda_image" > /dev/null; then
warning "Не удалось загрузить тестовый образ $cuda_image. Пропускаем тест GPU."
return 1
fi
# Пытаемся выполнить nvidia-smi внутри контейнера
local gpu_name_in_container
gpu_name_in_container=$(sudo docker run --rm --gpus all "$cuda_image" nvidia-smi --query-gpu=name --format=csv,noheader)
if [[ -n "$gpu_name_in_container" ]]; then
success "🎉 GPU успешно обнаружен в Docker контейнере: $gpu_name_in_container"
return 0 # Успех
else
error "Не удалось получить доступ к GPU из Docker контейнера."
warning "WhisperX будет работать на CPU (значительно медленнее)."
log "Возможные причины:"
log " - Конфликт версий драйвера, toolkit или docker."
log " - Необходимо перезагрузить систему: 'sudo reboot'"
return 1 # Неудача
fi
}
pull_whisperx_image() {
log "Загрузка Docker образа WhisperX..."
local whisperx_image="ghcr.io/jim60105/whisperx:latest"
if sudo docker pull "$whisperx_image"; then
success "Образ $whisperx_image загружен успешно."
local image_size_bytes
image_size_bytes=$(sudo docker image inspect "$whisperx_image" --format='{{.Size}}')
local image_size_gb
image_size_gb=$(awk "BEGIN {printf \"%.2f\", $image_size_bytes/1024/1024/1024}")
log "Размер образа: ~${image_size_gb} GB"
else
error "Не удалось загрузить образ WhisperX: $whisperx_image"
exit 1
fi
}
setup_workspace() {
log "Создание рабочих директорий и конфигурации..."
local base_dir="."
local cache_dir="$HOME/whisperx"
mkdir -p "$base_dir"/{audio,results}
mkdir -p "$cache_dir"
log "Установка прав 777 на папки..."
chmod -R 777 "$base_dir"/audio "$base_dir"/results "$cache_dir"
success "Созданы директории:"
printf " 📂 %s/audio - для входных аудиофайлов\n" "$(pwd)"
printf " 📂 %s/results - для результатов\n" "$(pwd)"
printf " 🧠 %s - для кеширования моделей\n" "$cache_dir"
local config_file="$base_dir/config.env"
if [ -f "$config_file" ]; then
# ИЗМЕНЕНИЕ: Исправлена переменная $config.env на $config_file
success "Конфигурационный файл $config_file уже существует. Пропускаем создание."
else
log "Создание конфигурационного файла: $config_file"
cat > "$config_file" << 'EOF'
# Конфигурация WhisperX
# HuggingFace токен для диаризации (получите на https://huggingface.co/settings/tokens)
# ВАЖНО: Примите лицензии на:
# https://huggingface.co/pyannote/speaker-diarization-3.1
# https://huggingface.co/pyannote/segmentation-3.0
HF_TOKEN=your_token_here
# Модель Whisper (tiny, base, small, medium, large-v1, large-v2, large-v3)
WHISPER_MODEL=large-v3
# Язык аудио (ru, en, auto для автоопределения)
LANGUAGE=ru
# Размер батча (чем больше - тем быстрее, но больше памяти GPU)
BATCH_SIZE=16
# Устройство для вычислений (cuda или cpu)
DEVICE=cuda
# Включить диаризацию (разделение по спикерам)
ENABLE_DIARIZATION=true
# Минимальное количество спикеров (оставить пустым для автоопределения)
MIN_SPEAKERS=
# Максимальное количество спикеров (оставить пустым для автоопределения)
MAX_SPEAKERS=
# Тип вычислений (float16, float32, int8)
COMPUTE_TYPE=float16
# Метод VAD для обнаружения речи (pyannote, silero)
VAD_METHOD=pyannote
# Размер чанков в секундах
CHUNK_SIZE=30
EOF
success "Конфигурационный файл создан: $config_file"
fi
}
final_check() {
log "Выполнение финальной проверки установки..."
if ! command -v docker &>/dev/null; then error "Docker не найден!"; exit 1; fi
if ! sudo docker image inspect "ghcr.io/jim60105/whisperx:latest" &>/dev/null; then error "Образ WhisperX не найден!"; exit 1; fi
if ! [ -d "./audio" ]; then error "Рабочая директория не найдена!"; exit 1; fi
if ! [ -d "$HOME/whisperx" ]; then error "Директория кеша моделей не найдена!"; exit 1; fi
success "Все компоненты установлены и готовы к работе!"
}
show_usage() {
printf "\n=====================================================================\n"
printf "🎉 УСТАНОВКА ЗАВЕРШЕНА УСПЕШНО!\n"
printf "=====================================================================\n\n"
printf "🔥 ВАЖНЫЕ СЛЕДУЮЩИЕ ШАГИ:\n\n"
printf "1. 🔑 ${YELLOW}Отредактируйте токен Hugging Face${NC} для диаризации:\n"
printf " - Откройте файл: nano ./config.env\n"
printf " - Замените 'your_token_here' на ваш токен с https://huggingface.co/settings/tokens\n\n"
printf "2. 🔄 ${YELLOW}Перезагрузите систему${NC}, если вы не были в группе docker:\n"
printf " sudo reboot\n\n"
printf "После перезагрузки:\n"
printf "3. 📁 Скопируйте ваши аудиофайлы в ./audio/\n"
printf "4. 🚀 Запустите обработку: python3 whisperx_diarization.py\n\n"
printf "Рабочие директории:\n"
printf " 📂 ./audio - Входные файлы (*.wav, *.mp3, *.m4a)\n"
printf " 📂 ./results - Результаты распознавания\n"
printf " 🧠 ~/whisperx/ - Кеш моделей (общий для всех проектов)\n"
printf " ⚙️ ./config.env - Настройки\n\n"
printf "=====================================================================\n"
}
# --- Основная функция ---
main() {
printf "=====================================================================\n"
printf "🎙️ УСТАНОВКА WHISPERX ДЛЯ ДИАРИЗАЦИИ РЕЧИ (DOCKER + NVIDIA)\n"
printf "=====================================================================\n\n"
check_distro
check_gpu
install_docker
install_nvidia_toolkit
if test_docker_gpu; then
log "Тест GPU пройден. WhisperX будет использовать видеокарту."
else
warning "Тест GPU не пройден. Проверьте настройки в './config.env' и установите DEVICE=cpu, если GPU не заработает."
fi
pull_whisperx_image
setup_workspace
final_check
show_usage
}
# Запуск основной функции
main