世界では毎日、数百万のユーザーがメール、ソーシャルネットワーク、およびチャットプラットフォームやメッセージボードなど、他のオンラインコミュニティを通じて互いに通信しています。しばしばユーザーは特定のコミュニティに興味を抱いてチャットの会話に参加したいと思いますが、オンラインコミュニティの第一言語がユーザーが理解できる言語とは異なる場合があります。
このようなコミュニティの 1 つがゲーマー向けの世界最大のソーシャルビデオサービスである Twitch です。毎日、共有している関心事について見たり、話したり、チャットしたりするために、数百万のコミュニティメンバーが集まります。チャットはどのストリームにも内蔵されているので、ストリームを受動的に見ている代わりに、ショーに参加することができます。あなたは Twitch にはあなたの言語を必ずしも話す必要のない多くのストリーマーがいることに気付きますが、それでもあなたはストリームを楽しんだり、チャットに参加したりしたいと思うかもしれません。
このようなコミュニケーションを実現するため、AWS は、高速、高品質、適正価格の言語翻訳機能を備えたニューラルマシン翻訳サービスである Amazon Translate を用意しています。Amazon Translate は、ユーザー間のクロスリンガルコミュニケーションを可能にするオンデマンド翻訳機能を提供します。チャット、メール、ヘルプデスク、チケット発行アプリケーションにリアルタイム翻訳機能を追加すれば、英語を話す担当者や従業員は複数の言語で顧客とコミュニケーションをとることができるようになります。
このブログポストは、Twitch チャネルで Amazon Translate を使ってクロスリンガルチャットを実現する方法をカバーします。あなたはまた、受信したリアルタイムメッセージを有声音で発音するテキスト読み上げサービス、Amazon Polly の使用法も学習します。この追加機能によって、アクセス機能が強化され、ユーザーは、他のタスクに集中しながらメッセージを聞くことができます。
ソリューションの概要
このポストでは、CSS と JavaScript を備えた HTML ページを作成します。JavaScript ライブラリを使用して、Twitch チャネルに接続し、メッセージの受信を開始します。リアルタイムメッセージを受信すると、AWS SDK を使用して Amazon Translate を呼び出し、翻訳されたこれらのメッセージを UI に表示します。音声オプションを有効にした場合、AWS SDK を使用して Amazon Polly を呼び出し、音声合成されたこれらのメッセージをユーザー先で再生します。
Twitch アカウントを作成して構成する
- twitch.tv に移動して、Twitch アカウントにサインアップします。
- Twitch アカウントのサインアップに成功したら、https://twitchapps.com/tmi に移動して、Twitch OAuth トークンを作成します。
- [Connect with Twitch (Twitch に接続)] ボタンをクリックして、OAuth トークンを指定します。
IAM ユーザーを作成する
AWS Identity and Access Management (IAM) を使用すると、AWS のサービスおよびリソースへのアクセスを安全に管理することができます。IAM を使用すると、AWS のユーザーおよびグループを作成して管理し、権限を使用して AWS リソースへのアクセス権を許可したり拒否したりすることができます。 最初に、この例を実行するのに最低限必要な権限のみを備えた IAM ユーザーを作成します。
- Go to https://console.aws.amazon.com/iam/home?region=us-east-1#/users.
- [Add user (ユーザーの追加)] を選択します。
- ユーザー名を入力し、[Programmatic access (プログラムによるアクセス)] にチェックを付けてから、[Next Permissions (次の権限)] を選択します。
- [Attach existing policies directly (既存のポリシーに直接接続する)] を選択し、翻訳を検索します。
- [TranslateReadOnly] の横にあるチェックボックスにチェックを付けて、[Next Review (次のレビュー)] を選択します。
- [Create user (ユーザーの作成)] を選択して、アクセスキー ID とシークレットアクセスキーを指定します。
アプリケーションを作成し実行する
- HTML ページを作成して、以下のコードブロックからコードをコピーします。
<!doctype html>
<html lang="en">
<head>
<title>Amazon Translate</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Latest compiled and minified CSS for Bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom CSS -->
<style>
.topHeader
{
background-color: #6441a4;
padding: 10px;
border-bottom: solid 1px #cacaca;
color: white
}
.panelHeading
{
background-color: #6441a4 !important;
}
.panelBody
{
min-height: 450px; max-height: 450px;overflow-y: scroll;
}
body{
margin-left: 0px;
margin-right: 0px;
height: 100%;
}
</style>
</head>
<body>
<div class="container-fluid">
<!--Top Header-->
<div class="row topHeader">
<div class="col-md-12">
<h4>Amazon Translate - Artificial Intelligence on AWS - Powerful machine learning for all Developers and Data Scientists</h4>
</div>
</div>
<!--Status Label-->
<div class="row">
<div class="col-md-12">
<p class="bg-info">
<div id="connecting-div"></div>
</p>
</div>
</div>
<div class="row" style="padding: 10px;">
<div class="col-md-6">
<div class="form-inline">
<div class="form-group">
<input type="text" id="channel" class="form-control" value="" placeholder="Channel"/>
</div>
<div class="form-group">
<select id="sourceLanguage" class="form-control">
<option value="en">en</option>
<option value="ar">ar</option>
<option value="de" selected="selected">de</option>
<option value="es">es</option>
<option value="fr">fr</option>
<option value="pt">pt</option>
<option value="zh">zh</option>
</select>
</div>
<div class="form-group">
<select id="targetLanguage" class="form-control">
<option value="en" selected="selected">en</option>
<option value="ar">ar</option>
<option value="de">de</option>
<option value="es">es</option>
<option value="fr">fr</option>
<option value="pt">pt</option>
<option value="zh">zh</option>
</select>
</div>
<div class="form-group">
<button type="button" class="form-control" id="btn-go" onclick="connect()">Go</button>
<button type="button" class="form-control" id="btn-stop" onclick="location.href='index.html';">Stop</button>
<span id="status"></span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-inline">
<div class="form-group">
<input type="checkbox" id="cbSpeak" value="Speak"> Speak Live Translation
<input type="text" id="follow" class="form-control" value="" placeholder="follow"/>
</div>
</div>
</div>
</div>
<!--Chat Boxes-->
<div class="row">
<!--Live Chat-->
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading panelHeading">Live Chat</div>
<div id="livechatc" class="panel-body panelBody">
<div class="subscribe" id="livechat"></div>
</div>
</div>
</div>
<!--Live Chat-->
<!--Translated Chat-->
<div class="col-md-6">
<div class="panel panel-primary">
<div class="panel-heading panelHeading">Live Translation</div>
<div id="livetranslationc" class="panel-body panelBody">
<div class="imageDetected" id="livetranslation"></div>
</div>
</div>
</div>
<!--Translated Chat-->
</div>
<!--Send Message-->
<div class="row">
<div class="col-md-11">
<input type="text" id="message" class="form-control"/>
</div>
<div class=" col-md-1">
<button type="button" class="form-control btn btn-default" id="btn-send" onclick="sendMessage()">Send</button>
</div>
</div>
</div>
<!-- Latest compiled and minified JavaScript -->
<!-- jQuery first, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="aws-js-sdk/dist/aws-sdk-all.js"></script>
<script src="http://cdn.tmijs.org/js/1.2.1/tmi.min.js" integrity="sha384-eE0n7sm1W7DOUI2Xh5I4qSpZTe6hupAO0ovLfqEy0yVJtGRBNfssdmjbJhEYm6Bw" crossorigin="anonymous"></script>
<script>
cred = {
twitchUsername: "Twitch user name",
twitchOAuthToken: "Twitch OAuth token",
awsAccessKeyId: "access key",
awsSecretAccessKey: "secret key"
};
AWS.config.region = 'region';
ep = new AWS.Endpoint('endpoint');
AWS.config.credentials = new AWS.Credentials(cred.awsAccessKeyId, cred.awsSecretAccessKey);
window.translator = new AWS.Translate({endpoint: ep, region: AWS.config.region});
/**************************Init and Connect to Chat****************************/
function connect(){
init();
//Twitch Client
var options = {
options: {
debug: false
},
connection: {
cluster: "aws",
reconnect: true
},
identity: {
username: cred.twitchUsername,
password: cred.twitchOAuthToken
},
channels: [con.channel]
};
window.client = tmi.client(options);
window.client.connect();
//Attached Handlers
window.client.on("chat", onChat);
window.client.on("connecting", onConnecting);
window.client.on("connected", onConnected);
//Disable UI Elements
document.getElementById("sourceLanguage").disabled = true;
document.getElementById("targetLanguage").disabled = true;
document.getElementById("channel").disabled = true;
document.getElementById("btn-go").disabled = true;
}
function init(){
//Get UI Controls
var lc = document.getElementById("livechat");
var lt = document.getElementById("livetranslation")
var lcc = document.getElementById("livechatc");
var ltc = document.getElementById("livetranslationc")
var cbspeak = document.getElementById("cbSpeak")
var follow = document.getElementById("follow");
var sendMessage = document.getElementById("message");
//Cache values
con = {
channel: document.getElementById("channel").value,
sourceLanguage: document.getElementById("sourceLanguage").value,
targetLanguage: document.getElementById("targetLanguage").value,
liveChatUI: lc,
liveTranslationUI: lt,
liveChatUIContainer: lcc,
liveTranslationUIContainer: ltc,
cbSpeak: cbspeak,
follow: follow,
sendMessage: sendMessage
}
lc.innerHTML = '';
lt.innerHTML = '';
//Speaker
var voiceId = "Joanna";
if(con.targetLanguage == "en")
voiceId = "Joanna";
else if(con.targetLanguage == "de")
voiceId = "Marlene";
else if(con.targetLanguage == "es")
voiceId = "Conchita";
else if(con.targetLanguage == "fr")
voiceId = "Celine";
else if(con.targetLanguage == "pt")
voiceId = "Ines";
else
voiceId = "Joanna";
window.audioPlayer = AudioPlayer(voiceId);
}
/**************************Init and Connect to Chat****************************/
/**************************Receive and Translate Chat****************************/
function onChat (channel, userstate, message, self) {
// Don't listen to my own messages..
if (self) return;
//Translate
if (message) {
var username = userstate['username'];
var params = {
Text: message,
SourceLanguageCode: con.sourceLanguage,
TargetLanguageCode: con.targetLanguage
};
window.translator.translateText(params, function onIncomingMessageTranslate(err, data) {
if (err) {
console.log("Error calling Translate. " + err.message + err.stack);
}
if (data) {
console.log("M: " + message);
console.log("T: " + data.TranslatedText);
//Print original message in chat UI
con.liveChatUI.innerHTML += '<strong>' + username + '</strong>: ' + message + '<br>';
//Print translation in translation UI
con.liveTranslationUI.innerHTML += '<strong>' + username + '</strong>: ' + data.TranslatedText + '<br>';
//If speak translation in enabled, speak translated message
if(con.cbSpeak.checked){
if(con.follow.value == "" || username == con.follow.value)
audioPlayer.Speak(username + " says " + data.TranslatedText);
}
//Scroll chat and translated UI to bottom to keep focus on latest messages
con.liveChatUIContainer.scrollTop = con.liveChatUIContainer.scrollHeight;
con.liveTranslationUIContainer.scrollTop = con.liveTranslationUIContainer.scrollHeight;
}
});
}
}
/**************************Receive and Translate Chat****************************/
/**************************Client Connecting****************************/
function onConnecting (address, port) {
document.getElementById("status").innerHTML = " [ Connecting...]"
}
function onConnected (address, port) {
document.getElementById("status").innerHTML = " [ Connected ]"
window.audioPlayer.Speak("Connected to channel " + con.channel + ". You should now be getting live chat messages.");
}
/**************************Client Connecting****************************/
/**************************Send Message****************************/
function sendMessage(){
if(con.sendMessage.value){
message = con.sendMessage.value;
var params = {
Text: con.sendMessage.value,
SourceLanguageCode: con.targetLanguage,
TargetLanguageCode: con.sourceLanguage
};
window.translator.translateText(params, function onSendMessageTranslate(err, data) {
if (err) {
console.log("Error calling Translate. " + err.message + err.stack);
}
if (data) {
console.log("M: " + message);
console.log("T: " + data.TranslatedText);
//Send message to chat
window.client.action(con.channel, data.TranslatedText);
//Clear send message UI
con.sendMessage.value = "";
//Print original message in Translated UI
con.liveTranslationUI.innerHTML += '<strong> ME: </strong>: ' + message + '<br>';
//Print translated message in Chat UI
con.liveChatUI.innerHTML += '<strong> ME: </strong>: ' + data.TranslatedText + '<br>';
//Scroll chat and translated UI to bottom to keep focus on latest messages
con.liveChatUIContainer.scrollTop = con.liveChatUIContainer.scrollHeight;
con.liveTranslationUIContainer.scrollTop = con.liveTranslationUIContainer.scrollHeight;
}
});
}
}
/**************************Send Message****************************/
/**************************Audio player****************************/
function AudioPlayer(voiceId) {
var audioPlayer = document.createElement('audio');
audioPlayer.setAttribute("id", "audioPlayer");
document.body.appendChild(audioPlayer);
var isSpeaking = false;
var speaker = {
self: this,
playlist:[],
Speak: function (text) {
//If currently speaking a message, add new message to the playlist
if (isSpeaking) {
this.playlist.push(text);
} else {
speakTextMessage(text).then(speakNextTextMessage)
}
}
}
// Speak text message
function speakTextMessage(text) {
return new Promise(function (resolve, reject) {
isSpeaking = true;
getAudioStream(text).then(playAudioStream).then(resolve);
});
}
// Speak next message in the list
function speakNextTextMessage() {
var pl = speaker.playlist;
if (pl.length > 0) {
var txt = pl[0];
pl.splice(0, 1);
speakTextMessage(txt).then(speakNextTextMessage);
}
}
// Get synthesized speech from Amazon polly
function getAudioStream(textMessage) {
return new Promise(function (resolve, reject) {
var polly = new AWS.Polly();
var params = {
OutputFormat: 'mp3',
Text: textMessage,
VoiceId: voiceId
}
polly.synthesizeSpeech(params, function (err, data) {
if (err)
reject(err);
else
resolve(data.AudioStream);
});
});
}
// Play audio stream
function playAudioStream(audioStream) {
return new Promise(function (resolve, reject) {
var uInt8Array = new Uint8Array(audioStream);
var arrayBuffer = uInt8Array.buffer;
var blob = new Blob([arrayBuffer]);
var url = URL.createObjectURL(blob);
audioPlayer.src = url;
audioPlayer.addEventListener("ended", function () {
isSpeaking = false;
resolve();
});
audioPlayer.play();
});
}
return speaker;
}
/**************************Audio player****************************/
</script>
</body>
</html>
- 以下のコードスニペットを、あなたの Twitch ユーザー名、OAuth トークン、AWS アクセスキー ID、および AWS シークレットアクセスキーで更新します。
cred = {
twitchUsername: "Twitch user name",
twitchOAuthToken: "Twitch OAuth token",
awsAccessKeyId: "access key",
awsSecretAccessKey: "secret key"
};
- ブラウザーでウェブページを開きます。
- Twitch チャネルの名前を入力します。
- 入力言語と出力言語を選択して、[Go (移動)] をクリックします。
画面にリアルタイムメッセージとその翻訳が表示されます。Twitch チャットとこの例のアプリケーションを並べて見ていると、メッセージは、Twitch チャット内に現れるのと同時に、チャット内にも表示されるので、Amazon Translate が高性能であり、レイテンシが非常に低いことがわかります。
また、re:Invent 2017 における Twitch チャットのリアルタイム翻訳のデモを紹介しているビデオを見ることもできます。
メッセージの受信と翻訳
下のコードブロックは、リアルタイム着信メッセージを受信したら、AWS SDK を使用して Amazon Translate を呼び出して、翻訳されたメッセージを UI 内に表示する方法を示しています。ご覧の通り、コードの大半は入力と UI の処理に関するものであり、翻訳に関わるコードはほんの数行です。
/**************************Receive and Translate Chat****************************/
function onChat (channel, userstate, message, self) {
// Don't listen to my own messages..
if (self) return;
//Translate
if (message) {
var username = userstate['username'];
var params = {
Text: message,
SourceLanguageCode: con.sourceLanguage,
TargetLanguageCode: con.targetLanguage
};
window.translator.translateText(params, function onIncomingMessageTranslate(err, data) {
if (err) {
console.log("Error calling Translate. " + err.message + err.stack);
}
if (data) {
console.log("M: " + message);
console.log("T: " + data.TranslatedText);
//Print original message in chat UI
con.liveChatUI.innerHTML += '<strong>' + username + '</strong>: ' + message + '<br>';
//Print translation in translation UI
con.liveTranslationUI.innerHTML += '<strong>' + username + '</strong>: ' + data.TranslatedText + '<br>';
//If speak translation in enabled, speak translated message
if(con.cbSpeak.checked){
if(con.follow.value == "" || username == con.follow.value)
audioPlayer.Speak(username + " says " + data.TranslatedText);
}
//Scroll chat and translated UI to bottom to keep focus on latest messages
con.liveChatUIContainer.scrollTop = con.liveChatUIContainer.scrollHeight;
con.liveTranslationUIContainer.scrollTop = con.liveTranslationUIContainer.scrollHeight;
}
});
}
}
/**************************Receive and Translate Chat****************************/
翻訳されたメッセージの送信
下のコードブロックは、発信メッセージを翻訳して、それを Twitch チャネルに送信する方法を示しています。
/**************************Send Message****************************/
function sendMessage(){
if(con.sendMessage.value){
message = con.sendMessage.value;
var params = {
Text: con.sendMessage.value,
SourceLanguageCode: con.targetLanguage,
TargetLanguageCode: con.sourceLanguage
};
window.translator.translateText(params, function onSendMessageTranslate(err, data) {
if (err) {
console.log("Error calling Translate. " + err.message + err.stack);
}
if (data) {
console.log("M: " + message);
console.log("T: " + data.TranslatedText);
//Send message to chat
window.client.action(con.channel, data.TranslatedText);
//Clear send message UI
con.sendMessage.value = "";
//Print original message in Translated UI
con.liveTranslationUI.innerHTML += '<strong> ME: </strong>: ' + message + '<br>';
//Print translated message in Chat UI
con.liveChatUI.innerHTML += '<strong> ME: </strong>: ' + data.TranslatedText + '<br>';
//Scroll chat and translated UI to bottom to keep focus on latest messages
con.liveChatUIContainer.scrollTop = con.liveChatUIContainer.scrollHeight;
con.liveTranslationUIContainer.scrollTop = con.liveTranslationUIContainer.scrollHeight;
}
});
}
}
/**************************Send Message****************************/
まとめ
このポストでは、チャットメッセージのリアルタイム翻訳のために Amazon Translate を使用する方法について学習しました。このブログでは、例として Twitch チャネルを使用しましたが、これは、他のチャットプラットフォーム、カスタマーサービスの対話、メッセージボードなど、他のリアルタイムストリーミングテキストのための出発点として使用することができます。
今回のブログ投稿者について
Kashif Imran は、Amazon Web Services のソリューションズアーキテクトです。 彼は最大の戦略的 AWS カスタマーの 1 つと連携して、テクニカルガイダンスと設計アドバイスを提供しています。彼の専門知識は、アプリケーションアーキテクチャ、サーバーレス、コンテナー、NoSQL、および機械学習など、広範囲にわたっています。