Elemental Inferenceを使って横動画mp4をAI処理で縦動画に自動変換してみた

Elemental_Inference_02

AWS Elemental Inference

AWS Elemental Inferenceというサービス。2026年2月下旬に提供開始されたサービスで、一言でいうとライブ映像をリアルタイムで縦型に変換してくれる機能です。

サービスの概要については以前の記事で簡単に紹介しているので、あわせてご覧ください。
AWS Elemental Inferenceをローンチ当日に早速使ってみた

スポーツ中継などの16:9の横型映像をSNS向けの縦型動画として投稿したいとき、これまでは編集ソフトで切り出しや調整をしてから投稿する必要がありましたが、AWS Elemental Inferenceを使えば編集ソフトなしでリアルタイムに縦型変換が可能です。

ただし現在、縦型動画への変換機能はライブ映像のリアルタイム変換のみに対応しています。

必要な箇所のみを変換したい

サッカーやバスケットボールのハイライト自動切り出し機能も併用するなら、スポーツ中継全体を丸ごと解析するのも有用だと思います。しかし、単に縦型にクロップするだけの目的で全時間を解析するのは、コスト面でもったいないと感じました。

具体的な料金を見てみると、縦型クロップとハイライト自動切り出しの2機能のうち、片方だけ使う場合は1時間あたり$9.00、両方使う場合は1時間あたり$13.80とのこと。
つまり、プロ野球中継を想定すると約4時間で$40ほどになります。そこにMediaLiveなどのコストも加算されます。

1フレームずつ縦型にクロップしてくれる点は魅力的ですが、必要な箇所をあらかじめ想定できるコンテンツなら、不要な部分までクロップするのはもったいない…。

そこで、素材の切り出しまでを先に行い、縦型クロップの部分だけElemental Inferenceを使えばコスパが良いのでは?と考えました。
いつかMediaConvertにも実装されることを夢見つつ、今回はMediaLive経由でアップロードしたmp4動画を縦型動画にクロップする仕組みを作ってみます。

アーキテクチャの構想

今回はStep Functionsでデモを作ります。
これをベースに、Webアプリでアップロードして処理させ、完成したものをダウンロードできる形にできればと考えています。

なお、MediaConvertではキューに積まれたジョブが並列処理されますが、MediaLiveには同時実行チャネル数に上限があります。そのため1つずつ処理することを前提とし、あらかじめ作成したチャネル1つを使い回す方針で進めます。

下ごしらえ

まず今回使用するS3バケットを作成し、以下のフォルダ構成にしました。

  • input: mp4動画のアップロード場所
  • archive: MediaLive (Elemental Inference) で書き出されたファイルの格納場所
  • output: 縦型変換済みのmp4動画の格納場所

次にMediaLiveの入力を作成します。
入力タイプはMP4、入力クラスはSINGLE_INPUTとし、入力ソースには先ほど作ったS3バケットのS3 URIを指定します。

続いてMediaLiveのチャネルを作成します。
チャネルクラスはSINGLE_PIPELINEとし、1つのエンコーダーパイプラインのみを使用します。

入力アタッチメントには先ほど作成した入力を選択し、「確認」ボタンを押してください。

出力グループは「アーカイブ」を選択します。

送信先は先ほど作ったS3バケットのarchiveフォルダを指定します。
名前修飾子は_1では寂しいので、archiveなどにしてみます。

ここでようやく、ずっと画面上部に表示されていながら無視し続けていたAWS Elemental Inferenceの出番です。
状態をEnabledにして、スマートクロッピングをONにします。

すると出力の設定欄に「ストリーム設定」が表示されるので、幅(1080)と高さ(1920)を設定し、「チャネルの作成」を選択します。

ここまでで下ごしらえは完了です。
チャネルを手動で開始・終了すると、archiveフォルダにarchive.000000.tsというファイルが生成されます。

自動化に向けて

手動でも縦型動画への変換は可能ですが、実運用を考えると以下の課題があります。

  • 手動でチャネルを開始する必要がある
  • 素材の尺が終わったタイミングで手動で停止する必要がある
  • 尺ぴったりで止めることはできないため、時間尺に合わせた切り出しが必要
  • TSファイルは再生できる環境が限られるため、mp4への変換が必要

アルゴリズム

上の課題を踏まえ、以下のアルゴリズムで実装することにしました。

  1. アップロードされたmp4素材の時間尺を取得する
  2. MediaLiveの特定チャネルの入力ソースを今回の素材に変更する
  3. チャネルを開始する
  4. チャネルのステータスを確認し、RUNNINGになるまで待機する
  5. 素材尺+5秒間、MediaLiveで配信しアーカイブ収録を行う
  6. チャネルを停止する
  7. 書き出されたHLS素材を時間尺で切り出してmp4を作成する

なお、今回のStep Functionsへの入力値は以下の形式とします。

{ "FileUrl": "s3://your-bucket-name/filename.mp4" }

それでは実装していきます。

1. アップロードされたmp4素材の時間尺を取得する

まずFileUrlを変数として設定するためにPass stateを配置し、変数を定義します。

{ "FileUrl": "{% $states.input.FileUrl %}" }

次にMediaConvert: Probeを配置します。
引数は以下のとおりで、出力は画像のようになります。

{ "InputFiles": [{ "FileUrl": "{% $FileUrl %}" }] }

ProbeResults.Container.Durationがmp4の素材尺です。
この値を使い、後のMediaConvertでの終了時間(EndTimecode)と、MediaLiveを停止するタイミングの「素材尺+5秒」を変数として定義します。

{
  "EndTimecode": "{% ($dur := $states.result.ProbeResults[0].Container.Duration; $h := $floor($dur / 3600); $m := $floor($dur / 60) - $floor($dur / 3600) * 60; $s := $floor($dur) - $floor($dur / 60) * 60; $f := $floor(($dur - $floor($dur)) * 29.97); ($h < 10 ? '0' : '') & $string($h) & ':' & ($m < 10 ? '0' : '') & $string($m) & ':' & ($s < 10 ? '0' : '') & $string($s) & ':' & ($f < 10 ? '0' : '') & $string($f)) %}",
  "recordingWaitSeconds": "{% $floor($states.result.ProbeResults[0].Container.Duration) + 5 %}"
}

これにより、EndTimecodeが00:00:15:01、recordingWaitSecondsが20となります。

2. MediaLiveの特定チャネルの入力ソースを変更する

MediaLive: UpdateInputを配置し、引数を以下のように設定します。

{
  "InputId": "7458662",
  "Sources": [{ "Url": "{% $FileUrl %}" }]
}

InputIdには先ほど作成したMediaLive入力のIDを入力してください。
実行結果は画像のとおりです。
アタッチされているチャネルがInput.AttachedChannelsに含まれているので、これを変数として定義します。

{ "ChannelId": "{% $states.result.Input.AttachedChannels[0] %}" }

3. チャネルを開始する
4. RUNNINGになるまで待機する

MediaLive: StartChannelで開始しますが、起動直後はすぐに配信が始まりません。
そのためMediaLive: DescribeChannelでステータスがRUNNINGになったか確認し、まだであれば5秒待ってから再確認するループを作ります。

なお、MediaLive: StartChannelMediaLive: DescribeChannelの引数は共通です。

{ "ChannelId": "{% $ChannelId %}" }

Choice stateの条件は以下のとおりです。

{% $states.input.State = 'RUNNING' %}

5. アーカイブ収録する
6. チャネルを停止する

RUNNINGを確認してから素材尺+5秒間待機し、MediaLive: StopChannelで停止します。

Wait stateの秒数は{% $recordingWaitSeconds %}とし、MediaLive: StopChannelの引数は以下のとおりです。

{ "ChannelId": "{% $ChannelId %}" }

7. HLS素材を切り出してmp4を作成する

最後にMediaConvert: CreateJobでジョブを作成し、TSファイルをmp4に変換します。

引数は以下のとおりです。

{
  "Role": "arn:aws:iam::YOUR_ACCOUNT_ID:role/service-role/MediaConvert_Default_Role",
  "Settings": {
    "Inputs": [
      {
	      "TimecodeSource":  "ZEROBASED",
        "FileInput": "s3://test-elemental-inference-bucket/archive/archive.000000.ts",
        "AudioSelectors": {
          "Audio Selector 1": {
            "DefaultSelection": "DEFAULT"
          }
        },
        "InputClippings": [
          {
            "StartTimecode": "00:00:00:00",
            "EndTimecode": "{% $EndTimecode %}"
          }
        ]
      }
    ],
    "OutputGroups": [
      {
        "Outputs": [
          {
            "ContainerSettings": {
              "Container": "MP4"
            },
            "VideoDescription": {
              "CodecSettings": {
                "Codec": "H_264",
                "H264Settings": {
                  "RateControlMode": "QVBR",
                  "MaxBitrate": 5000000
                }
              }
            },
            "AudioDescriptions": [
              {
                "AudioSourceName": "Audio Selector 1",
                "CodecSettings": {
                  "Codec": "AAC",
                  "AacSettings": {
                    "Bitrate": 96000,
                    "CodingMode": "CODING_MODE_2_0",
                    "SampleRate": 48000
                  }
                }
              }
            ]
          }
        ],
        "OutputGroupSettings": {
          "Type": "FILE_GROUP_SETTINGS",
          "FileGroupSettings": {
            "Destination": "s3://test-elemental-inference-bucket/output/"
          }
        }
      }
    ]
  }
}

以上のStep Functionsを構築・実行することで、縦型のmp4が生成されます。

なお、StopChannel直後にMediaConvertを実行すると、素材の書き出しが間に合わない場合がありました。
Waitを挟んだりS3への書き出し完了チェックを入れたりする対策も検討が必要となります。

また、実際にデモアプリを作った際はエラーハンドリングやステータス確認、出力ファイル名の動的変更なども対応しました。

実際にデモアプリを作ってみた

このStep Functionsをベースに、実際のデモアプリを構築しました。
mp4をアップロードして処理を開始すると、現在のステータスもリアルタイムで確認できるようにしています。

実際に手を動かしてみると、Step Functionsの細かいチューニングや、Elemental InferenceとMediaLiveの挙動の把握など、ドキュメントを読むだけでは見えてこない部分がたくさんありました。
特にチャネルの起動タイミングや素材の書き出し完了を待つ処理まわりは、何度か失敗しながら調整した部分です。
新しいサービスを触るとはそういうことだよな、と改めて実感しました。

ただ、そういう試行錯誤の過程でMediaLiveやElemental Inferenceへの理解も深まりましたし、新しいサービスを起点に周辺サービスまで知見が広がるのは、大きな収穫だと感じました。
 
次はEvent Clippingに挑戦してみたいです!

Previous Post