Эта статья была рецензирована Марком Таулером . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
Преобразование текста в речь, также известное как синтез речи или TTS (преобразование текста в речь), является искусственным способом воспроизведения человеческой речи. Согласно Википедии, это не что-то новое, поскольку люди пытались создавать машины, которые производят человеческую речь, по крайней мере, тысячу лет.
Сегодня TTS становится все более и более повсеместным в нашей жизни, и каждый может воспользоваться этим. Мы продемонстрируем это, создав расширение Chrome, которое преобразует текст в речь. HTML5 предоставил нам API для синтеза речи, который позволяет любому веб-приложению преобразовывать произвольную текстовую строку в речь и проигрывать ее своим пользователям без каких-либо затрат.
Расширение для Chrome обычно состоит из следующих вещей:
- Манифест (обязательный файл с метаданными)
- Изображения (например, значок расширения)
- HTML-файлы (например, всплывающее окно, которое будет отображаться, когда пользователи нажимают на значок расширения)
- Файлы JavaScript (такие как контент и / или фоновые скрипты, которые будут объяснены чуть позже)
- Любой другой актив, который может использоваться вашим приложением (например, таблица стилей)
О расширении страницы до речи
Из-за популярности Chrome и роста TTS мы будем создавать расширение Chrome, которое преобразует текст в речь. Расширение будет ждать, пока пользователь не нажмет на его значок или не нажмет специальную горячую клавишу ( shift + Y
), а затем попытается либо найти то, что пользователь выделил на странице, которую он просматривает в данный момент, либо попытается найти что копируется в их буфер обмена. Если что-то есть, оно преобразует это в речь, сначала пытаясь использовать API синтеза речи HTML5, а если это недоступно — вызывая сторонний API.
Основы расширения Chrome
Каждое расширение Chrome должно иметь файл manifest.json . Манифест представляет собой файл в формате JSON, который содержит данные, важные для приложения, начиная от таких вещей, как имя, описание, значки и автор расширения, и заканчивая данными, определяющими потребности расширения — какие веб-сайты должны расширять его возможности. запустить (это будут разрешения, которые должен предоставить пользователь) или какие файлы запускать, когда пользователь просматривает определенный веб-сайт.
{ "manifest_version": 2, "name": "Page to Speech", "description": "This extension will produce English speech to whatever text you highlight on a webpage.Highlight text and click the extension's icon", "author": , "version": "1.0", "icons": { "16": "icon16.png", "48": "icon48.png", "128": "icon128.png" },
Наш манифест начинается с документирования имени, описания, автора, версии и значков расширения. Вы можете предоставить множество значков, которые реагируют на различные размеры в объекте icons
.
"background": { "scripts": ["background.min.js"] }, "content_scripts": [ { "matches": ["http://*/*", "https://*/*"], "js": [ "polyfill.min.js", "ext.min.js"], "run_at": "document_end" }],
Затем у нас есть фоновый скрипт background.min.js
(обратите внимание, что мы используем уменьшенные файлы), определенный в background
объекте. Фоновые скрипты — это скрипты, которые работают долго и будут работать до тех пор, пока браузер пользователя не закроется или расширение не будет отключено.
После этого у нас есть массив content_scripts
который инструктирует Chrome загружать два файла JavaScript при каждом запросе веб-сайта из-за подстановочных знаков "http://*/*"
и "https://*/*"
. Сценарии содержимого , в отличие от фоновых, имеют доступ к DOM реального веб-сайта, который посещает пользователь. Сценарии содержимого могут как читать, так и вносить изменения в DOM любой веб-страницы, в которую они встроены. Поэтому наши polyfill.min.js
и ext.min.js
смогут читать и изменять все данные на каждой веб-странице.
"browser_action": { "default_icon": "speech.png" }, "permissions": [ "activeTab", "clipboardRead" ] }
Не так быстро! У нас есть еще один массив с именем permissions
с помощью которого мы запрашиваем доступ только к той веб-странице, которая в данный момент открыта пользователем (активная вкладка). Мы также просим другое разрешение, называемое clipboardRead
которое позволит нам читать буфер обмена пользователя (чтобы мы могли преобразовать его содержимое в речь).
Кодирование страницы в расширение Chrome
Во-первых, мы создаем наш единственный фоновый скрипт, который подключает прослушиватель событий, который будет запущен, когда пользователь нажимает на значок расширения. Когда это происходит, мы вызываем функцию sendMessage
которая отправляет сообщение нашему скрипту контента (скрипт контента может прочитать DOM и узнать, что пользователь выделил или / и что пользователь поместил в его буфер обмена) с помощью chrome.tabs.sendMessage(tabId, message, callback)
. Мы отправляем сообщение на открытую в данный момент вкладку — поскольку это то, что нас интересует и к чему мы имеем доступ, — с помощью метода chrome.tabs.query
, аргументы которого включают в себя обратный вызов, который будет вызываться с аргументом, который содержит вкладки, соответствующие запросу.
chrome.browserAction.onClicked.addListener(function (tab) { //fired when the user clicks on the ext's icon sendMessage(); }); function sendMessage() { chrome.tabs.query({active: true, currentWindow: true}, function(tabs){ chrome.tabs.sendMessage(tabs[0].id, {action: "pageToSpeech"}, function(response) {}); }); }
Теперь самая длинная часть — это наш контент-скрипт. Мы создаем объект, который будет содержать некоторые данные, связанные с расширением, а затем определим наш метод инициализации.
initialize: function() { if (!pageToSpeech.hasText()) { return;} if (!pageToSpeech.trySpeechSynthesizer()) { pageToSpeech.trySpeechApi(); } },
Метод проверяет, не выделил ли пользователь текст или нет ли у него чего-либо в буфере обмена, и просто возвращается в таком случае. В противном случае он пытается произвести речь с помощью API синтеза речи HTML5. Если это тоже не удается, он, наконец, пытается использовать сторонний API.
Метод, который проверяет текст, делает несколько вещей. Он пытается получить объект с выделенным текстом с помощью встроенного метода getSelection()
и преобразовать его в текстовую строку с помощью toString()
. Затем, если текст не выделен, он пытается найти текст в буфере обмена пользователя. Это делается путем добавления элемента ввода на страницу, фокусировки его, execCommand('paste')
события вставки с помощью execCommand('paste')
и последующего сохранения вставленного текста внутри этого ввода в свойстве. Затем он очищает вход. В любом случае, он возвращает все, что нашел.
hasText: function() { this.data.highlightedText = window.getSelection().toString(); if (!this.data.highlightedText) { var input = document.createElement("input"); input.setAttribute("type", "text"); input.id = "sandbox"; document.getElementsByTagName("body")[0].appendChild(input); var sandbox = document.getElementById("sandbox"); sandbox.value = ""; sandbox.style.opacity = 0; sandbox.focus(); if (document.execCommand('paste')) { this.data.highlightedText = sandbox.value; } sandbox.value = ""; } return this.data.highlightedText; },
Чтобы позволить пользователю выполнять преобразование текста в речь с помощью горячей клавиши (жестко запрограммированной на shift + Y
), мы инициализируем массив и устанавливаем прослушиватель событий для событий onkeydown
и onkeyup
. В слушателях мы храним индекс, соответствующий keyCode
нажатой клавиши, значение которого является результатом сравнения типа события e.type
с e.type
и является логическим значением. Следовательно, всякий раз, когда ключ не работает, значение соответствующего индекса ключа будет установлено равным true
а при каждом отпускании ключа значение индекса будет изменено на false
. Таким образом, если оба индекса 16 и 84 содержат истинное значение — мы знаем, что пользователь использует наши горячие клавиши, поэтому мы инициализируем преобразование текста в речь.
addHotkeys: function() { var activeKeys = []; onkeydown = onkeyup = function(evt) { var e = evt || event; activeKeys[e.keyCode] = e.type == 'keydown'; if (activeKeys[16] && activeKeys[84]) { pageToSpeech.initialize(); } }; }
Для преобразования текста в речь мы используем метод trySpeechSynthesizer()
. Если в браузере пользователя существует HTML5 Speech Synthesis ( window.speechSynthesis
), мы знаем, что пользователь может его использовать, и поэтому проверяем, выполняется ли речь в данный момент (мы знаем, выполняется ли она через логический pageToSpeech.data.speechInProgress
) , Мы останавливаем текущую речь, если она продолжается (так как trySpeechSynthesizer
начнет новую речь, и нам не нужны два одновременных звука). Затем мы устанавливаем для speechInProgress
значение true
и всякий раз, когда речь заканчивается, снова устанавливают для свойства ложное значение.
Теперь я не хочу вдаваться в подробности о том, почему мы используем speechUtteranceChunker
но это исправление ошибки, связанной с тем, что Chrome останавливает синтез речи, пока он еще выполняется после того, как произнесено 200-300 сотен слов. По сути, он разбивает нашу текстовую строку на множество более мелких фрагментов (в нашем случае это 120 слов) и вызывает API синтеза речи с одним фрагментом за другим.
trySpeechSynthesizer: function() { if (window.speechSynthesis ) { //new speech is about to get started if (this.data.speechInProgress) { polyfills.speechUtteranceChunker.cancel = true; } this.data.speechInProgress = true; var msg = new SpeechSynthesisUtterance(this.data.highlightedText); //speechSynthesis.speak(msg); // Chrome Implementation BUG: http://stackoverflow.com/questions/21947730/chrome-speech-synthesis-with-longer-texts polyfills.speechUtteranceChunker(msg, { chunkLength: 120 },function() { //speech has finished pageToSpeech.data.speechInProgress = false; });
Наконец, если API синтеза речи HTML5 недоступен, мы попробуем API. У нас есть то же свойство, которое используется для определения необходимости остановки уже запущенного аудио. Затем мы напрямую создаем новый объект Audio
и передаем ему URL-адрес к желаемой конечной точке API, поскольку API, выбранный нами для демонстрации, напрямую передает аудио. Мы просто передаем ему наш ключ API и текст для преобразования. Мы также проверяем, если аудио вызывает ошибку. В таком случае мы просто показываем пользователю alert
о том, что в настоящее время мы не можем помочь (этот конкретный API, Voice RSS , разрешает 300 запросов на бесплатном уровне, с которым мы тестировали код).
trySpeechApi: function() { if (this.data.speechInProgress) { this.data.fallbackAudio.pause(); } this.data.speechInProgress = true; this.data.fallbackAudio = new Audio("http://api.voicerss.org/?key=your_api_key&src=" + this.data.highlightedText); this.data.fallbackAudio.addEventListener("error", function(evt) { alert("Sorry, we cannot produce speech right now. Try upgrading your Chrome browser!"); }) this.data.fallbackAudio.play(); this.data.fallbackAudio.onended = function() { pageToSpeech.data.speechInProgress = false; } },
В конце, вне любой локальной области мы вызываем метод addHotkeys
который начнет ждать, пока пользователь addHotkeys
правую горячую клавишу, и мы addHotkeys
слушателя, который будет ждать, пока сообщение не будет получено из фонового скрипта. Если получено правильное сообщение ( speakHighlight ) или нажата горячая клавиша, мы инициализируем наш объект преобразования текста в речь.
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) { if (msg.action == 'pageToSpeech') { pageToSpeech.initialize(); } }); pageToSpeech.addHotkeys();
Вывод
И вуаля, у нас есть хорошее расширение Chrome, которое преобразует текст в речь. Понятия здесь могут быть использованы для создания расширений Chrome для различных целей. Вы создали какие-нибудь интересные расширения для Chrome или вы хотите создать одно? Дай мне знать в комментариях!
Если вам понравилась идея и вы хотите развить ее дальше, вы можете найти полный код в нашем репозитории GitHub . Производственную версию расширения можно найти в Chrome Web Store, если вы хотите протестировать его.
Ссылки:
https://en.wikipedia.org/wiki/Speech_synthesis#History
http://stackoverflow.com/questions/21947730/chrome-speech-synthesis-with-longer-texts