IT関連の最新情報は、どうしても英語のものが多いですよね。英語に行くと、有償セッションのビデオ動画があったりするのですが、日本語のものはどうしても人が翻訳して、映像のシーンを指定して字幕を付けるので、どうしても時間とお金がかかります。結果としては、多くの情報は日本語になりません。
また、日本語と英語、くらいの数の翻訳であればまだいいのですが、フランス語、ドイツ語など増やそうと思ったら大変です。
Azure Media Servicesがそのお手伝いをします。
System.Configurationが初期設定では参照されていませんので、[参照設定] を右クリックして出てくるポップアップメニューから[参照の追加]を押して、”System.Configuration” を追加してください。
private static string TranslatorClientSecret = ConfigurationManager.AppSettings[“TranslatorClientSecret”];
private static string uploadFile = ConfigurationManager.AppSettings[“uploadfile”];
ここでIndexingのジョブを実行します。といっても、Media Processorとして”Azure Media Indexer”の指定をして、先ほどの設定ファイルの内容を文字列として渡すだけです。
// 2. ジョブ実行.
元ファイルがmp4ファイルの場合は、そのままHTML5ビデオにプログレッシブダウンロード方式にて配信ができます。ここでは、アップロードしたファイルをそのまま配信に使います。
<ttm:copyright>Copyright (c) 2013 Microsoft Corporation. All rights reserved.
xml:id=”CaptionArea” tts:origin=”0c 12.6c” tts:extent=”32c 2.4c” tts:backgroundColor=”rgba(0,0,0,160)” tts:displayAlign=”center” tts:padding=”0.3c 0.5c” />
…welcome executive vice …zygote guthrie
Good morning everyone and welcome …to …
We live in a mobile first cloud first-world yesterday we
————————————————————————-
- タグに、抽出結果の文字列が入っています。
- それぞれ”begin“と”end“があります。再生開始してからのその文字が話されていた時間、という意味です。
Microsoft Translatorの呼び出し
さて、この先で作成されたTTMLの英文を翻訳するわけですが、その為にMicrosoft Translatorを呼び出す処理を書いていきます。
[BingTranslatorSvc.cs]
ここでは全文を載せます。ちょっと長いですが…
-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.Web;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.Threading;
namespace AzureMediaIndexer
{
// http://blogs.msdn.com/b/translation/p/gettingstarted1.aspx
class BingTranalatorSvc
{
public string from = "en";
public string to = "ja";
// Azure MarketPlace と紐づけ
//Get Client Id and Client Secret from https://datamarket.azure.com/developer/applications/
public string clientID = "";
public string clientSecret = "";
public string Translate(string text)
{
string result = "";
// BingTranslator アクセス用のアクセストークンをAzure DataMarketから取得
var accessToken = new AdmAuthentication(clientID, clientSecret);
string uri = "http://api.microsofttranslator.com/v2/Http.svc/Translate?text=" + System.Web.HttpUtility.UrlEncode(text) + "&from=" + from + "&to=" + to;
string authToken = "Bearer " + accessToken.GetAccessToken().access_token;
// Microsoft Translator呼び出し
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.Headers.Add("Authorization", authToken);
// 結果取得
WebResponse response = null;
response = httpWebRequest.GetResponse();
using (Stream stream = response.GetResponseStream())
{
System.Runtime.Serialization.DataContractSerializer dcs = new System.Runtime.Serialization.DataContractSerializer(Type.GetType("System.String"));
result = (string)dcs.ReadObject(stream);
}
return result;
}
// Refer obtaining AccessToken (http://msdn.microsoft.com/en-us/library/hh454950.aspx)
[DataContract]
public class AdmAccessToken
{
[DataMember]
public string access_token { get; set; }
[DataMember]
public string token_type { get; set; }
[DataMember]
public string expires_in { get; set; }
[DataMember]
public string scope { get; set; }
}
public class AdmAuthentication
{
public static readonly string DatamarketAccessUri = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13";
private string clientId;
private string clientSecret;
private string request;
private AdmAccessToken token;
private Timer accessTokenRenewer;
//Access token expires every 10 minutes. Renew it every 9 minutes only.
private const int RefreshTokenDuration = 9;
public AdmAuthentication(string clientId, string clientSecret)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
//If clientid or client secret has special characters, encode before sending request
this.request = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=http://api.microsofttranslator.com", HttpUtility.UrlEncode(clientId), HttpUtility.UrlEncode(clientSecret));
this.token = HttpPost(DatamarketAccessUri, this.request);
//renew the token every specfied minutes
accessTokenRenewer = new Timer(new TimerCallback(OnTokenExpiredCallback), this, TimeSpan.FromMinutes(RefreshTokenDuration), TimeSpan.FromMilliseconds(-1));
}
public AdmAccessToken GetAccessToken()
{
return this.token;
}
private void RenewAccessToken()
{
AdmAccessToken newAccessToken = HttpPost(DatamarketAccessUri, this.request);
//swap the new token with old one
//Note: the swap is thread unsafe
this.token = newAccessToken;
}
private void OnTokenExpiredCallback(object stateInfo)
{
try
{
RenewAccessToken();
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed renewing access token. Details: {0}", ex.Message));
}
finally
{
try
{
accessTokenRenewer.Change(TimeSpan.FromMinutes(RefreshTokenDuration), TimeSpan.FromMilliseconds(-1));
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed to reschedule the timer to renew access token. Details: {0}", ex.Message));
}
}
}
private AdmAccessToken HttpPost(string DatamarketAccessUri, string requestDetails)
{
//Prepare OAuth request
WebRequest webRequest = WebRequest.Create(DatamarketAccessUri);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
byte[] bytes = Encoding.ASCII.GetBytes(requestDetails);
webRequest.ContentLength = bytes.Length;
using (Stream outputStream = webRequest.GetRequestStream())
{
outputStream.Write(bytes, 0, bytes.Length);
}
using (WebResponse webResponse = webRequest.GetResponse())
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AdmAccessToken));
//Get deserialized object from JSON stream
AdmAccessToken token = (AdmAccessToken)serializer.ReadObject(webResponse.GetResponseStream());
return token;
}
}
}
}
}
-----------------------------------------------------------------------------
ポイントです。
ttmlから英文を切り出して、Microsoft Translatorを呼び出す
せっかくですから一度に複数言語に翻訳してみます。ここでは、日本語とクリンゴン語ですね。
——————————————————————————-
// Translator Language Codes:
// http://msdn.microsoft.com/en-us/library/hh456380.aspx
var langs = new string[]{
“ja”, //日本語
“tlh”}; //クリンゴン語
BingTranalatorSvc translator = new BingTranalatorSvc();
translator.clientID = TranslatorClientID;
translator.clientSecret = TranslatorClientSecret;
int i = 0;
——————————————————————————-
- Microsoft Azure Marketplace で作成した、Client IDとClient Secretを設定します。
注: クリンゴン語は地球上には存在していません。Star Trekの中のとある種族の話している言語です。Microsoft Translatorは、クリンゴン語への機械翻訳をしてくれます!
Microsoft Translatorによる機械翻訳呼び出し
——————————————————————————-
foreach (var lang in langs)
{
var TTMLjp = XDocument.Load(TTMLFile);
XNamespace ttmlns = “http://www.w3.org/ns/ttml”; //LINQ to XMLでクエリするためには、xmlnsの指定は必須
var transTargets = from p in TTMLjp.Descendants(ttmlns + “p”)
select p;
translator.to = lang;
foreach (var item in transTargets)
{
try
{
Console.WriteLine(” 機械翻訳: {0}:{1}/{2}”,
lang,
i,
item.Value);
item.SetValue(translator.Translate(item.Value));
i++;
}
catch (WebException we)
{
ProcessWebException(we);
break;
}
}
TTMLjp.Save(TTMLFile.Replace(“.ttml”,
string.Format(“.{0}.ttml”,lang)));
Console.WriteLine(” 機械翻訳 完了”);
Console.WriteLine(” 機械翻訳数: {0}:{1}/{2}”,
lang,
i,
transTargets.Count());
}
————————————————————-
これで完了です。
以下、メイン部分のソースコード全文です。
[Program.cs]
このサンプルでは、それぞれの処理の実行時間を計測しています。
————————————————————————————————-
using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Xml;
using System.Xml.Linq;
using Microsoft.WindowsAzure.MediaServices.Client;
namespace AzureMediaIndexer
{
class Program
{
private static string TranslatorClientID = ConfigurationManager.AppSettings[“TranslatorClientID”];
private static string TranslatorClientSecret = ConfigurationManager.AppSettings[“TranslatorClientSecret”];
private static string uploadFile = ConfigurationManager.AppSettings[“uploadfile”];
static void Main(string[] args)
{
// 処理時間の計測
var totalSw = new Stopwatch();
var sw = new Stopwatch();
totalSw.Start();
var context = new CloudMediaContext(
ConfigurationManager.AppSettings[“accountName”],
ConfigurationManager.AppSettings[“accountKey”]
);
Console.WriteLine(“*** 1. ファイルアップロード ***”);
sw.Start();
var asset = context.Assets.CreateFromFile(
uploadFile,
AssetCreationOptions.None,
(a, p) =>
{
Console.WriteLine(” 経過 {0}%”, p.Progress);
});
sw.Stop();
Console.WriteLine(” アップロード処理完了”);
Console.WriteLine(” アップロード処理時間: {0}”, sw.Elapsed.ToString());
Console.WriteLine(“*** 2. Indexing 実行 ***”);
// 2.1. ジョブ作成
var configuration = File.ReadAllText(“indexing.config”);
var job = context.Jobs.CreateWithSingleTask(
“Azure Media Indexer”,
configuration,
asset,
asset.Name + “-Indexed”,
AssetCreationOptions.None);
sw.Reset();
sw.Start();
// 2.2. ジョブ実行.
job.Submit();
job = job.StartExecutionProgressTask(
j =>
{
Console.WriteLine(” 状態: {0}”, j.State);
Console.WriteLine(” 経過: {0:0.##}%”, j.GetOverallProgress());
},
System.Threading.CancellationToken.None).Result;
sw.Stop();
Console.WriteLine(” Indexing 完了”);
Console.WriteLine(” Indexing 時間: {0}”, sw.Elapsed.ToString());
var outputAsset = job.OutputMediaAssets.FirstOrDefault();
Console.WriteLine(“***** 3. 配信ポイント作成 *****”);
// Progressive Download
context.Locators.CreateLocator(
LocatorType.Sas,
asset,
context.AccessPolicies.Create(
“Streaming Access Policy”,
TimeSpan.FromDays(7),
AccessPermissions.Read)
);
WriteToFile(String.Format(“{0}_SASURL.txt”,
asset.Name),
asset.AssetFiles.FirstOrDefault().GetSasUri().ToString());
Console.WriteLine(“*** 4. ファイルダウンロード ***”);
sw.Reset();
sw.Start();
var TTMLFile = “”;
foreach (IAssetFile file in outputAsset.AssetFiles)
{
Console.WriteLine(” ファイルダウンロード中: {0}”, file.Name);
file.Download(Environment.GetEnvironmentVariable(“USERPROFILE”) +
@”\Desktop\” +
file.Name);
// ttml は翻訳対象
if (file.Name.EndsWith(“.ttml”))
{
TTMLFile = Environment.GetEnvironmentVariable(“USERPROFILE”)
+ @”\Desktop\”
+ file.Name;
}
}
Console.WriteLine(“*** 5. Bing Translator での ttml 機械翻訳 ***”);
sw.Reset();
sw.Start();
// Translator Language Codes:
// http://msdn.microsoft.com/en-us/library/hh456380.aspx
var langs = new string[]{
“ja”, //日本語
“tlh”}; //クリンゴン語
BingTranalatorSvc translator = new BingTranalatorSvc();
translator.clientID = TranslatorClientID;
translator.clientSecret = TranslatorClientSecret;
int i = 0;
foreach (var lang in langs)
{
var TTMLjp = XDocument.Load(TTMLFile);
XNamespace ttmlns = “http://www.w3.org/ns/ttml”; //LINQ to XMLでクエリするためには、xmlnsの指定は必須
var transTargets = from p in TTMLjp.Descendants(ttmlns + “p”)
select p;
translator.to = lang;
foreach (var item in transTargets)
{
try
{
Console.WriteLine(” 機械翻訳: {0}:{1}/{2}”,
lang,
i,
item.Value);
item.SetValue(translator.Translate(item.Value));
i++;
}
catch (WebException we)
{
ProcessWebException(we);
break;
}
}
TTMLjp.Save(TTMLFile.Replace(“.ttml”,
string.Format(“.{0}.ttml”,lang)));
Console.WriteLine(” 機械翻訳 完了”);
Console.WriteLine(” 機械翻訳数: {0}:{1}/{2}”,
lang,
i,
transTargets.Count());
}
Console.WriteLine(” 機械翻訳時間: {0}”, sw.Elapsed.ToString());
Console.WriteLine();
Console.WriteLine(“全ての処理が終了しました。Indexing 結果がデスクトップに出力されていますので、ご確認ください。”);
Console.WriteLine(“総処理時間: {0}”, totalSw.Elapsed.ToString());
Console.WriteLine(“何かキーを押してください。”);
Console.ReadLine();
}
///
/// Utility: 文字列のファイル出力
///
/// outFileName”>
/// fileContent”>
static void WriteToFile(string outFileName, string fileContent)
{
System.IO.StreamWriter sr = System.IO.File.CreateText(
Environment.GetEnvironmentVariable(“USERPROFILE”) +
@”\Desktop\” +
outFileName);
sr.Write(fileContent);
sr.Flush();
sr.Close();
}
private static void ProcessWebException(WebException e)
{
Console.WriteLine(“>>> Error: {0}”, e.ToString());
// Obtain detailed error information
string strResponse = string.Empty;
using (HttpWebResponse response = (HttpWebResponse)e.Response)
{
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream, System.Text.Encoding.ASCII))
{
strResponse = sr.ReadToEnd();
}
}
}
Console.WriteLine(“>>> Http status code={0}, error message={1}”, e.Status, strResponse);
}
}
}
————————————————————————————————-
プログラム実行
早速Visual Studio から実行してみましょう。
結構時間がかかりますね。
実行結果の確認
デスクトップに幾つかファイルが出力されています。
このうち、”.ttml”を使用します。
-
HTMLページ作成
以下の様になります。
[index.html]
———————————————————
<!––>DOCTYPE html>
Azure Media Indexer での字幕作成
Segoe UI’, Tahoma, Geneva, Verdana, sans-serif; font-size: x-large;”>
HTML5 Video + Track
字幕: Azure Media Indexer と Bing Translator で自動生成
ビデオプレイヤーの[CC]ボタンを押して、字幕の切り替えを試してください
</body>
</html>
————————————————————
- Videoのsrcには、mp4ファイル向けに作成したSAS URLを指定しています。
-
trackタグは:
- idは必須です
- srcに.ttmlファイルを指定しています。ここでは、一緒にWeb Serverにアップロードして使います
- label の部分がプレイヤーの字幕の表示文字列になります。
- defaultと設定してあるものが、初期値で表示されます。
-
Azure WebSiteの作成
Azure WebSiteを作成して、ファイルをアップロードしましょう。
Azureの管理画面で、[新規] – [Webサイト] – [簡易作成] から、以下を参考にURLとデータセンターの場所を選択します。
10秒程度で本番サイトが使えるようになります。
作成した、WebSiteを選択して、[構成] のタブに移動します。
少し下にスクロールすると[VISUAL STUDIO ONLINEでの編集]という項目があります。これを「オン」にして、画面したの[保存]ボタンを押してください。
設定が完了したら、[ダッシュボード]に戻ります。画面右側の[概要]の中に[Visual Studio Onlineでの編集]という項目が表示されます。これをクリックします。
ブラウザーの中でコーディングが出来る、Visual Studio Online “Monaco”という環境が立ち上がってきます。
ほんの少し修正するだけなのに、PC上でソースコードを直して、アップロードしなおす、という事をする必要がありません。直接Azure WebSite上にあるHTML, PHPなどのソースを編集して、保存できるのです。
それに加えて、ドラックアンドドロップでファイルをアップロードする機能まであります。
ドラックアンドドロップします。
ファイルがアップロードされているのが確認できます。
HTMLの場合は、タグとして認識されていますので、エディター上で色分けがされて大変読みやすくなっています。
手元でファイル作成して、アップロード。という方法をとりましたが、いきなり”Monaco”に入って、”Monaco”の中でコーディングする、という方法でも構いません。
-
MIMEの設定
Azure WebSiteでは、ttmlのMIME設定がされていませんので、そのままでは動作しません。
“text/xml”のmime設定を行います。
Azure WebSiteの実体はIISですので、IISのweb.configファイルを作成し、MIME設定を行います。
[web.config]
—————————————————————————————————-
<?xml version=”1.0″ encoding=”utf-8″?>
<!– For HTML5 configuration details and recommendations, see https://github.com/h5bp/server-configs/wiki/web.config –>
<configuration>
<system.webServer>
<staticContent>
<remove fileExtension=”.ttml” />
<mimeMap fileExtension=”.ttml” mimeType=”text/xml” />
</staticContent>
</system.webServer>
</configuration>
Azure WebSiteは最終的にこのようになりました。
画面左側の再生ボタンを押して、本番サイトをみてみます。
実行結果はこのような感じです。
クリンゴン語で表示されていますね。
[CC] のところで設定した字幕が選べるようになっています。
-
まとめ
Azure Media Indexerを使うと、そのままHTML5 Videoで作成できるTTMLのファイルが作成できる事を見てきました。
そして、Microsoft Translatorで、かなり意味の分かる各国語に機械翻訳できるところも見てきました。
話している場所は、シーンになります。検索結果、ビデオファイルのURLがわかるのではなく、ビデオの中のどのシーンに飛ばせばいいのかまでわかります。
Azure Media Indexerはその他にも面白いファイルを作成してくれます。
是非、いろんな可能性にチャレンジしてください!
ご参考:
Azure Media Indexer料金:
http://azure.microsoft.com/ja-jp/pricing/details/media-services/#encoding
Introducing Azure Media Indexer:
http://azure.microsoft.com/blog/2014/09/10/introducing-azure-media-indexer/
MSDN: Indexing Media Files with Azure Media Indexer:
http://msdn.microsoft.com/en-us/library/dn783455.aspx
What Can You Do with Azure Media Indexer? [William Zhang BLOG]:
http://blogs.msdn.com/b/playready4/archive/2014/09/17/what-can-you-do-with-azure-media-indexer.aspx