TL;DR: Классические фильтры (FFmpeg, Audacity) плохо справляются со сложным шумом в аудиокнигах. Нейросети для source separation работают в разы лучше. Написал обертку над audio-separator, которая умеет обрабатывать многочасовые файлы без вылетов по памяти.
В прошлой статье я рассказывал про go-audio-converter — конвертер аудио на чистом Go без FFmpeg. Сегодня — про следующий инструмент в моей аудио-экосистеме: очистку аудиокниг от шума с помощью нейросетей.
Проблема
Я фанат аудиокниг и подкастов. Одна из любимых — «Модель для сборки», культовый проект Влада Копа с фантастическими рассказами под эмбиент. Проблема: ранние выпуски 2000-х записаны так, что музыка местами забивает голос диктора. Слушать сложно — приходится напрягаться, чтобы разобрать текст.
Особенно проблемно на 3 из моих самых любимых книг - Цикл про богатыря Жихаря, Михаила Успенского (рекомендую)
И это не единичный случай. В моей коллекции полно аудиокниг с похожими проблемами:
Фоновая музыка громче голоса
Гул 50/60 Гц от старого оборудования
Шипение от кассетных оцифровок
Эхо и гулкость помещения
Первая мысль — FFmpeg:
bash
ffmpeg -i input.mp3 -af "highpass=f=200, lowpass=f=3000, anlmdn" output.mp3
Не работает. Фоновая музыка сидит в тех же частотах, что и голос. Режешь частоты — режешь голос. Noise reduction (anlmdn) справляется только с простым шумом типа шипения.
Audacity с плагинами чуть лучше, но результат всё равно так себе — либо музыка остаётся, либо голос звучит как из-под воды.
Проблема №2: длинные файлы
Допустим, нашел инструмент который работает. UVR5 (Ultimate Vocal Remover) с нейросетями реально отделяет голос от музыки. Но попробуй скормить ему 7-часовую аудиокнигу.
UVR5 — GUI зависает, вылетает с "out of memory" на файлах >1 ГБ
Demucs CLI — то же самое, жрёт всю память и падает
Онлайн-сервисы — лимит 10-20 минут на файл
Аудиокнига — 7 часов. В WAV это около 3 ГБ. Ни один из существующих инструментов не умеет это нормально обрабатывать. Либо режь вручную на куски, обрабатывай по одному, потом склеивай — но это часы ручной работы на каждую книгу.
Нужен был инструмент, который:
Работает с файлами любой длины
Не падает по памяти
Автоматизируется (batch processing)
Сохраняет прогресс (упал — продолжил)
Почему нейросети?
Source separation — задача отделения источников звука друг от друга. Музыкальная индустрия давно использует это для караоке: отделить вокал от инструментов.
Ключевое наблюдение: голос диктора — это тот же "вокал". Модели, обученные отделять вокал от музыки, отлично справляются с аудиокнигами.
Основные архитектуры:
MDX-Net — быстрая, хорошее качество, работает на слабом железе
Demucs (Meta) — универсальная, но тяжёлая
Roformer — трансформеры для аудио, лучшее качество, но медленная
Выбор инструмента
Есть несколько способов использовать эти модели:
UVR5 (Ultimate Vocal Remover) — GUI-приложение, удобное, но не автоматизируешь
Demucs CLI — только модели Demucs
audio-separator — Python-библиотека, поддерживает все модели UVR5
Выбрал audio-separator — это те же модели, что в UVR5, но с нормальным API для автоматизации.
Архитектура решения
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Входной │ → │ Нарезка на │ → │ MDX-Net/ │ │ файл (7ч) │ │ чанки (5мин) │ │ Roformer │ └─────────────┘ └──────────────┘ └─────────────┘ ↓ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Результат │ ← │ Склейка │ ← │ Vocals + │ │ .mp3 │ │ ffmpeg │ │ DeNoise │ └─────────────┘ └──────────────┘ └─────────────┘
Главная проблема — память. 7-часовая аудиокнига в WAV — это ~4 ГБ. Нейросеть хочет загрузить всё в память, плюс промежуточные тензоры. На 16 ГБ RAM получаем "bad allocation".
Решение: чанкование. Режем файл на части по 5 минут, обрабатываем каждую отдельно, склеиваем обратно.
Реализация
Нарезка на чанки
def split_audio(input_file: str, output_dir: Path, chunk_duration: int) -> list[Path]: """Нарезает аудиофайл на части указанной длительности.""" pattern = output_dir / "chunk_%04d.wav" subprocess.run([ 'ffmpeg', '-y', '-i', str(input_file), '-f', 'segment', '-segment_time', str(chunk_duration), '-c:a', 'pcm_s16le', str(pattern) ], capture_output=True, check=True) return sorted(output_dir.glob("chunk_*.wav"))
FFmpeg умеет резать точно по времени без перекодирования. Конвертируем в WAV (PCM), потому что нейросети ожидают несжатый вход.
Обработка чанка
from audio_separator.separator import Separator separator = Separator( output_dir=str(chunk_output_dir), output_format='wav', sample_rate=44100, use_autocast=use_gpu, # Mixed precision для GPU mdx_params={ "hop_length": 1024, "segment_size": 64, "overlap": 0.25, "batch_size": 1, } ) # Шаг 1: Отделяем голос от музыки separator.load_model('UVR-MDX-NET-Voc_FT.onnx') result = separator.separate(str(chunk_path)) # Получаем: chunk_0001_(Vocals).wav, chunk_0001_(Instrumental).wav # Шаг 2: Убираем остаточный шум из вокала separator.load_model('UVR-DeNoise.pth') denoised = separator.separate(str(vocals_file)) # Получаем: chunk_0001_(Vocals)_(No Noise).wav
Двухэтапная обработка:
MDX-Net отделяет голос от всего остального (музыка, шумы)
DeNoise убирает остаточное шипение и гул
Сохранение прогресса
Критично для многочасовых файлов — процесс может упасть на середине:
# Проверяем, обработан ли уже чанк existing_vocals = list(chunk_output_dir.glob("*Vocal*.wav")) if existing_vocals: logger.info(f"Пропуск (уже обработан): {chunk.name}") continue
Промежуточные файлы сохраняются в chunks/ и processed/. Если скрипт упал — перезапускаем, он продолжит с того места.
Параллельная обработка
Для CPU-режима добавил параллелизм через multiprocessing:
def _process_chunk_worker(args: tuple) -> dict: """Воркер для параллельной обработки чанка.""" chunk_idx, chunk_path, chunk_output_dir, model_name, ... = args # Каждый воркер создаёт свой экземпляр Separator separator = Separator(...) separator.load_model(model_name) result = separator.separate(str(chunk_path)) return {'idx': chunk_idx, 'vocals': vocals, 'instrumental': instrumental} # Запуск пула воркеров with mp.Pool(processes=workers) as pool: for result in pool.imap(_process_chunk_worker, tasks): # Обрабатываем результаты по мере готовности ...
На 6-ядерном CPU это даёт ускорение в 3-4 раза.
Склейка результата
def concat_audio(input_files: list[Path], output_file: Path, output_format: str): """Склеивает аудиофайлы в один.""" list_file = output_file.parent / "concat_list.txt" with open(list_file, 'w') as f: for file in input_files: abs_path = str(file.absolute()).replace('\\', '/') f.write(f"file '{abs_path}'\n") codec_args = { 'mp3': ['-c:a', 'libmp3lame', '-q:a', '2'], 'flac': ['-c:a', 'flac'], 'wav': ['-c:a', 'pcm_s16le'], }[output_format] subprocess.run([ 'ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', str(list_file), *codec_args, str(output_file) ])
FFmpeg concat без перекодирования WAV-чанков, финальное сжатие в MP3/FLAC на последнем шаге.
Выбор модели
Протестировал на своей коллекции:
Модель | Качество голоса | Скорость (GPU) | VRAM | Когда использовать |
|---|---|---|---|---|
| ★★★★☆ | ~30 сек/5мин | 2 ГБ | По умолчанию |
| ★★★★☆ | ~30 сек/5мин | 2 ГБ | Альтернатива, иногда лучше |
| ★★★★★ | ~2 мин/5мин | 4 ГБ | Максимальное качество |
| - | ~15 сек/5мин | 1 ГБ | Второй проход для шума |
Рекомендация: MDX-Net для большинства случаев. Roformer — если качество критично и есть время.
Использование
# Базовая очистка python audiobook_cleaner.py audiobook.mp3 # Большой файл — обязательно с чанкованием python audiobook_cleaner.py audiobook.mp3 --chunk-duration 300 # Без второго прохода DeNoise (быстрее) python audiobook_cleaner.py audiobook.mp3 --chunk-duration 300 --no-denoise # Максимальное качество (Roformer) python audiobook_cleaner.py audiobook.mp3 --model roformer --chunk-duration 300 # Обработка директории python audiobook_cleaner.py --dir ./audiobooks --output ./clean # На слабом железе python audiobook_cleaner.py audiobook.mp3 --chunk-duration 120 --segment-size 16 --cpu
Производительность
Время обработки 5-минутного чанка:
Конфигурация | Время | Примечание |
|---|---|---|
CPU (i7-8700) | 2:30 | Без GPU |
GPU GTX 1660 (PyTorch fallback) | 1:40 | ONNX → PyTorch |
GPU GTX 1660 (ONNX native) | 0:30-0:40 | Полное ускорение |
7-часовая аудиокнига (85 чанков по 5 минут):
CPU: ~3.5 часа
GPU (ONNX): ~45-60 минут
Подводные камни
1. ONNX Runtime и CUDA
audio-separator использует ONNX Runtime для MDX-Net моделей. Чтобы GPU работал:
pip install onnxruntime-gpu==1.18.1 pip install nvidia-cudnn-cu12==8.9.7.29 # Критично!
Без cuDNN получите "CUDAExecutionProvider not available" и fallback на CPU.
2. Segment size vs dim_t
Если в логах видите:
Model converted from onnx to pytorch due to segment size not matching dim_t
Модель конвертируется в PyTorch на лету — это медленнее в 3-4 раза. Не критично, но знайте.
3. Артефакты на стыках чанков
Теоретически могут быть щелчки на границах чанков. На практике FFmpeg concat справляется — не заметил проблем на 200+ часах обработки.
4. DeNoise убивает тихие места
Модель DeNoise иногда слишком агрессивна на тихих фрагментах (паузы между фразами). Если это критично — используйте --no-denoise.
Результаты
Обработал ~200 часов аудиокниг. Субъективно:
Фоновая музыка — убирается на 90-95%, остаются лёгкие "призраки"
Гул и шипение — убирается почти полностью
Голос — сохраняется без заметных искажений
PS.
Инструмент подходит не только для аудиокниг:
Минусовки — те же модели работают в обратную сторону: берёте трек, получаете инструментал без вокала.
Архивные записи — я увлекаюсь авиацией и расследованиями катастроф, использовал для очистки записей бортовых самописцев. Выделить голоса экипажа из шума двигателей — та же задача source separation.
