Расшифровка аудиозаписей вкладки браузера с помощью расширений Chrome

Скорее всего, вы уже установили хотя бы одно расширение для браузера. Поскольку Chrome используют более 2,5 миллиардов (да, с буквой «б») пользователей по всему миру, это отличная платформа для создания и выпуска приложений. В этом руководстве вы создадите расширение Chrome, которое будет захватывать аудиозаписи вкладок браузера и транскрибировать их с помощью Deepgram.

Манифест

Создайте файл manifest.json. Этот файл содержит важную информацию о нашем расширении, которая необходима браузеру для его загрузки (и публикации в Chrome Web Store). Добавьте в него следующее:

{
    "name": "Transcribe Tab Audio",
    "version": "1.0",
    "manifest_version": 3,
    "host_permissions": ["*://*/"],
    "permissions": ["storage", "tabs", "scripting"]
}
Войти в полноэкранный режим Выйти из полноэкранного режима

host_permissions указывает, на каких веб-страницах будет активно это расширение — * соответствует всем, поэтому оно будет работать на каждой странице. Вы можете изменить этот параметр, если хотите, чтобы расширение работало только на определенных страницах или доменах.

Указанные полномочия также необходимы для этого проекта — "storage" позволяет расширению хранить небольшие объемы данных на машине, "tabs" обеспечивает доступ ко всем полям данных относительно вкладок в браузере, а "scripting" позволяет нам выполнять файлы JavaScript — подробнее об этом позже.

На данный момент у вас есть действующее расширение Chrome — давайте загрузим его. Перейдите по адресу chrome://extensions, включите режим разработчика и нажмите Load Unpacked. Выберите папку с файлом manifest.json, и вы увидите, как расширение появится в вашем браузере.

Сейчас оно немного потертое — время исправить это.

Создание всплывающего окна

Всплывающее окно расширения — это небольшая панель, которая появляется, когда вы нажимаете на значок расширения в адресной строке.

Создайте файл popup.html:

<!DOCTYPE html>
<html>
  <head>
  </head>
  <body style="padding: 1em;">
    <button id="start">Start transcription</button>
    <p id="transcript"></p>
    <script src="popup.js"></script>
  </body>
</html>
Вход в полноэкранный режим Выход из полноэкранного режима

В файле manifest.json укажите файл всплывающего окна, добавив это свойство:

"action": {
    "default_popup": "popup.html"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Возможно, вы заметили, что связанный файл JavaScript еще не существует. Прежде чем мы создадим его, важно отметить, что как только всплывающее окно будет закрыто, страница как будто перестанет существовать, и код перестанет выполняться. По этой причине расширение должно внедрить некоторый код для выполнения на текущей веб-странице. Это означает, что код продолжит выполняться даже после закрытия всплывающего окна.

Для этого создайте файл popup.js:

document.getElementById('start').addEventListener('click', async () => {
    const tab = await getCurrentTab()
    if(!tab) return alert('Require an active tab')
    chrome.scripting.executeScript({
        target: { tabId: tab.id },
        files: ['main.js']
    })
})

async function getCurrentTab() {
    const queryOptions = { active: true, lastFocusedWindow: true }
    const [tab] = await chrome.tabs.query(queryOptions)
    return tab
}
Вход в полноэкранный режим Выход из полноэкранного режима

Когда будет нажата кнопка запуска, она получит активную вкладку и внедрит файл main.js. Создайте его:

alert('This is an injected script!')
Войти в полноэкранный режим Выход из полноэкранного режима

Откройте расширение и нажмите кнопку. Вы должны увидеть оповещение! Удалите предупреждение, прежде чем двигаться дальше.

Транскрибирование аудиозаписи вкладки

В файле main.js запросите доступ к дисплею пользователя, проверьте, что к нему подключен звук, и подключите его к MediaRecorder:

navigator.mediaDevices.getDisplayMedia({ video: true, audio: true }).then(stream => {
    if(stream.getAudioTracks().length == 0) return alert('You must share your tab with audio. Refresh the page.')
    const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' })

    // Further code here
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Попробуйте. Когда вы предоставляете общий доступ к вкладке, убедитесь, что вы также предоставляете общий доступ к звуку вкладки. Если нет, мы настроили предупреждение, чтобы показать ошибку и остановить дальнейшее выполнение кода.

Подключитесь к Deepgram с помощью WebSocket и, как только соединение будет открыто, начните отправлять аудиоданные вкладки:

socket = new WebSocket('wss://api.deepgram.com/v1/listen?tier=enhanced', ['token', 'YOUR_DEEPGRAM_API_KEY'])

recorder.addEventListener('dataavailable', evt => {
    if(evt.data.size > 0 && socket.readyState == 1) socket.send(evt.data)
})

socket.onopen = () => { recorder.start(250) }

// Further code here
Войти в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что socket находится в глобальной области видимости (об этом свидетельствует отсутствие ключевых слов var, let или const), чтобы мы могли позже закрыть соединение.

Затем прослушайте транскрипты, выданные Deepgram:

socket.onmessage = msg => {
    const { transcript } = JSON.parse(msg.data).channel.alternatives[0]
    if(transcript) {
        console.log(transcript)
    }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Перейдите на вкладку с аудио, начните транскрибировать и посмотрите в инструментах разработчика вашего браузера.

Отлично! Все определенно сходится.

Передача данных из сценария контента во всплывающее окно

Не стоит ожидать, что пользователи откроют консоль браузера, чтобы увидеть транскрипты. Вы можете отправлять «сообщения» из внедренного скрипта во всплывающее окно, но если окно будет закрыто, они не будут получены. Итак, вот план:

  1. Когда появится новая расшифровка, поместите ее в хранилище chrome.
  2. Отправьте сообщение из внедренного скрипта во всплывающее окно, чтобы сказать, что доступна новая расшифровка.
  3. Если всплывающее окно открыто, отобразить последнюю расшифровку из хранилища.
  4. Когда всплывающее окно откроется, получите последнюю расшифровку (даже если сообщения пропущены, это позволит нам получить актуальную информацию).

Chrome Storage — это специфический для расширений API, который действует аналогично localStorage, но более специализирован для нужд расширений и может быть синхронизирован с помощью Chrome Sync (в данном расширении это не так).

В самом верху main.js, поверх всего остального кода, создайте новый ключ транскрипта в хранилище Chrome и задайте начальное значение пустой строки:

chrome.storage.local.set({ transcript: '' })
Войти в полноэкранный режим Выйти из полноэкранного режима

Замените console.log(transcript) на:

chrome.storage.local.get('transcript', data => {
    chrome.storage.local.set({
      transcript: data.transcript += ' ' + transcript
    })

    // Throws error when popup is closed, so this swallows the errors with catch.
    chrome.runtime.sendMessage({
      message: 'transcriptavailable'
    }).catch(err => ({}))
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Это позволяет получить существующую расшифровку и добавить новую расшифровку в ее конец. Затем отправляется сообщение со значением ‘transcriptavailable’, которое мы теперь можем прослушать в popup.js.

В нижней части popup.js:

chrome.runtime.onMessage.addListener(({ message }) => {
    if(message == 'transcriptavailable') {
        showLatestTranscript()
    }
})

function showLatestTranscript() {
    chrome.storage.local.get('transcript', ({ transcript }) => {
        document.getElementById('transcript').innerHTML = transcript
    })
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Также получите последнюю расшифровку в самом верху popup.js, над всем остальным кодом:

showLatestTranscript()
Войти в полноэкранный режим Выйти из полноэкранного режима

Остановка транскрипции

Добавьте кнопку, чуть ниже кнопки запуска, в popup.html:

<button id="stop">Stop transcription</button>
Войти в полноэкранный режим Выйти из полноэкранного режима

Когда кнопка будет нажата, отправьте сообщение обратно внедренному скрипту. В popup.js:

document.getElementById('stop').addEventListener('click', async () => {
    const tab = await getCurrentTab()
    if(!tab) return alert('Require an active tab')
    chrome.tabs.sendMessage(tab.id, { message: 'stop' })
})
Войти в полноэкранный режим Выйти из полноэкранного режима

В самом низу main.js, ниже всего остального кода, получите сообщение и закройте WebSocket-соединение с Deepgram:

chrome.runtime.onMessage.addListener(({ message }) => {
    if(message == 'stop') {
        socket.close()
        alert('Transcription ended')
    }
})
Войти в полноэкранный режим Выйти из полноэкранного режима

Превосходно.

Создание страницы параметров

Сейчас ваш API-ключ Deepgram закодирован прямо в приложении. Далее вы создадите страницу опций, чтобы пользователь мог ввести свой ключ, сохранить его в хранилище Chrome и использовать это значение при подключении к Deepgram.

В manifest.json добавьте следующее свойство:

"options_page": "options.html"
Войти в полноэкранный режим Выйти из полноэкранного режима

Создайте и откройте файл options.html:

<!DOCTYPE html>
<html>
  <body>
    <h1>Provide your Deepgram API Key</h1>

    <input type="text" id="api">
    <button>Save</button>

    <script src="options.js"></script>
  </body>
</html>
Войти в полноэкранный режим Выйти из полноэкранного режима

Создайте и откройте файл options.js:

const api = document.getElementById('api')
const button = document.querySelector('button')

// If it exists, load it in
chrome.storage.local.get('key', ({ key }) => {
  if(key) api.value = key
})

button.addEventListener('click', () => {
  const key = api.value
  chrome.storage.local.set({ key }, () => {
    alert('Deepgram API Key Set')
  })
})
Войдите в полноэкранный режим Выход из полноэкранного режима

Пора воспользоваться ключом. В верхней части файла main.js, над всем остальным кодом:

let apiKey
chrome.storage.local.get('key', ({ key }) => apiKey = key)
Войти в полноэкранный режим Выйти из полноэкранного режима

После этого apiKey будет либо undefined, либо строкой с ключом API.

Замените следующее в файле main.js:

socket = new WebSocket('wss://api.deepgram.com/v1/listen?tier=enhanced', ['token', 'YOUR_DEEPGRAM_API_KEY'])

// Replace with 👇

if(!apiKey) return alert('You must provide a Deepgram API Key in the options page.')
socket = new WebSocket('wss://api.deepgram.com/v1/listen?tier=enhanced', ['token', apiKey])
Войти в полноэкранный режим Выйти из полноэкранного режима

Щелкните расширение правой кнопкой мыши и выберите Options, чтобы открыть новую страницу. Сохраните свой API-ключ Deepgram, и расширение будет работать.

Доступ к аудио и микрофону вкладки браузера

Гипотетическая ситуация — вы хотите расшифровать браузерный видеозвонок с помощью этого расширения. Все голоса будут расшифрованы, кроме вашего — это потому, что ваш звук не проходит через вкладку (иначе вы бы услышали себя!), поэтому давайте изменим это расширение так, чтобы ваш микрофон и звук вкладки были расшифрованы вместе.

Если вы хотите транскрибировать только звук вкладки, пропустите до конца.

На данный момент в main.js вы запрашиваете отображение пользователя, проверяете наличие звука и передаете полученный поток в MediaRecorder. Теперь мы должны:

  1. Получить доступ к пользовательскому дисплею и проверить, есть ли там звук.
  2. Получить доступ к аудиоустройству пользователя (микрофону).
  3. Создать новый, пустой AudioContext.
  4. Смешайте два источника звука вместе как источники в одном AudioContext.
  5. Создайте MediaRecorder с AudioContext, содержащим теперь два источника.

В самом низу файла main.js, ниже всего остального кода:

// https://stackoverflow.com/a/47071576
function mix(audioContext, streams) {
    const dest = audioContext.createMediaStreamDestination()
    streams.forEach(stream => {
        const source = audioContext.createMediaStreamSource(stream)
        source.connect(dest)
    })
    return dest.stream
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Замените следующее в main.js:

const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' })

// Replace with 👇

const micStream = await navigator.mediaDevices.getUserMedia({ audio: true })
const audioContext = new AudioContext()
const mixed = mix(audioContext, [stream, micStream])
const recorder = new MediaRecorder(mixed, { mimeType: 'audio/webm' })
Войти в полноэкранный режим Выйти из полноэкранного режима

Добавьте ключевое слово async непосредственно перед stream в функции .then():

navigator.mediaDevices.getDisplayMedia({ video: true, audio: true }).then(await stream => {
Вход в полноэкранный режим Выход из полноэкранного режима

Бум. Готово.

Следующие шаги

Вы можете многое сделать для улучшения своего расширения Chrome — сделать его более красивым с помощью CSS, изменить способ отображения транскриптов или изменить значок расширения во время записи. Вы также можете рассмотреть возможность использования функций Deepgram, таких как диаризация, для обнаружения разных дикторов и отображения их по-разному.

Полный готовый код этого проекта вы можете найти на GitHub по адресу deepgram-devs/transcription-chrome-extension. Как всегда, если у вас есть вопросы, пожалуйста, обращайтесь в Twitter (мы @DeepgramDevs).

Оцените статью
devanswers.ru
Добавить комментарий