Закрыть
npm

Отправляем голосовое сообщение ВКонтакте с эффектами Редактировано: 28.08.2020 в 04:45

Структура статьи

Предыстория

Когда-то давно, помню, мне пришла идея сделать чат-бота, который бы обрабатывал голосовые сообщения. Я не совсем понял, зачем. Но мне показалась эта идея прикольной. А потом я увлекся музыкой, начал делать биты, че-то какой-то реп стал записывать с другом, и получилось, что про эту идею я забыл. (Кстати о репе и битосах. Подписывайтесь - LovTnt.)

Когда начал изучать работу с аудио, узнал что такое VST плагины, как работают некоторые эффекты, какой софт есть в музыкальной индустрии, как вообще блин это все работает. Поэтому, делая то, что я сейчас буду описывать статье, я примерно понимал, что к чему и как. Но только примерно.

О записи звука

И так. Для того, чтобы отправить голосовое сообщение ВКонтакте, нам надо его сначала записать. Я тестировал все только на Windows 10, кто знает, тот поймет, почему что-то подобное можно будет легко и просто запустить на компьютере с Linux. Главное, чтобы у вас был микрофон и нормальный софт (драйвера, кодеки всякие)

Мы будем записывать голос прямо из Node.js. Для этого мы воспользуемся очень удобной программой - SoX. По сути это аудиоредактор, который не имеет графического интерфейса, и в нем все работает через командную строку.

Я тестировал программу на версии SoX 14.4.2, но с ней у меня ничего не получилось, поэтому я воспользовался более старой версией, в которой не было тех проблем, которые я не знал, как решить. Поэтому и вам рекоменудю качать именно версию 14.4.1

Ссылки для скачивания находятся ниже

После установки софта, вам необходимо добавить папку с этой программой в переменную PATH, для того, чтобы вы могли запустить ее из любого места. Можно, конечно, так не делать, но это если знаете, как сделать по-другому.

Для того, чтобы добавить папку в PATH, необходимо открыть свойства компьютера и найти там параметры среды.

Свойства мой компьютер

Дополнительные параметры

Параметры среды

Ищем Path, нажимаем изменить и добавляем адрес к папке, в которой лежит SoX PATH -> Изменить

Вот так

На этом установка SoX заканчивается, теперь мы переходим к части Node.js

Ближе к коду

Вся программа будет делится на четрые этапа

  • Запись аудиофайла
  • Обработка записанного аудиофайла
  • Загрузка файла на сервер
  • Отправка его в сообщении

Первые два этапа можно объединить в один, но я сделал так специально, потому что предполагаю, что кто-нибудь все-таки захочет написать чат-бота для обработок чужих голосовых.

Запись

Мы воспользуемся npm пакетом node-audiorecorder, который тоже использует SoX для записи, настраивая его на вывод данных в stdout

npm init -y && npm install node-audiorecorder easyvk && touch index.js

Теперь мы можем писать код в файле index.js (Node.js 10)

const easyvk = require('easyvk')
const fs = require('fs')

const AudioRecorder = require('node-audiorecorder')

const options = {
  program: `sox`,
  device: null,
  bits: 16,
  channels: 1,
  encoding: `signed-integer`,
  format: `S16_LE`,
  rate: 16000,
  type: 'wav',
  keepSilence: false  
};

let audioRecorder = new AudioRecorder(options, console);

easyvk({
  username: 'ВАШ ЛОГИН',
  password: 'ВАШ ПАРОЛЬ',
  v: '5.103'
}).then(async vk => {

  /** Файл, в который идет запись */
  let fileName = 'record.wav'
  let fileStream = fs.createWriteStream(fileName, { encoding: 'binary' })
  
  /** Начинаем запись */
  audioRecorder.start().on('close', console.log)
  audioRecorder.stream().pipe(fileStream)

})

Если вы запустите данный код, то SoX начнет записывать ваш голос в файл record.wav. Чтобы остановить запись, нажмите клавиши Ctrl + C

C записью разобрались, но лично меня не устривает, что Ctrl + C убивает весь софт моментально, поэтому мы сделаем так, чтобы на вход требовалась команда, отправляя которую скрипт самостоятельно остановит запись. Сделаем это.

const readline = require('readline')

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

easyvk({...}).then(async vk => {
  /** Файл, в который идет запись */
  let fileName = 'record.wav'
  let fileStream = fs.createWriteStream(fileName, { encoding: 'binary' })
  
  /** Начинаем запись */
  audioRecorder.start().on('close', console.log)
  audioRecorder.stream().pipe(fileStream)
  
  rl.question('Нажмите клавишу Enter, чтобы остановить запись ', ()=>audioRecorder.stop())

})

Обработка аудио

Теперь перейдем к эффектам. Они создаются все тем же SoX. Как только стрим audiorecorder'а с входящим аудио завершается, мы будем обрабатывать получивший аудиофайл. Посмотрим, как это выглядит.

Ловим событие close у стрима.

audioRecorder.start().on('close', makeEffects)

Теперь перейдем к коду функции makeEffects

async function makeEffects () {
  child_process.exec('sox '+ fileName +' '+ fileNameOutput +' pitch -320 bass +20', (err) => {
    if (err) throw err;
    sendAudioMessage()
  })
}

Переменная child_process хранит в себе объект для работы с дочернимим программами. С помощью метода exec мы запускаем программу SoX, говоря ей, что ей надо обработать файл .wav, а результат сохранить в файл .ogg. После выполнения программы, мы уже вызываем функцию sendAudioMessage, которая отправляет полученный файл.

Теперь полностью. Эффекты.

const child_process = require('child_process')

easyvk({...}).then(async vk => {
  
  /** Файл, в который идет запись */
  let fileName = 'record.wav'
  /** Обработанный файл */
  let fileNameOutput = 'result.ogg'

  let fileStream = fs.createWriteStream(fileName, { encoding: 'binary' })

  /** Начинаем запись */
  audioRecorder.start().on('close', makeEffects)
  audioRecorder.stream().pipe(fileStream)
  
  rl.question('Нажмите клавишу Enter, чтобы остановить запись ', ()=>audioRecorder.stop())
  
  async function makeEffects () {
    child_process.exec('sox '+ fileName +' '+ fileNameOutput +' pitch -320 bass +20', (err) => {
      if (err) throw err;
      sendAudioMessage()
    })
  }

})

Код будет работать только, если вы скажете хотябы пару слов в микрофон.

Какие эффекты наложены?

В данном примере я добавил эффект pith на -320, а также bass +20.
Эффект bass отвечает за подъем или спуск уровня бассовых частот, pith за понижение или повышения тона.

Отправка голосового сообщения

Самое сложно уже позади. Осталось получившийся файл загрузить на сервер и отправить в личное сообщение. Сделаем и это.

async function sendAudioMessage () {

  /** Получаем URL для загрузки */
  let {upload_url: uploadUrl} = await vk.call('docs.getMessagesUploadServer', {
    peer_id: peerId,
    type: "audio_message"
  })

  /** Загружаем файл */
  let file =  await vk.uploader.uploadFile(uploadUrl, fileNameOutput);

  /** Сохраняем */
  let doc = await vk.post('docs.save', file)
  doc = doc.audio_message;
  
  /** Отправляем */
  await vk.call('messages.send', {
    peer_id: peerId,
    attachment: `doc${doc.owner_id}_${doc.id}_${doc.access_key}`,
    random_id: easyvk.randomId()
  })
}

Переменная peerId хранит ID принимающего сообщение. Может быть беседой, сообществом, другим пользователем. Теперь полная картина.

easyvk({...}).then(async vk => {
  
  /** ID принимающего сообщение */
  let peerId = vk.session.user_id

  /** Файл, в который идет запись */
  let fileName = 'record.wav'
  /** Обработанный файл */
  let fileNameOutput = 'result.ogg'

  let fileStream = fs.createWriteStream(fileName, { encoding: 'binary' })

  /** Начинаем запись */
  audioRecorder.start().on('close', makeEffects)
  audioRecorder.stream().pipe(fileStream)
  
  rl.question('Нажмите клавишу Enter, чтобы остановить запись ', ()=>audioRecorder.stop())
  
  async function makeEffects () {
    child_process.exec('sox '+ fileName +' '+ fileNameOutput +' pitch -320 bass +20', (err) => {
      if (err) throw err;
      sendAudioMessage()
    })
  }

  async function sendAudioMessage () {

    /** Получаем URL для загрузки */
    let {upload_url: uploadUrl} = await vk.call('docs.getMessagesUploadServer', {
      peer_id: peerId,
      type: "audio_message"
    })

    /** Загружаем файл */
    let file =  await vk.uploader.uploadFile(uploadUrl, fileNameOutput);

    /** Сохраняем */
    let doc = await vk.post('docs.save', file)
    doc = doc.audio_message;
    
    /** Отправляем */
    await vk.call('messages.send', {
      peer_id: peerId,
      attachment: `doc${doc.owner_id}_${doc.id}_${doc.access_key}`,
      random_id: easyvk.randomId()
    })
    
    process.exit(0)
  }

})

Вауля! Сообщение отправлено!

Голосовое сообщение вконтакте

Поиграемся с эффектами

reverb pitch -320 bass +20

reverse pitch -320 bass +20

Со всеми остальными эффектами вы можете познакомиться здесь

Итог

Каков итог?

Возможно, вы впервые научились отправлять голосовые сообщения. Возможно, впервые узнали, что есть такая замечательная программа, как SoX. Возможно, впервые узнали, как пользоваться child_process, возможно, впервые попробовали записать голосовое сообщение через Node.js

Надеюсь, что статья кому-то в чем-то помогла и принесла пользу.

Спасибо, что дочитали до конца!)

Комментарии к статье