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.
Ссылки
Автор: formeo


