Голосовое управление мультимедиа центром

от автора

В этой статье хотелось бы описать свой опыт по применению web speech api в браузере Google Chrome для реализации голосового поиска и автоматического воспроизведения видеороликов с канала Youtube. Для демонстрации данного функционала нам понадобиться сделать следующие шаги:

  1. Установить набор: Apache2, PHP5(пакет curl обязательно).
  2. Иметь в наличии мультимедиа центр Dune HD или установить XBMC и настроить его для работы в сети INTERNET.
  3. Получить Youtube API Key для выполнения поисковых запросов.

Как сделать все вышеперечисленное, здесь описывать не буду, так как на эти темы полно статей. Принцип реализации такой:

  1. Распознаем фразу с помощью скрипта, написанного на JavaScript — работать будет только в Google Chrome.
  2. Ищем ролики, соответствующие поисковому запросу.
  3. Получаем прямые ссылки на ролики.
  4. Создаем плейлист из ссылок и названий роликов.
  5. Отправляем плейлист для воспроизведения на устройство.


Топология сети: Internet приходит в wan порт Wi-Fi роутера, а к нему подключаются:

  • устройство, с которого будем управлять (планшет, смартфон, ноутбук).
  • компьютер с web сервером Apache (если управление будет производится с него, то первое устройство может отсутствовать).
  • собственно сам мультимедиа центр (программный — XBMC, или аппаратный — Dune HD).

Скрипт распознавания речи на JavaScript — index.html:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru"> <head> <title>Умный Дом</title> <script language="javascript" type="text/javascript"> /* Создание нового объекта XMLHttpRequest для общения с Web-сервером */ var xmlHttp = false; /*@cc_on @*/ /*@if (@_jscript_version >= 5) try {   xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {   try {     xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");   } catch (e2) {     xmlHttp = false;   } } @end @*/  if (!xmlHttp && typeof XMLHttpRequest != 'undefined') {   xmlHttp = new XMLHttpRequest(); } </script> <style>   * {     font-family: Verdana, Arial, sans-serif;     font-size: 20px;   }   a:link {     color:#000;     text-decoration: none;   }   a:visited {     color:#000;   }   a:hover {     color:#33F;   }   body {       text-align: center;   }   .button {     background: -webkit-linear-gradient(top,#008dfd 0,#0370ea 100%);     border: 1px solid #076bd2;     border-radius: 3px;     color: #fff;     display: none;     font-size: 13px;     font-weight: bold;     line-height: 1.3;     padding: 8px 25px;     text-align: center;     text-shadow: 1px 1px 1px #076bd2;     letter-spacing: normal;   }      .final {     color: black;     padding-right: 3px;   }   .interim {     color: gray;   }   .info {     font-size: 14px;     text-align: center;     color: #777;     display: none;   }      .sidebyside {     display: inline-block;     width: 45%;     min-height: 40px;     text-align: left;     vertical-align: top;   }   #headline {     font-size: 40px;     font-weight: 300;   }   #info {     font-size: 20px;     text-align: center;     color: #777;     visibility: hidden;   }   #results {     font-size: 14px;     font-weight: bold;     border: 1px solid #ddd;     padding: 15px;     text-align: left;     min-height: 30px;     width: 500px;     margin: 0 auto;   }   #start_button {     border: 0;     padding: 0;     background: url(images/mic.gif);     width: 50px;     height: 50px;     cursor: pointer;     vertical-align: top;   }  #info_speak_now, #info_no_speech, #info_no_microphone, #info_upgrade {     display: none; }    </style> <meta charset="UTF-8" /> </head> <body> <div id="messages"> 	 <input type="button" id="start_button" onclick="startButton(event);" /> 	<!-- сообщения на разные случаи --> 	<p id="info_start">Кликни на микрофон чтобы начать раздавать команды.</p> 	<p id="info_speak_now">Командуй!</p> 	<p id="info_no_speech">Голос не обнаружен.</p> 	<p id="info_no_microphone">Микрофон не найден.</p> 	<p id="info_upgrade">Твой браузер не поддерживает Web Speech API.</p> </div> <div id="results">   <span id="final_span" class="final"></span> </div> <script> var start_button = document.getElementById('start_button'), 	recognizing = false, // флаг идет ли распознование 	final_transcript = '';  // проверяем поддержку speach api if (!('webkitSpeechRecognition' in window)) { 	 	start_button.style.display = "none"; 	showInfo("info_upgrade"); 	 } else { /* инициализируем api */    /* создаем объект 	*/  var recognition = new webkitSpeechRecognition();    /* базовые настройки объекта */    recognition.lang = 'ru'; // язык, который будет распозноваться. Значение - lang code  recognition.continuous = true; // не хотим чтобы когда пользователь прикратил говорить, распознование закончилось    /* метод вызывается когда начинается распознование */  recognition.onstart = function() { 	      recognizing = true; 	     showInfo('info_speak_now'); // меняем инфо текст     start_button.style.background = 'url(images/mic-animate.gif)'; // меняем вид кнопки        };      /* обработчик ошибок */   recognition.onerror = function(event) {     if (event.error == 'no-speech') {       start_button.style.background = 'url(images/mic.gif)';       showInfo('info_no_speech');     }     if (event.error == 'audio-capture') {       start_button.style.background = 'url(images/mic.gif)';       showInfo('info_no_microphone');     }   };      /* метод вызывается когда распознование закончено */   recognition.onend = function() {  	recognizing = false;  	recognition.start(); 	start_button.style.background = 'url(images/mic.gif)'; 	showInfo('info_start');    };      /*    	метод вызывается после каждой сказанной фразы. Параметра event используем атрибуты: 	- resultIndex - нижний индекс в результирующем массиве 	- results - массив всех результатов в текущей сессии  */   recognition.onresult = function(event) { 	      	  /*  	  	обход результирующего массива 	  */ 	  for (var i = event.resultIndex; i < event.results.length; ++i) { 		 		/* если фраза финальная (уже откорректированная) сохраняем в конечный результат */       	if (event.results[i].isFinal) {         	final_transcript += event.results[i][0].transcript.toLowerCase();         	      	 }       }         final_span.innerHTML = final_transcript; 	 var newText2 = final_transcript.replace(/(^\s+|\s+$)/g,''); 	 var url = "/voice_search.php?q=" + encodeURI(newText2);   	 xmlHttp.open("GET", url, true);   	 xmlHttp.send(null); 	 	 final_transcript = '';	// очищаем рапознанный текст 	   };   }  /* показ нужного сообщения */ function showInfo(id) { 		 	var messages = document.querySelectorAll('p'); 	 	for(i=0; i<messages.length; i++) messages[i].style.display = 'none';  	document.getElementById(id).style.display = 'block'; }   /* обработчик клика по микрофону */ function startButton(event) {    if (recognizing) { // если запись уже идет, тогда останавливаем     recognition.stop(); 	document.getElementById('final_span').innerHTML = '';     return;   }     recognition.start();    } </script> </body> </html> 

Для полноценной работы скрипта, нужно еще создать папку images и положить в неё картинки с микрофончиками, которые можно взять здесь и здесь.

Данный скрипт делает всего два действия — распознает фразу и отправляет её AJAX запросом PHP скрипту. Также необходимо обратить внимание на то, что кодировка во всех скриптах должна быть UTF-8 (если делаете в Windows, то UTF-8 без ВОМ).

Скрипт поиска видео роликов на PHP — voice_search.php:

<?php // send info into core function send_info($info) { 	echo $info; 	 } function send_req($url)  	{ 	   $ch = curl_init(); 		curl_setopt($ch, CURLOPT_USERAGENT,				"Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"); 		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 		FALSE); 		curl_setopt($ch, CURLOPT_HEADER,				false); 		curl_setopt($ch, CURLOPT_FOLLOWLOCATION,		true); 		curl_setopt($ch, CURLOPT_URL,					$url); 		curl_setopt($ch, CURLOPT_REFERER,				$url); 		curl_setopt($ch, CURLOPT_RETURNTRANSFER,		TRUE); 		$out = curl_exec($ch); 		curl_close($ch); 		return $out; 	} function get_video_url($videoId)  { 	  			$url = 'http://www.youtube.com/get_video_info?&video_id='.$videoId.'&asv=3&el=detailpage&hl=en_US';  			   			$first_found = "";       	$last_found = "";       	$first_quality = "";       	$last_quality = "";       	//$video_quality = 'medium'; 			$video_quality = 'hd1080'; 			$doc=send_req($url); 		 			$x=explode("&",$doc); 			$t=array(); $g=array(); $h=array(); 			foreach($x as $r) 			{ 				$c=explode("=",$r); 				$n=$c[0]; $v=$c[1]; 				$y=urldecode($v); 				$t[$n]=$v; 		 			} 			$links = explode(',',urldecode($t['url_encoded_fmt_stream_map'])); 			$dlinks = array(); 			foreach ($links as $link)  			{ 				parse_str($link,$linkarr); 				$itag = $linkarr['itag']; 				$quality = $linkarr['quality']; 		 		 				if (in_array($itag, array('18', '22', '37', '38'))) 				{ 					if(isset($linkarr['s']))  					{ 						$linkarr['signature'] = file_get_contents('http://dune-club.info/echo?message=' . $linkarr['s']); 						unset($linkarr['s']);	 						$dlinks[$linkarr['itag']] = $linkarr['url'] . "&signature=" . $linkarr['signature']; 					} 					else 					{ 						$dlinks[$linkarr['itag']] = $linkarr['url']; 						$playback_url = $dlinks[$linkarr['itag']]; 						if ($first_found === "")  						{         					$first_found = $playback_url;         					$first_quality = $quality;         				}         				$last_found = $playback_url;         				$last_quality = $quality;        				if (($quality === $video_quality) || (($quality !== 'medium') && ($video_quality === 'hdonly')))         				{        					$playback_url=(urldecode($playback_url));        					return $playback_url;        				}        			}        		}        				        	} 			if (($last_found !== "") && ($video_quality !== 'hdonly'))  			{             if ($video_quality === 'hd1080')              {               $first_found=(urldecode($first_found));               return $first_found;             }              else              {                  $last_found=(urldecode($last_found));                  return $last_found;             } 			}  			else  			{             //hd_print("--> video: $id; no mp4-stream.");             return false; 			}     }    if(isset($_GET['q']) == false or $_GET['q']=="" )  	{ 		 			$url = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=голосовое%20управление%20телевизором&type=video&maxResults=10&key=Youtube_API_key"; 		  	} 	else  	{ 		$url = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=".urlencode($_GET['q'])."&type=video&maxResults=10&key=Youtube_API_key"; 		 	}     $res = json_decode(send_req($url));   if(isset($res->items) == false or ($res->items)=="" )  	{ 		$info="похоже что-то пошло не так!";																 		send_info($info);  	} 	else  	{   		$res = $res->items;   		//print_r($res);   		$fp = fopen('play_list.m3u', 'w+t'); 		$start="#EXTM3U\r\n"; 		fwrite($fp, $start);   		foreach ($res as $searchResult)    		{  			   			$title=($searchResult->snippet->title);   			$videoId = ($searchResult->id->videoId) ;    			$clip_url = get_video_url($videoId);   			if(isset($clip_url) == false or $clip_url=="")    			{ 				$info="клип не найден!";																 				send_info($info);   			}   			else    			{	   				$clip="#EXTINF:-1,$title\r\n$clip_url\r\n"; 				fwrite($fp, $clip); 			}   		}   		fclose($fp);   		$info="плейлист создан";																   		send_info($info); 		//url для Dune HD  	       $url="http://ip_addr_dune/cgi-bin/do?cmd=start_file_playback&media_url=http://server_ip/play_list.m3u"; 	      //url для XBMC  	      $url="http://логин:пароль@ip-адрес:8080/jsonrpc?request={"jsonrpc":"2.0","id":"1","method":"Player.Open","params":{"item":{"file":"http://server_ip/play_list.m3u"}}}"; 	      $curl = curl_init();  	      curl_setopt($curl, CURLOPT_URL, $url);  	      curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);              $out = curl_exec($curl); 	     curl_close($curl);   	} ?> 

В данном скрипте в самом конце нужно будет отредактировать $url под настройки вашего мультимедиа центра и удалить лишний, а также исправить текст server_ip на ip адрес вашего Apache сервера и вставить свой Youtube_API_Key. Что происходит здесь: из скрипта распознавания речи сюда приходит текст распознанной фразы, далее с помощью Youtube API v3 производится поиск видео роликов подходящих под поисковой запрос, получив ссылки на ролики, мы пропускаем их через цикл, в котором извлекаются полные пути до видеофайлов, которые и записываются в плейлист play_list.m3u. Данный скрипт не претендует на идеальный код, так как написан чисто в ознакомительных целях, поэтому всевозможные проверки здесь отсутствуют.

Вот и всё, теперь заходим на наш web сервер по его ip адресу. Можно заходить с любого устройства: планшет, смартфон, ноутбук, единственное, что я заметил — в последнее время на смартфонах с Android, скрипт распознавания речи при отсутствии её, отправляет фразу повторно, с чем это связанно пока не понятно, но раньше такого не было.

На основе данного материала можно сделать еще много интересных вещей, таких как голосовой поиск музыки в VK и управления 1-wire устройствами. В общем пробуйте, если что не получится, то спрашивайте с удовольствием отвечу на все ваши вопросы.

P.S.: Статья написана по материалам:

W3C Web Speech API
YouTube api v3

Скрипт получения прямых ссылок взят из приложения YouTube для Dune HD и немного доработан под свои нужды. Для тех, кто хочет просто попробовать управлять мультимедиа центрами, без написания скриптов — можно почитать здесь. Результат моей работы на YouTube

ссылка на оригинал статьи http://habrahabr.ru/post/270809/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *