
Полная версия
Python для творческих. Звук на твоей стороне.
Глава 2. Учимся резать и склеивать звук
О чём эта глава
В предыдущей главе мы смотрели на звук и изучали его свойства. Мы были как учёные, которые исследуют образец под микроскопом. Теперь пришло время стать ремесленниками. Мы возьмём аудиофайлы в руки и начнём с ними работать: отрезать лишнее, соединять нужное, переставлять куски местами. Всё то, что обычно делается мышкой в программах вроде Audacity, мы будем делать кодом. Почему кодом, а не мышкой? Потому что код можно запустить повторно. Потому что код не устаёт и не ошибается от усталости. Потому что код может обработать сто файлов за время, которое вы тратите на ручную обработку одного. В этой главе мы напишем инструменты, которые превратят монтаж аудио из утомительной рутины в быстрое и приятное занятие. Мы научимся вырезать фрагменты из аудио, склеивать несколько файлов в один, добавлять паузы нужной длины, выравнивать громкость разных фрагментов и собирать полноценный эпизод подкаста из заготовок. К концу главы у вас будет скрипт, который делает всю черновую работу по монтажу за вас.
Как резать аудио в коде
Начнём с самого простого действия: отрезать кусок от начала или конца записи. Представьте, что вы записали подкаст. Вы начали запись, проверили микрофон, сказали «раз-раз, меня слышно?», потом выпили воды, прокашлялись и только через три минуты начали говорить по делу. Эти три минуты не должны попасть в финальную версию. Более того, в конце записи вы, скорее всего, скажете что-то вроде «Всё, выключаю запись. Ой, она ещё пишет? Ну всё, пока». Эти последние десять секунд тоже нужно убрать. В графическом редакторе вы бы выделили ненужный фрагмент мышкой и нажали Delete. В коде мы сделаем то же самое, только вместо мышки у нас будут индексы массива.
Вспомним, что наш аудиофайл — это массив чисел y. Каждое число имеет свой порядковый номер, или индекс. Первое число имеет индекс ноль, второе — индекс один, сотое — индекс девяносто девять. В Python мы можем взять любую часть массива, указав начальный и конечный индекс в квадратных скобках через двоеточие. Это называется срез. Если мы хотим убрать первые три минуты, нам нужно выбросить все числа с нулевого индекса до индекса, соответствующего трём минутам. А этот индекс мы вычисляем умножением: три минуты — это сто восемьдесят секунд, умножаем на частоту дискретизации 22050, получаем около четырёх миллионов. Значит, нам нужны все числа начиная с индекса четыре миллиона и до конца массива.
Вот как это выглядит в коде:
python
import librosa
import soundfile as sf
y, sr = librosa.load('long_record.wav')
# Сколько секунд отрезать от начала
cut_seconds = 180
cut_samples = cut_seconds * sr
# Берём всё, начиная с cut_samples и до конца
y_trimmed = y[cut_samples:]
sf.write('record_trimmed.wav', y_trimmed, sr)
print(f"Было: {len(y) / sr:.1f} сек")
print(f"Стало: {len(y_trimmed) / sr:.1f} сек")
Запись y[cut_samples:] — это и есть срез. Квадратные скобки говорят Python, что мы хотим взять часть массива. Число до двоеточия — начальный индекс. Двоеточие без числа после него означает «и до самого конца». Всё, что было до индекса cut_samples, игнорируется. Обратите внимание: мы не удаляем данные из исходного массива y. Мы создаём новый массив y_trimmed, который содержит только нужную нам часть. Исходный массив остаётся нетронутым, и мы всегда можем к нему вернуться.
Теперь обратная задача: обрезать конец. Это делается похоже, только мы указываем конечный индекс, а начальный оставляем пустым:
python
# Сколько секунд отрезать от конца
cut_end_seconds = 10
cut_end_samples = len(y) - (cut_end_seconds * sr)
# Берём всё от начала до cut_end_samples
y_trimmed = y[:cut_end_samples]
Здесь срез y[:cut_end_samples] означает «от начала до индекса cut_end_samples, не включая его». Мы вычисляем этот индекс как общую длину массива минус количество измерений в десяти секундах. Таким образом мы отрезаем ровно последние десять секунд.
Если нужно обрезать и начало, и конец одновременно, оба среза можно объединить в одну строчку:
python
y_trimmed = y[180 * sr : len(y) - 10 * sr]
Читается это так: «Возьми массив y от индекса, соответствующего ста восьмидесяти секундам, до индекса, который находится за десять секунд до конца». Всё просто и понятно.
Склеиваем несколько файлов
С разрезанием разобрались. Теперь обратная операция — склеивание. Предположим, у вас есть три файла: музыкальное вступление, записанный голос и музыкальная концовка. Их нужно объединить в один файл, который пойдёт на публикацию. В видеоредакторе это делается перетаскиванием файлов на дорожку. В коде это делает функция np.concatenate из библиотеки numpy. Она берёт несколько массивов и соединяет их в один длинный массив, как будто склеивает ленту из кусков.
Вот базовый пример:
python
import librosa
import numpy as np
import soundfile as sf
# Загружаем три файла
intro, sr1 = librosa.load('intro.wav')
voice, sr2 = librosa.load('voice.wav')
outro, sr3 = librosa.load('outro.wav')
# Склеиваем
final = np.concatenate([intro, voice, outro])
# Сохраняем
sf.write('final.wav', final, sr1)
Однако здесь есть подводный камень: все три файла должны иметь одинаковую частоту дискретизации. Если вступление записано с частотой 44100, голос — с 22050, а концовка — с 48000, склеенный файл будет звучать неправильно. На стыках скорость воспроизведения будет меняться, и голос может стать то быстрее, то медленнее. Поэтому перед склеиванием нужно убедиться, что все файлы имеют одинаковую частоту, и если нет — привести их к единому стандарту.
Вот улучшенная версия скрипта с проверкой частоты:
python
import librosa
import numpy as np
import soundfile as sf
# Загружаем и проверяем частоту
target_sr = 22050
intro, sr_i = librosa.load('intro.wav', sr=target_sr)
voice, sr_v = librosa.load('voice.wav', sr=target_sr)
outro, sr_o = librosa.load('outro.wav', sr=target_sr)
print(f"Интро: {len(intro) / target_sr:.1f} сек")
print(f"Голос: {len(voice) / target_sr:.1f} сек")
print(f"Аутро: {len(outro) / target_sr:.1f} сек")
# Склеиваем
final = np.concatenate([intro, voice, outro])
print(f"Итого: {len(final) / target_sr:.1f} сек")
sf.write('episode.wav', final, target_sr)
Параметр sr=target_sr в функции librosa.load говорит библиотеке: «Загрузи файл и пересчитай его так, как будто он был записан с частотой target_sr». Если исходный файл был записан с другой частотой, librosa автоматически подгонит его под нужную. Это очень удобно и экономит нам кучу ручной работы.
Добавляем паузы между фрагментами
Когда вы склеиваете вступление и голос вплотную, они могут звучать неестественно. Обычно между музыкальной заставкой и началом речи нужна небольшая пауза — полсекунды или секунда, чтобы слушатель переключился с музыки на голос. В коде пауза — это просто массив из нулей. Ноль в аудио означает абсолютную тишину. Чтобы создать секунду тишины, нам нужно создать массив из нулей длиной в частоту дискретизации. При частоте 22050 это будет 22050 нулей.
python
# Создаём паузу в одну секунду
silence_sec = 1.0
silence = np.zeros(int(target_sr * silence_sec))
# Склеиваем с паузами
final = np.concatenate([intro, silence, voice, silence, outro])
Функция np.zeros(n) создаёт массив из n нулей. Мы передаём ей количество сэмплов, которое равно длительности паузы в секундах, умноженной на частоту дискретизации. Этот массив нулей можно вставлять между любыми фрагментами, и на слух это будет восприниматься как естественная пауза. Длину паузы можно менять по своему вкусу. Для энергичного контента паузы делают короче — четверть секунды или даже меньше. Для спокойного, вдумчивого — длиннее, до полутора секунд.
Выравниваем громкость фрагментов
Ещё одна важная задача при склеивании — выравнивание громкости. Музыкальная заставка часто бывает записана громче, чем голос. Если их склеить без обработки, слушателю придётся убавлять громкость на музыке и прибавлять на голосе. Это раздражает. Профессиональный монтаж подразумевает, что все части звучат на одном уровне.
Выравнивать громкость мы будем через пиковую нормализацию — метод, который мы кратко упоминали в Главе 1, а подробно разберём в Главе 3. Сейчас используем упрощённый вариант:
python
def normalize_volume(y, target_peak=0.9):
"""Подгоняет пиковую громкость к заданному уровню."""
current_peak = np.max(np.abs(y))
if current_peak == 0:
return y
gain = target_peak / current_peak
return y * gain
# Нормализуем все части
intro = normalize_volume(intro, 0.9)
voice = normalize_volume(voice, 0.9)
outro = normalize_volume(outro, 0.9)
После такого выравнивания все три части будут иметь одинаковый пиковый уровень, и слушателю не придётся тянуться к регулятору громкости.
Собираем готовый эпизод подкаста
Теперь давайте соберём всё вместе в один большой скрипт, который принимает набор файлов и выдаёт готовый смонтированный эпизод. Этот скрипт можно будет использовать для каждого выпуска подкаста, меняя только имена файлов.
python
import librosa
import numpy as np
import soundfile as sf
def build_podcast(intro_file, voice_file, outro_file, output_file,
trim_start=180, trim_end=10,
silence_between=1.0,
target_sr=22050,
target_peak=0.9):
"""
Собирает эпизод подкаста из трёх файлов.
trim_start - сколько секунд отрезать от начала голосовой дорожки
trim_end - сколько секунд отрезать от конца голосовой дорожки
silence_between - длина паузы между частями в секундах
"""
print("=== Сборка эпизода ===")
# Загружаем
print("Загружаю файлы...")
intro, _ = librosa.load(intro_file, sr=target_sr)
voice, _ = librosa.load(voice_file, sr=target_sr)
outro, _ = librosa.load(outro_file, sr=target_sr)
# Обрезаем голос
start_sample = int(trim_start * target_sr)
end_sample = len(voice) - int(trim_end * target_sr)
voice = voice[start_sample:end_sample]
print(f"Голос после обрезки: {len(voice) / target_sr:.1f} сек")
# Нормализуем громкость
for arr in [intro, voice, outro]:
peak = np.max(np.abs(arr))
if peak > 0:
arr *= target_peak / peak
# Создаём паузу
silence = np.zeros(int(target_sr * silence_between))
# Склеиваем
final = np.concatenate([intro, silence, voice, silence, outro])
print(f"Финальная длина: {len(final) / target_sr:.1f} сек")
# Сохраняем
sf.write(output_file, final, target_sr)
print(f"Сохранено: {output_file}")
print("Готово!")
# Запускаем
build_podcast(
intro_file='intro.wav',
voice_file='raw_voice.wav',
outro_file='outro.wav',
output_file='episode_ready.wav',
trim_start=180,
trim_end=10,
silence_between=1.0
)
Запустите этот скрипт, указав свои файлы. Если у вас нет готовых джинглов для интро и аутро, не беда — можете пока использовать любые музыкальные фрагменты или даже тишину. Главное — понять процесс. Когда появятся настоящие джинглы, вы просто подставите их в скрипт, и всё будет работать.
Пакетная обработка: монтируем много файлов сразу
Одно из главных преимуществ кода перед ручным монтажом — возможность обрабатывать много файлов одной командой. Предположим, у вас в папке лежит десять записей, и каждую нужно обрезать с начала и с конца, а потом сохранить результат в другую папку. В Audacity это заняло бы час. В Python это делается одним скриптом и занимает минуту.
python
import os
import librosa
import soundfile as sf
input_folder = 'raw_recordings'
output_folder = 'trimmed_recordings'
# Создаём папку для результатов, если её нет
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# Настройки обрезки
trim_start_sec = 180
trim_end_sec = 10
target_sr = 22050
# Проходим по всем WAV-файлам в папке
for filename in os.listdir(input_folder):
if not filename.endswith('.wav'):
continue
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
print(f"Обрабатываю: {filename}")
y, sr = librosa.load(input_path, sr=target_sr)
start_sample = int(trim_start_sec * target_sr)
end_sample = len(y) - int(trim_end_sec * target_sr)
y_trimmed = y[start_sample:end_sample]
sf.write(output_path, y_trimmed, target_sr)
print("Все файлы обработаны!")
Этот скрипт проходит по всем файлам в папке raw_recordings, для каждого обрезает начало и конец, и сохраняет результат в папку trimmed_recordings. Попробуйте запустить его на своих записях. Вы увидите, как Python методично, файл за файлом, делает работу, которая вручную заняла бы полдня. И не устаёт, и не ошибается.
Что делать, если файлы в разных форматах
В реальной жизни файлы приходят к нам в самых разных форматах. Кто-то присылает MP3, кто-то M4A, кто-то записывает на телефон и получает непонятный формат. Для пакетной обработки это проблема: librosa не все форматы понимает. На этот случай есть несколько решений.
Первое решение — конвертировать все файлы в WAV заранее. Это можно сделать в любой программе для конвертации аудио. Если файлов много, можно написать скрипт для конвертации с помощью библиотеки pydub. Установите её командой pip install pydub, и вы сможете конвертировать практически любой формат в WAV.
Второе решение — использовать библиотеку soundfile для загрузки, если файл уже в WAV, и pydub для конвертации, если в другом формате. Это более универсальный подход.
Третье решение для самых продвинутых — установить ffmpeg, мощный инструмент для работы с аудио и видео из командной строки, и использовать его через Python-обёртку. Это тема для отдельной книги, но знать о такой возможности полезно.
Распространённые ошибки при монтаже
Склеенный файл звучит с щелчком на стыках. Это случается, когда два соседних фрагмента имеют сильно различающуюся громкость в точке стыка. Если первый фрагмент заканчивается на громкости плюс ноль пять, а второй начинается на минус ноль восемь, динамик вашего устройства должен мгновенно перескочить из одного положения в другое, что физически невозможно — отсюда и щелчок. Решение простое: всегда вставляйте между фрагментами хотя бы крошечную паузу. Даже двадцать миллисекунд тишины сгладят переход и уберут щелчок. В профессиональном монтаже для этого используют кроссфейды — плавное затухание одного фрагмента и нарастание другого. Мы можем реализовать кроссфейд в коде, но это более сложная тема, которую мы разберём в продвинутых главах.
Конец ознакомительного фрагмента.
Текст предоставлен ООО «Литрес».
Прочитайте эту книгу целиком, купив полную легальную версию на Литрес.
Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.





