Azure Media Indexerが日本語対応したので試してみた

# はじめに
昨日まで、他の記事を書こうと思っていたのですが、今日、Azure Media Indexerが日本語対応した！という記事を教えてもらって急遽やってみることにしました。

https://azure.microsoft.com/ja-jp/blog/japanese-language-support-in-azure-media-indexer-2-preview/

# Azure Media Indexer
Azure Media Indexerはなにものかというと、動画の中の言葉を拾って、テキスト化してくれる代物です。
実は、今年の4月ころにMicrosoftのAzure Media Services担当の人と話をして、「6月頃に日本語対応するぜ！」とかいう返事をもらったので今か今かと待っていたのです。(今はもう師走。

# やってみた
- https://docs.microsoft.com/ja-jp/azure/media-services/media-services-dotnet-get-started
- https://docs.microsoft.com/ja-jp/azure/media-services/media-services-index-content

この2つのページを参考にしててこてこ書いてみました。
毎回アップロードするのは非効率なので、Assetだけは最初にアップロードしておきました。

で、ページに書かれた通りに指定して、言語の指定だけを日本語にしてみました。

言語に"JaJp"を指定して、ひたすら実行するも、configurationのフォーマットがおかしい。的なエラーが出てうまく動きません。
試しに、言語を"English"にしてみると、普通に動きます。

何かがおかしい…と思って↓のページをもう一度読み返してみると…
- https://azure.microsoft.com/ja-jp/blog/japanese-language-support-in-azure-media-indexer-2-preview/

## Azure Media Indexer 2: Japanese support, punctuation improvements, no more time limit

Azure Media Indexer **2** って書いてある。 **2** ってなんじゃい、と思ったら、MediaProcessorにそれらしいものが2つあることを発見。
- Azure Media Indexer
- Azure Media Indexer 2 Preview
Azure Media Indexerのほうをずっと試していたので、Azure Media Indexer **2 Preview**でやってみたらできた。

## 結果
適当な動画がなかったので自分で会社の入り口でしゃべってみました。
~~私の活舌がとても良いので、~~結構な精度で解析できている気がします。
やはり固有名詞、会社の名前とか、その辺は難しいのかなと。
日常会話レベルであれば、8割方合ってるという感覚です。

映画とか言葉以外にも音があったり、聞き取りにくかったりするのは難しいかもしれませんが、講演とか誰か一人が静かな環境で喋っているものであれば十分に利用用途はあるかと思います。

## インプット
~~qiitaは動画直接置けないのがめんどくさいな~~
喋った言葉をテキストで記載しておきます。

```
皆さんこんにちは.

ネクストスケープ配信事業部のqiita記事を書くために動画を取っています
今日はメディアインデクサーのテストということで、簡単な動画をちょっと取っていますが、
ちなみにここはうちの会社の入り口です.

なんかいろいろクリスタルの盾とかあります.

はいでは、えーと、この動画をMedia Indexerにかけてみたいと思います.
```

## アウトプット
出来上がったファイルは以下の3つ。
旧タイプのMedia Indexerはキーワードを抜き出したファイルも生成できたのですが、**2**のほうは現時点では文章の形の者しかoutputできないようです。

### nextscape_aud_SpReco.smi
```xml
<SAMI>
<HEAD>
  <STYLE TYPE="text/css">
    <!--
    P {}
    JAJP {Name:'English Captions'; lang: {ja-jp}; SAMI_Type: CC;}
    -->
  </STYLE>
</HEAD>
<BODY>
<SYNC Start="1">
<P Class="ENUSCC">皆さんこんにちは.</P>
</SYNC>
<SYNC Start="3">
<P Class="ENUSCC">一つ経験配信事業の.</P>
</SYNC>
<SYNC Start="5">
<P Class="ENUSCC">ニキータ記事を書くためにね動画を取っています今日はメディアインデクサーの部屋テストということで.</P>
</SYNC>
<SYNC Start="15">
<P Class="ENUSCC">簡単なものがをちょっと取っていますが.</P>
</SYNC>
<SYNC Start="18">
<P Class="ENUSCC">ちなみにここはうちの会社の入り口です.</P>
</SYNC>
<SYNC Start="21">
<P Class="ENUSCC">なんかいろいろクリスタルの盾とかあります.</P>
</SYNC>
<SYNC Start="25">
<P Class="ENUSCC">はいでは2人をこの動画のメディアインデクサに賭けてみたいと思います.</P>
</SYNC>
</BODY>
</SAMI>
```

### nextscape_aud_SpReco.ttml
```xml
<?xml version="1.0" encoding="utf-8"?>
<tt xml:lang="en-us" xmlns="http://www.w3.org/ns/ttml" xmlns:tts="http://www.w3.org/ns/ttml#styling" xmlns:ttm="http://www.w3.org/ns/ttml#metadata">
  <head>
    <metadata>
      <ttm:title>nextscape_aud_SpReco</ttm:title>
      <ttm:copyright>Copyright (c) 2013 Microsoft Corporation.  All rights reserved.</ttm:copyright>
    </metadata>
    <styling>
      <style xml:id="Style1" tts:fontFamily="proportionalSansSerif" tts:fontSize="0.8c" tts:textAlign="center" tts:color="white" />
    </styling>
    <layout>
      <region style="Style1" 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" />
    </layout>
  </head>
  <body region="CaptionArea">
    <div>
      <p begin="00:00:00.710" end="00:00:01.900">皆さんこんにちは.</p>
      <p begin="00:00:02.690" end="00:00:05.420">一つ経験配信事業の.</p>
      <p begin="00:00:05.420" end="00:00:14.080">ニキータ記事を書くためにね動画を取っています今日はメディアインデクサーの部屋テストということで.</p>
      <p begin="00:00:14.550" end="00:00:17.200">簡単なものがをちょっと取っていますが.</p>
      <p begin="00:00:17.790" end="00:00:20.900">ちなみにここはうちの会社の入り口です.</p>
      <p begin="00:00:21.300" end="00:00:24.200">なんかいろいろクリスタルの盾とかあります.</p>
      <p begin="00:00:25.150" end="00:00:31.240">はいでは2人をこの動画のメディアインデクサに賭けてみたいと思います.</p>

    </div>
  </body>
</tt>
```

### nextscape_aud_SpReco.vtt
```
WEBVTT

00:00:00.710 --> 00:00:01.900
皆さんこんにちは.

00:00:02.690 --> 00:00:05.420
一つ経験配信事業の.

00:00:05.420 --> 00:00:14.080
ニキータ記事を書くためにね動画を取っています今日はメディアインデクサーの部屋テストということで.

00:00:14.550 --> 00:00:17.200
簡単なものがをちょっと取っていますが.

00:00:17.790 --> 00:00:20.900
ちなみにここはうちの会社の入り口です.

00:00:21.300 --> 00:00:24.200
なんかいろいろクリスタルの盾とかあります.

00:00:25.150 --> 00:00:31.240
はいでは2人をこの動画のメディアインデクサに賭けてみたいと思います.
```

## ソースコードとか

Azure Media Indexer **2**はconfigurationをjsonで指定するらしいので、jsonを作る。

```json
{
  "version": "1.0",
  "Features": [
    {
      "Options": {
        "Formats": [ "WebVtt", "ttml", "Sami" ],
        "Language": "JaJp",
        "Type": "RecoOptions"
      },
      "Type": "SpReco"
    }
  ]
}
```

ソースコードも**Azure Media Indexer 2 Preview**を使うように変更。

```c#
        static bool RunIndexingJob(string inputAssetName, string outputFolder, string configurationFile)
        {
            // Find already uploaded asset.
            IAsset asset = _context.Assets.Where(
                (d => (d.Name == inputAssetName))).Single();

            // Declare a new job.
            IJob job = _context.Jobs.Create("My Indexing Job");

            // Get a reference to the Azure Media Indexer.
            //string MediaProcessorName = "Azure Media Indexer";

            // Japanese support processor is "Azure Media Indexer 2 Preview".
            string MediaProcessorName = "Azure Media Indexer 2 Preview";

            // Get media processor
            IMediaProcessor processor = _context.MediaProcessors.Where(
                p => (p.Name == MediaProcessorName)).Single();

            // Read configuration from file
            string configuration = File.ReadAllText(configurationFile);

            // Create a task with the encoding details, using a string preset.
            string InputTaskName = string.Format("My Indexing Task {0:yyyyMMddHHmmss}", DateTime.Now);
            ITask task = job.Tasks.AddNew(InputTaskName,
                processor,
                configuration,
                TaskOptions.None);

            // Specify the input asset to be indexed.
            task.InputAssets.Add(asset);

            // Add an output asset to contain the results of the job.
            string OutputAssetName = string.Format("My Indexing Output Asset {0:yyyyMMddHHmmss}", DateTime.Now);
            task.OutputAssets.AddNew(OutputAssetName, AssetCreationOptions.None);

            // Log status
            job.StateChanged += (j, e) => {
                Console.Out.WriteLine("{0}, {1}", DateTime.Now, e.CurrentState);
            };

            // Launch the job.
            job.Submit();

            // Check job execution and wait for job to finish.
            Task progressJobTask = job.GetExecutionProgressTask(CancellationToken.None);
            progressJobTask.Wait();

            // If job state is Error, the event handling
            // method for job progress should log errors.  Here we check
            // for error state and exit if needed.
            if (job.State == JobState.Error)
            {
                Console.WriteLine("Exiting method due to job error.");
                return false;
            }

            // Download the job outputs
            IAsset DownloadAsset = _context.Assets.Where(d => d.Name == OutputAssetName).First();
            DownloadAsset.AssetFiles.ToList().ForEach(d => d.Download(Path.Combine(outputFolder, d.Name)));

            return true;
        }
```