Azure Media ServicesのSDK(Software Development Kit)のJavaを試してみる

nazology.net

水の確保が難しい地域では、海水を飲料水に変える「淡水化装置」が重宝されます。特に、どこでも利用できる携帯型の淡水化装置は、離島や船で大いに役立つでしょう。

海水を飲水に変える! MITが開発した「フィルターいらずの携帯型淡水化装置」 - ナゾロジー

しかし、従来の装置は何度もフィルターを交換しなければならず、不便でした。

海水を飲水に変える! MITが開発した「フィルターいらずの携帯型淡水化装置」 - ナゾロジー

そこでアメリカ・マサチューセッツ工科大学(MIT)の電子工学者ジョンヨン・ハン氏ら研究チームは、フィルターいらずの携帯型淡水化装置を開発しました。

海水を飲水に変える! MITが開発した「フィルターいらずの携帯型淡水化装置」 - ナゾロジー

⇧ amazing...漂流しても飲み水に困らないってのは革命的ですね。

個人利用だとMicrosoftアカウントかGitHubアカウントが必須

公式のドキュメントだと見当たらないのですが、

ascii.jp

Azureアカウントを作成するために必要なものは、以下の3点です。

 ●クレジットカード
 ●利用可能な電話番号
 ●Microsoftアカウント、もしくはGitHubアカウント

 Microsoftアカウントは、WindowsやOffice 365などのMicrosoft製品/サービスを利用するために必要な共通アカウントです。そのため、すでに持っている方も多いかもしれません。こちらからアカウントの情報を確認することができます。

ASCII.jp:Azureアカウントを作成しよう ―利用前の準備と知っておきたい3つのこと (1/3)

⇧ 上記サイト様が「Azure アカウント」作成するにあたり必要な情報をまとめてくださってます。

まずは、Microsoft Azureのアカウント作成で

何はともあれ、Microsoft Azureのアカウントを作成しないと、Azureのサービスは一切利用できないので、作成します。

作成して30日間だけ無料期間なので、始めるタイミングに迷うところですよね...

そもそも自分の場合、

ts0818.hatenablog.com

⇧ 過去に「Azure アカウント」を一度作成していて、

docs.microsoft.com

⇧ アップグレードしなかった記憶が...

docs.microsoft.com

⇧う~ん、過去に「Azure アカウント」を一度作成していると、無料期間は付いてこない感じですかね...

新たに作成できるのか試してみることに。

azure.microsoft.com

このあたりは、自分は一度作成してるということで、電話番号とか設定されてましたが、各々の情報を入力すればOK。「テキスト メッセージを送信する」ボタンを押下。

スマホに届いた「認証コード」を入力して、「コードの確認」ボタンを押下。

クレジットカードが有効期限切れになってる場合は、「新しい支払方法を追加する」を選択。

クレジットカードが更新されてる場合は、無料アカウント作成で特典が付いてくるみたい。

メールでも、無料アカウントの特典が付与された旨が記載されてました、一安心。

Azure Media Servicesのアカウント作成で

とりあえず、Azure Media Servicesを探します。

「メディア サービスの作成」ボタンを押下。

「リソース グループ」は「新規作成」で適当に作成してます。「Media Services アカウント名」を適当に入力し、「新しいストレージ アカウントの作成」のリンクを選択。

「タグ」については、今回は割愛しますが、

docs.microsoft.com

⇧ 上記が詳しいです。

画面下部の同意にチェックをしてから、「確認および作成」タブを選択。

「作成」ボタンを押下。

「すべてのリソース」を確認すると、

⇧ 作成した「Azure リソース」が確認できます。

依存関係は旧いものは非推奨らしい

出だしから躓いてますが、

docs.microsoft.com

mvnrepository.com

⇧ なんか、公式には載ってないのが、Maven Repositoryにはあるというところが引っかかって調べたところ、

github.com

Historical Releases

Note that the latest libraries from Microsoft are in the com.azure Maven group ID, and have the package naming pattern of beginning with com.azure. If you're using libraries that are in com.microsoft.azure Maven group ID, or have this as the package structure, please consider migrating to the latest libraries. You can find a mapping table from these historical releases to their equivalent here.

https://github.com/Azure/azure-sdk-for-java

⇧ どうやら、依存関係は、

の2つあるらしく、「com.azure」のほうが新しいほうだから、新しいほうを使ってください、ということらしい...。

公式のサンプルが公開されてるので、

github.com

⇧ 各サンプルプロジェクトとのpom.xmlを参考に「依存関係」を追加していきます。

Azure Media ServicesのAPIの利用に、Azure Active Directory (Azure AD) のIDが必須な件

で、早速、必要な情報が確認できないのだが...

なんか、CLIがあるみたいなので、

docs.microsoft.com

docs.microsoft.com

⇧ インストールしてみたのですが、

ドキュメントを参考に、

docs.microsoft.com

コマンドで確認しようとしたら、

まさかの正常に動かないという...流石はMicrosoftさん。

動くコマンドもあるにはあるけど、

Azure Portalだと、「Azure テナント ID」が設定されてるんだけど、コマンドだとnullって...ちょっと酷すぎるんじゃないですかね、Microsoftさん...。

で、最終的に、調べた限りでは、

docs.microsoft.com

blog.azure.moe

⇧ 上記サイト様によりますと、「AAD(Azure Active Directory)アプリID」なるものを作成する必要があるみたい。

「サービス プリンシパルの認証」タブを選択した状態で、

「ADD アプリとシークレットの管理」で「ADD アプリ」の「新規作成」のリンクを押下。

続いて、「シークレット」で「新規作成」のリンクを押下。

「説明」を適当に入力し、今回「有効期限」は「1年」にしてますが、一般的には「なし」とかにするのではないかと。

作成が済むと、

「Azure クライアント ID」「Azure クライアント シークレット」が自動で設定されるようです。ただ、ページの再読み込みとかすると、設定がリセットされるので、適当なエディターなりにコピっときましょう。

2022年5月18日(水)追記:↓ ここから

どうやら、Azureのリソースとして表示されないのですが、「Azure Active Directory」を選択し、

「App registrations」を選択し、

「All applications」タブを選択し、「Display name」に表示されてるリンクを選択すると、

「Azure クライアント ID」については「Application (client) ID」の値で確認できました。ちなみに、「Client credentials」を確認してみると、

「Secret ID」なるものは確認できるのですが、「Azure クライアント シークレット」の値は確認できず...

Value」が「Azure クライアント シークレット」の値らしいのですが、

Client secret values cannot be viewed, except for immediately after creation. Be sure to save the secret when created before leaving the page.    

⇧ とメッセージが表示されていて、Google翻訳したところ

クライアントシークレット値は、作成直後を除いて表示できません。ページを離れる前に、作成時にシークレットを必ず保存してください。』

とあるんですが、この仕様はどうにかしたほうが良いような...

2022年5月18日(水)追記:↑ ここまで

2022年5月31日(火)追記:↓ ここから

なんか、

azure.microsoft.com

⇧ 上記の説明によると、

認証方法が、「ADD(Azure Active Directory)」を利用した方式に変わっていて、

Making it easy to get started with the new API Access Blade for Media Services

Azure Active Directory authentication could be complex for users unfamiliar with the details of AAD, so we wanted to make it very easy to get started with very little knowledge of AAD. For that reason, we are introducing a new "API Access" blade for Media Services accounts in the portal that will replace the previous ACS "Account keys" blade. We are also disabling the ability to rotate the ACS keys to promote users to update their code and move to AAD support.

https://azure.microsoft.com/ja-jp/blog/azure-media-service-aad-auth-and-acs-deprecation/

⇧「ADD(Azure Active Directory)」の認証はユーザーにとって「complex」だから「API access」を用意しましたってことらしい。

scrapbox.io

以上のことからソフトウェアの設計において、ComplexとComplicatedを次のように使いたい。
 Complex: 1つのエンティティ、1つのクラス、1つのfunctionに複数の責務・概念が混ざっている。単一責任原則(SRP)に違反した状態
 Complicated: エンティティやクラス、functionの数が多く、それぞれの役割や関係性を理解するのが厄介。

ComplexとComplicated - kawasima

⇧ う~む、「ADD(Azure Active Directory)」は、構造的にイケてないってことなんかな?

話が脱線しましたが、

stackoverflow.com

It depends on whether your application supports personal account login. If your application supports personal account login, you can only create two client secrets at most.

If your application only supports work account login, there will be no limit to the number of client secrets created.

You can learn about or modify the account types supported by the application by viewing the manifest of the application. It is the signInAudience attribute in the manifest.

https://stackoverflow.com/questions/67536954/can-i-generate-azure-ad-client-credentials-secrets-using-an-api

⇧ 衝撃...secretが2つまでしか作成できないとかアカンやろ...

docs.microsoft.com

signInAudience attribute 

Specifies what Microsoft accounts are supported for the current application. Supported values are:

  • AzureADMyOrg - Users with a Microsoft work or school account in my organization's Azure AD tenant (for example, single tenant)
  • AzureADMultipleOrgs - Users with a Microsoft work or school account in any organization's Azure AD tenant (for example, multi-tenant)
  • AzureADandPersonalMicrosoftAccount - Users with a personal Microsoft account, or a work or school account in any organization's Azure AD tenant
  • PersonalMicrosoftAccount - Personal accounts that are used to sign in to services like Xbox and Skype.

https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-app-manifest#signinaudience-attribute

⇧「PersonalMicrosoftAccount」じゃなければ制限ないってことなんかな?

確認してみた。

⇧ secretの作成できる数に制限はないってことで良いんかな?

2022年5月31日(火)追記:↑ ここまで

Azure Media Servicesのストリーミングエンドポイントは動画配信しなくても作成が必要な件

動画配信しないのであれば、「ストリーミングエンドポイント」は作成不要と思いきや、然に非ず。

手前味噌になり恐縮ですが、

ts0818.hatenablog.com

⇧ 上記でまとめさせていただいております。

どうも、「VoD(Video on Demand)」での動画配信の場合は、「動画」を「エンコード」して、「Asset」の作成までであっても、「ストリーミングエンドポイント」が必要ってことになるらしい...少なくとも、公式のJavaのサンプルだと必要みたい。

なので、作成。

CDNはなしにしました。

Azure Media ServicesのSDK(Software Development Kit)のJavaを試してみる

実際に動くか試してみました。

コードについては、

ts0818.hatenablog.com

⇧ 上記の時のものを流用してます。

新規追加、修正を加えた部分だけ記載。

◇フロントエンド

■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-router-work\my-project-vue\src\views\AboutView.vue

<template>
  <div class="about">
    <h1>This is an about page</h1>
    <label
      >画像を選択
      <input type="file" @change="onFileChange" />
    </label>
    <button @click="removeFile">削除</button>
    <button @click="uploadFile">アップロード</button>
    <div>
      <video v-if="isMovie" :src="selectedFile" />
      <img v-else :src="selectedFile" />
    </div>

    <!--<img :src="require('@/assets/images/SVGアイコン.svg')" />-->
  </div>
</template>

<script lang="ts">
/* eslint-disable no-console */
import { Component, Vue, Watch } from "vue-property-decorator";
import UserService from "../api/userService";
import UserDto from "../dto/user/UserDto";

@Component
export default class AboutView extends Vue {
  private temp = "";
  private selectedFile = "";
  private tempFile = {};
  private userDto?: any;
  private isMovie = false;

  created() {
    //this.test();
  }

  private onFileChange(event: any) {
    const files = event.target.files || event.dataTransfer.files;
    this.tempFile = files[0];
    this.createDisplayImage(files[0], event);
  }

  private createDisplayImage(file: any, event: any) {
    const fileReader = new FileReader();
    fileReader.onload = (event) => {
      console.log("createDisplayImage");
      console.dir(event);
      if (event && event.target) {
        this.selectedFile = event.target.result as string;
        this.isMovie = this.isMimeTypeOfMovie(file.type);
      }
    };
    fileReader.readAsDataURL(file);
  }

  private removeFile() {
    this.selectedFile = "";
  }

  private uploadFile() {
    console.log("■created()");
    console.dir(this.selectedFile);
    console.dir(this.tempFile);
    let data = "テスト";
    this.userDto = Object.entries({
      id: data,
      file: this.tempFile as Blob,
    });
    let formData = new FormData();
    formData.append("id", data);
    formData.append("file", this.tempFile as File);

    //formData.append("userFormDto", JSON.stringify(userDto as any));
    formData.append("userFormDto", this.userDto);
    var postdata = new URLSearchParams();
    postdata.append("userFormDto", this.userDto);
    //postdata.append("id", data);
    //postdata.append("file", JSON.stringify(this.tempFile as File));

    // https://tech.chakapoko.com/nodejs/http/axios.html
    console.log("■送信前");
    UserService.save(formData)
      //UserService.save(postdata)
      .then((result) => {
        console.log("■レスポンス受信");
        console.log("■バックエンドからの返却の値");
        console.dir(result);
      })
      .catch((error) => {
        console.log(error);
      });
    console.log("■処理中...");
    console.dir(UserService.chunkedArr);
    //const numArr = [...Array(100000).keys()].map((i: number) => ++i);
    const numArr = Array.from(Array(1000000).keys()).map((x: number) => x + 1);
    let response = "[";
    for (let val of numArr) {
      let tmp = val === numArr.length ? "" : ",";
      response = response.concat(val.toString(), tmp);
    }
    response += "]";
    console.log("■フロントエンド");
    console.log(response);
  }

  private test() {
    console.log("■created()");
    let data = "テスト";
    let formData = new FormData();
    formData.append("id", data);
    // https://tech.chakapoko.com/nodejs/http/axios.html
    console.log("■送信前");
    UserService.save(formData)
      .then((result) => {
        console.log("■レスポンス受信");
        console.log("■バックエンドからの返却の値");
        console.dir(result);
      })
      .catch((error) => {
        console.log(error);
      });
    console.log("■処理中...");
    console.dir(UserService.chunkedArr);
    //const numArr = [...Array(100000).keys()].map((i: number) => ++i);
    const numArr = Array.from(Array(1000000).keys()).map((x: number) => x + 1);
    let response = "[";
    for (let val of numArr) {
      let tmp = val === numArr.length ? "" : ",";
      response = response.concat(val.toString(), tmp);
    }
    response += "]";
    console.log("■フロントエンド");
    console.log(response);
  }

  @Watch("temp")
  private setInfo() {
    console.dir(this.temp);
  }

  private isMimeTypeOfMovie(mimeType: string): boolean {
    if (mimeType) {
      switch (mimeType) {
        case "video/mp4":
        case "video/mov":
          return true;
        default:
          return false;
      }
    }
    return false;
  }
}
</script>

続いて、サーバーサイド。新規で追加したファイルは、以下の2ファイル。

◇サーバーサイド

■test-uml/build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.6'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'org.postgresql:postgresql'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'com.azure.resourcemanager:azure-resourcemanager-mediaservices:2.0.0'
    implementation 'com.googlecode.json-simple:json-simple:1.1.1'
    implementation 'com.azure:azure-storage-blob:12.16.0'
    implementation 'com.azure:azure-identity:1.5.0'
}

tasks.named('test') {
	useJUnitPlatform()
}

■test-uml/src/main/resources/application.properties

# application server port
server.port=8088

# cors
management.endpoints.web.cors.allowed-origins=http://localhost:8080

# database
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5434/test
spring.datasource.username=postgres
spring.datasource.password=postgres

# multipart 
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=1024MB
spring.servlet.multipart.max-request-size=1025MB

# Azure Media Services
azure.media.client.id=[Azure クライアント IDの値]
azure.media.client.secret=[Azure クライアント シークレットの値]
azure.media.tenant.id=[Azure テナント IDの値]
azure.media.service.account.name=[Azure Media Services アカウント名の値]
azure.media.arm.token.id=[Azure ARM トークン対象ユーザーの値]
azure.media.arm.endpoint=[Azure ARM エンドポイントの値]
azure.media.region=[リージョンの値]
azure.resource.group=[Azure Resource グループの値]
azure.subscription.id=[Azure サブスクリプション IDの値]

⇧ Azure Portalの「Azure Media Services」でそれぞれの値を確認して設定します。

ちなみに、Eclipseだと、application.propertiesでカスタムなプロパティを利用すると警告が出ますが、プロパティの値として利用はできます。

■test-uml/src/main/java/com/example/demo/service/azure/media/ConfigWrapper.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.example.demo.service.azure.media;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * This class reads values from local configuration file
 * resources/conf/appsettings.json.
 * Please change the configuration using your account information. For more
 * information, see
 * https://docs.microsoft.com/azure/media-services/latest/access-api-cli-how-to.
 * For security
 * reasons, do not check in the configuration file to source control.
 */
@Configuration
//@AllArgsConstructor
@NoArgsConstructor
@Data
public class ConfigWrapper {
	@Value("${azure.media.client.id}")
    private String azureClientId;
	@Value("${azure.media.client.secret}")
    private String azureClientSecret;
	@Value("${azure.media.tenant.id}")
    private String aadTenantId;
	@Value("${azure.media.service.account.name}")
    private String azureMediaServicesAccountName;
	@Value("${azure.media.arm.token.id}")
    private  String azureArmTokenAudience;
	@Value("${azure.media.arm.endpoint}")
    private String azureArmEndpoint;
	@Value("${azure.media.region}")
    private String region;
	@Value("${azure.resource.group}")
    private String azureResourceGroup;
	@Value("${azure.subscription.id}")
    private String azureSubscriptionId;
//    private static String CONF_JSON = "conf/appsettings.json";
//    private final JSONObject jsonObject;
//    private final InputStreamReader isReader;

//    public ConfigWrapper() {
//        InputStream inStream = ConfigWrapper.class.getClassLoader().getResourceAsStream(CONF_JSON);
//        isReader = new InputStreamReader(inStream);
//
//        JSONParser parser = new JSONParser();
//        Object obj = null;
//        try {
//            obj = parser.parse(isReader);
//        } catch (Exception ioe) {
//            System.err.println(ioe);
//            System.exit(1);
//        }
//
//        jsonObject = (JSONObject) obj;
//    }
//
//    public void close() {
//        try {
//            if (isReader != null) {
//                isReader.close();
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
//
//    public String getAadClientId() {
//        return (String) jsonObject.get(AZURE_CLIENT_ID);
//    }
//
//    public String getAadSecret() {
//        return (String) jsonObject.get(AZURE_CLIENT_SECRET);
//    }
//
//    public String getAadTenantId() {
//        return (String) jsonObject.get(AAD_TENANT_ID);
//    }
//
//    public String getAccountName() {
//        return (String) jsonObject.get(AZURE_MEDIA_SERVICES_ACCOUNT_NAME);
//    }
//
//    public String getArmAadAudience() {
//        return (String) jsonObject.get(AZURE_ARM_TOKEN_AUDIENCE);
//    }
//
//    public String getArmEndpoint() {
//        return (String) jsonObject.get(AZURE_ARM_ENDPOINT);
//    }
//
//    public String getRegion() {
//        return (String) jsonObject.get(REGION);
//    }
//
//    public String getResourceGroup() {
//        return (String) jsonObject.get(AZURE_RESOURCE_GROUP);
//    }
//
//    public String getSubscriptionId() {
//        return (String) jsonObject.get(AZURE_SUBSCRIPTION_ID);
//    }
}

■test-uml/src/main/java/com/example/demo/service/azure/media/EncodingWithMESPredefinedPreset.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.example.demo.service.azure.media;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.naming.AuthenticationException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.azure.core.credential.TokenCredential;
import com.azure.core.http.policy.HttpLogDetailLevel;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.core.management.exception.ManagementException;
import com.azure.core.management.profile.AzureProfile;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.azure.resourcemanager.mediaservices.MediaServicesManager;
import com.azure.resourcemanager.mediaservices.models.Asset;
import com.azure.resourcemanager.mediaservices.models.AssetContainerPermission;
import com.azure.resourcemanager.mediaservices.models.AssetContainerSas;
import com.azure.resourcemanager.mediaservices.models.BuiltInStandardEncoderPreset;
import com.azure.resourcemanager.mediaservices.models.EncoderNamedPreset;
import com.azure.resourcemanager.mediaservices.models.Job;
import com.azure.resourcemanager.mediaservices.models.JobInput;
import com.azure.resourcemanager.mediaservices.models.JobInputAsset;
import com.azure.resourcemanager.mediaservices.models.JobOutput;
import com.azure.resourcemanager.mediaservices.models.JobOutputAsset;
import com.azure.resourcemanager.mediaservices.models.JobState;
import com.azure.resourcemanager.mediaservices.models.ListContainerSasInput;
import com.azure.resourcemanager.mediaservices.models.ListPathsResponse;
import com.azure.resourcemanager.mediaservices.models.StreamingEndpoint;
import com.azure.resourcemanager.mediaservices.models.StreamingEndpointResourceState;
import com.azure.resourcemanager.mediaservices.models.StreamingLocator;
import com.azure.resourcemanager.mediaservices.models.StreamingPath;
import com.azure.resourcemanager.mediaservices.models.Transform;
import com.azure.resourcemanager.mediaservices.models.TransformOutput;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobContainerClientBuilder;

@Service
public class EncodingWithMESPredefinedPreset {
	
	@Autowired
	private ConfigWrapper configWrapper;
	
    private static final String TRANSFORM_NAME = "AdaptiveBitrate";
    private static final String OUTPUT_FOLDER = "Output";
    //private static final String BASE_URI = "https://nimbuscdn-nimbuspm.streaming.mediaservices.windows.net/2b533311-b215-4409-80af-529c3e853622/";
    //private static final String INPUT_LABEL = "input1";

    // Please change this to your endpoint name
    private static final String STREAMING_ENDPOINT_NAME = "default";

    private MediaServicesManager createMediaServicesManager () {
        // Connect to media services, please see https://docs.microsoft.com/en-us/azure/media-services/latest/configure-connect-java-howto
        // for details.
        TokenCredential credential = new ClientSecretCredentialBuilder()
                .clientId(configWrapper.getAzureClientId())
                .clientSecret(configWrapper.getAzureClientSecret())
                .tenantId(configWrapper.getAadTenantId())
                .build();
        AzureProfile profile = new AzureProfile(configWrapper.getAadTenantId(), configWrapper.getAzureSubscriptionId(),
                com.azure.core.management.AzureEnvironment.AZURE);

        // MediaServiceManager is the entry point to Azure Media resource management.
        MediaServicesManager manager = MediaServicesManager.configure()
                .withLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS))
                .authenticate(credential, profile);
        return manager;
    }

    /**
     * Run the sample.
     *
     * @param config This param is of type ConfigWrapper. This class reads values from local configuration file.
     */
    public void runEncodingWithMESPredefinedPreset(MultipartFile uploadFile) {
        // MediaServiceManager is the entry point to Azure Media resource management.
        MediaServicesManager manager = createMediaServicesManager();

        // Creating a unique suffix so that we don't have name collisions if you run the
        // sample
        UUID uuid = UUID.randomUUID();
        String uniqueness = uuid.toString();
        String jobName = "job-" + uniqueness.substring(0, 13);
        String locatorName = "locator-" + uniqueness;
        String outputAssetName = "output-" + uniqueness;
        String inputAssetName = "input-" + uniqueness;
        boolean stopEndpoint = false;

        try {
            List<TransformOutput> outputs = new ArrayList<>();
            outputs.add(new TransformOutput().withPreset(
                    new BuiltInStandardEncoderPreset().withPresetName(EncoderNamedPreset.CONTENT_AWARE_ENCODING)));

            // Create the transform.
            System.out.println("Creating a transform...");
            Transform transform = manager.transforms()
                    .define(TRANSFORM_NAME)
                    .withExistingMediaService(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName())
                    .withOutputs(outputs)
                    .create();
            System.out.println("Transform created");

            // Create a JobInputHttp. The input to the Job is a HTTPS URL pointing to an MP4 file.
//            List<String> files = new ArrayList<>();
//            files.add(uploadFile.getOriginalFilename());
//            JobInputHttp input = new JobInputHttp().withBaseUri(BASE_URI);
//            input.withFiles(files);
//            input.withLabel(INPUT_LABEL);

            Asset inputAsset = createInputAsset(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), inputAssetName,
            		uploadFile);
            
            // Output from the encoding Job must be written to an Asset, so let's create one. Note that we
            // are using a unique asset name, there should not be a name collision.
            System.out.println("Creating an output asset...");
            Asset outputAsset = manager.assets()
                    .define(outputAssetName)
                    .withExistingMediaService(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName())
                    .create();

            Job job = submitJob(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), transform.name(), jobName,
            		inputAsset.name(), outputAsset.name());

            long startedTime = System.currentTimeMillis();

            // In this demo code, we will poll for Job status. Polling is not a recommended best practice for production
            // applications because of the latency it introduces. Overuse of this API may trigger throttling. Developers
            // should instead use Event Grid. To see how to implement the event grid, see the sample
            // https://github.com/Azure-Samples/media-services-v3-java/tree/master/ContentProtection/BasicAESClearKey.
            job = waitForJobToFinish(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), transform.name(),
                    jobName);

            long elapsed = (System.currentTimeMillis() - startedTime) / 1000; // Elapsed time in seconds
            System.out.println("Job elapsed time: " + elapsed + " second(s).");            

            if (job.state() == JobState.FINISHED) {
                System.out.println("Job finished.");
                System.out.println();

                // Now that the content has been encoded, publish it for Streaming by creating
                // a StreamingLocator. 
                StreamingLocator locator = getStreamingLocator(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(),
                        outputAsset.name(), locatorName);

                StreamingEndpoint streamingEndpoint = manager.streamingEndpoints()
                        .get(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), STREAMING_ENDPOINT_NAME);

                if (streamingEndpoint != null) {
                    // Start The Streaming Endpoint if it is not running.
                    if (streamingEndpoint.resourceState() != StreamingEndpointResourceState.RUNNING) {
                        System.out.println("Streaming endpoint was stopped, restarting it...");
                        manager.streamingEndpoints().start(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), STREAMING_ENDPOINT_NAME);

                        // We started the endpoint, we should stop it in cleanup.
                        stopEndpoint = true;
                    }

                    System.out.println();
                    System.out.println("Streaming urls:");
                    List<String> urls = getStreamingUrls(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), locator.name(), streamingEndpoint);

                    for (String url : urls) {
                        System.out.println("\t" + url);
                    }

                    System.out.println();
                    System.out.println("To stream, copy and paste the Streaming URL into the Azure Media Player at 'http://aka.ms/azuremediaplayer'.");
                    System.out.println("When finished, press ENTER to continue.");
                    System.out.println();
                    System.out.flush();
                    

                    // Download output asset for verification.
                    System.out.println("Downloading output asset...");
                    System.out.println();
                    File outputFolder = new File(OUTPUT_FOLDER);
                    if (outputFolder.exists() && !outputFolder.isDirectory()) {
                        outputFolder = new File(OUTPUT_FOLDER + uniqueness);
                    }
                    if (!outputFolder.exists()) {
                        outputFolder.mkdir();
                    }

                    downloadResults(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), outputAsset.name(),
                            outputFolder);

                    System.out.println("Done downloading. Please check the files at " + outputFolder.getAbsolutePath());
                } else {
                    System.out.println("Could not find streaming endpoint: " + STREAMING_ENDPOINT_NAME);
                }

                System.out.println("When finished, press ENTER to cleanup.");
                System.out.println();
                System.out.flush();
               
            } else if (job.state() == JobState.ERROR) {
                System.out.println("ERROR: Job finished with error message: " + job.outputs().get(0).error().message());
                System.out.println("ERROR:                   error details: "
                        + job.outputs().get(0).error().details().get(0).message());
            }
        } catch (Exception e) {
            Throwable cause = e;
            while (cause != null) {
                if (cause instanceof AuthenticationException) {
                    System.out.println("ERROR: Authentication error, please check your account settings in appsettings.json.");
                    break;
                } else if (cause instanceof ManagementException) {
                    ManagementException apiException = (ManagementException) cause;
                    System.out.println("ERROR: " + apiException.getValue().getMessage());
                    break;
                }
                cause = cause.getCause();
            }
            System.out.println();
            e.printStackTrace();
            System.out.println();
        } finally {
            System.out.println("Cleaning up...");
            cleanup(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), TRANSFORM_NAME, jobName,
                    outputAssetName, locatorName, stopEndpoint, STREAMING_ENDPOINT_NAME);
            System.out.println("Done.");
        }
    }

    /**
     * Create and submit a job.
     *
     * @param manager         The entry point of Azure Media resource management.
     * @param resourceGroup   The name of the resource group within the Azure subscription.
     * @param accountName     The Media Services account name.
     * @param transformName   The name of the transform.
     * @param jobName         The name of the job.
     * @param jobInput        The input to the job.
     * @param outputAssetName The name of the asset that the job writes to.
     * @return The job created.
     */
    private static Job submitJob(MediaServicesManager manager, String resourceGroup, String accountName, String transformName,
                                 String jobName, String inputAssetName,  String outputAssetName) {
        System.out.println("Creating a job...");
        // Use the name of the created input asset to create the job input.
        JobInput jobInput = new JobInputAsset().withAssetName(inputAssetName);

        // First specify where the output(s) of the Job need to be written to
        List<JobOutput> jobOutputs = new ArrayList<>();
        jobOutputs.add(new JobOutputAsset().withAssetName(outputAssetName));

        Job job = manager.jobs().define(jobName)
                .withExistingTransform(resourceGroup, accountName, transformName)
                .withInput(jobInput)
                .withOutputs(jobOutputs)
                .create();

        return job;
    }

    /**
     * Polls Media Services for the status of the Job.
     *
     * @param manager       This is the entry point of Azure Media resource
     *                      management
     * @param resourceGroup The name of the resource group within the Azure
     *                      subscription
     * @param accountName   The Media Services account name
     * @param transformName The name of the transform
     * @param jobName       The name of the job submitted
     * @return The job
     */
    private static Job waitForJobToFinish(MediaServicesManager manager, String resourceGroup, String accountName,
                                          String transformName, String jobName) {
        final int SLEEP_INTERVAL = 10 * 1000;

        Job job = null;
        boolean exit = false;

        do {
            job = manager.jobs().get(resourceGroup, accountName, transformName, jobName);

            if (job.state() == JobState.FINISHED || job.state() == JobState.ERROR || job.state() == JobState.CANCELED) {
                exit = true;
            } else {
                System.out.println("Job is " + job.state());

                int i = 0;
                for (JobOutput output : job.outputs()) {
                    System.out.print("\tJobOutput[" + i++ + "] is " + output.state() + ".");
                    if (output.state() == JobState.PROCESSING) {
                        System.out.print("  Progress: " + output.progress());
                    }
                    System.out.println();
                }

                try {
                    Thread.sleep(SLEEP_INTERVAL);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } while (!exit);

        return job;
    }

    /**
     * Use Media Service and Storage APIs to download the output files to a local folder
     *
     * @param manager       The entry point of Azure Media resource management
     * @param resourceGroup The name of the resource group within the Azure subscription
     * @param accountName   The Media Services account name
     * @param assetName     The asset name
     * @param outputFolder  The output folder for downloaded files.
     * @throws Exception
     * @throws URISyntaxException
     * @throws IOException
     */
    private static void downloadResults(MediaServicesManager manager, String resourceGroup, String accountName,
                                        String assetName, File outputFolder) throws URISyntaxException, IOException {
        ListContainerSasInput parameters = new ListContainerSasInput()
                .withPermissions(AssetContainerPermission.READ)
                .withExpiryTime(OffsetDateTime.now().plusHours(1));
        AssetContainerSas assetContainerSas = manager.assets()
                .listContainerSas(resourceGroup, accountName, assetName, parameters);

        BlobContainerClient container =
                new BlobContainerClientBuilder()
                        .endpoint(assetContainerSas.assetContainerSasUrls().get(0))
                        .buildClient();

        File directory = new File(outputFolder, assetName);
        directory.mkdir();

        container.listBlobs().forEach(blobItem -> {
            BlobClient blob = container.getBlobClient(blobItem.getName());
            File downloadTo = new File(directory, blobItem.getName());
            blob.downloadToFile(downloadTo.getAbsolutePath());
        });

        System.out.println("Download complete.");
    }

    /**
     * Creates a StreamingLocator for the specified asset and with the specified streaming policy name.
     * Once the StreamingLocator is created the output asset is available to clients for playback.
     *
     * @param manager       The entry point of Azure Media resource management
     * @param resourceGroup The name of the resource group within the Azure subscription
     * @param accountName   The Media Services account name
     * @param assetName     The name of the output asset
     * @param locatorName   The StreamingLocator name (unique in this case)
     * @return The locator created
     */
    private static StreamingLocator getStreamingLocator(MediaServicesManager manager, String resourceGroup, String accountName,
                                                        String assetName, String locatorName) {
        // Note that we are using one of the PredefinedStreamingPolicies which tell the Origin component
        // of Azure Media Services how to publish the content for streaming.
        System.out.println("Creating a streaming locator...");
        StreamingLocator locator = manager
                .streamingLocators().define(locatorName)
                .withExistingMediaService(resourceGroup, accountName)
                .withAssetName(assetName)
                .withStreamingPolicyName("Predefined_ClearStreamingOnly")
                .create();

        return locator;
    }

    /**
     * Checks if the streaming endpoint is in the running state, if not, starts it.
     *
     * @param manager           The entry point of Azure Media resource management
     * @param resourceGroup     The name of the resource group within the Azure subscription
     * @param accountName       The Media Services account name
     * @param locatorName       The name of the StreamingLocator that was created
     * @param streamingEndpoint The streaming endpoint.
     * @return List of streaming urls
     */
    private static List<String> getStreamingUrls(MediaServicesManager manager, String resourceGroup, String accountName,
                                                 String locatorName, StreamingEndpoint streamingEndpoint) {
        List<String> streamingUrls = new ArrayList<>();

        ListPathsResponse paths = manager.streamingLocators().listPaths(resourceGroup, accountName, locatorName);

        for (StreamingPath path : paths.streamingPaths()) {
            StringBuilder uriBuilder = new StringBuilder();
            uriBuilder.append("https://")
                    .append(streamingEndpoint.hostname())
                    .append("/")
                    .append(path.paths().get(0));

            streamingUrls.add(uriBuilder.toString());
        }
        return streamingUrls;
    }

    /**
     * Cleanup
     *
     * @param manager               The entry point of Azure Media resource management.
     * @param resourceGroupName     The name of the resource group within the Azure subscription.
     * @param accountName           The Media Services account name.
     * @param transformName         The transform name.
     * @param jobName               The job name.
     * @param assetName             The asset name.
     * @param streamingLocatorName  The streaming locator name.
     * @param stopEndpoint          Stop endpoint if true, otherwise keep endpoint running.
     * @param streamingEndpointName The endpoint name.
     */
    private static void cleanup(MediaServicesManager manager, String resourceGroupName, String accountName, String transformName, String jobName,
                                String assetName, String streamingLocatorName, boolean stopEndpoint, String streamingEndpointName) {
        if (manager == null) {
            return;
        }

        manager.jobs().delete(resourceGroupName, accountName, transformName, jobName);
        manager.assets().delete(resourceGroupName, accountName, assetName);

        manager.streamingLocators().delete(resourceGroupName, accountName, streamingLocatorName);

        if (stopEndpoint) {
            // Because we started the endpoint, we'll stop it.
            manager.streamingEndpoints().stop(resourceGroupName, accountName, streamingEndpointName);
        } else {
            // We will keep the endpoint running because it was not started by this sample. Please note, There are costs to keep it running.
            // Please refer https://azure.microsoft.com/en-us/pricing/details/media-services/ for pricing.
            System.out.println("The endpoint '" + streamingEndpointName + "' is running. To halt further billing on the endpoint, please stop it in azure portal or AMS Explorer.");
        }
    }
    /**
     * Creates a new input Asset and uploads the specified local video file into it.
     *
     * @param manager           This is the entry point of Azure Media resource
     *                          management.
     * @param resourceGroupName The name of the resource group within the Azure
     *                          subscription.
     * @param accountName       The Media Services account name.
     * @param assetName         The name of the asset where the media file to
     *                          uploaded to.
     * @param mediaFile         The path of a media file to be uploaded into the
     *                          asset.
     * @return The asset.
     */
    private static Asset createInputAsset(MediaServicesManager manager, String resourceGroupName, String accountName,
            String assetName, MultipartFile mediaFile) throws Exception {

        System.out.println("Creating an input asset...");
        // Call Media Services API to create an Asset.
        // This method creates a container in storage for the Asset.
        // The files (blobs) associated with the asset will be stored in this container.
        Asset asset = manager.assets().define(assetName).withExistingMediaService(resourceGroupName, accountName)
                .create();

        // Use Media Services API to get back a response that contains
        // SAS URL for the Asset container into which to upload blobs.
        // That is where you would specify read-write permissions
        // and the expiration time for the SAS URL.
        ListContainerSasInput parameters = new ListContainerSasInput()
                .withPermissions(AssetContainerPermission.READ_WRITE).withExpiryTime(OffsetDateTime.now().plusHours(4));
        AssetContainerSas response = manager.assets()
                .listContainerSas(resourceGroupName, accountName, assetName, parameters);

        // Use Storage API to get a reference to the Asset container
        // that was created by calling Asset's create method.
        BlobContainerClient container = new BlobContainerClientBuilder()
                .endpoint(response.assetContainerSasUrls().get(0))
                .buildClient();

        // Uploading from a local file:
       // URI fileToUpload = StreamHLSAndDASH.class.getClassLoader().getResource(mediaFile).toURI(); // The file is a
        // resource in
        // CLASSPATH.
        //File file = new File(fileToUpload);
        BlobClient blob = container.getBlobClient(mediaFile.getOriginalFilename());

        // Use Storage API to upload the file into the container in storage.
        System.out.println("Uploading a media file to the asset...");
        blob.upload(mediaFile.getInputStream(), mediaFile.getBytes().length);
        //blob.uploadFromFile(mediaFile.getOriginalFilename());

        return asset;
    }
}

■test-uml/src/main/java/com/example/demo/controller/UserController.java

package com.example.demo.controller;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import com.example.demo.dto.user.UserFormDto;
import com.example.demo.service.azure.media.EncodingWithMESPredefinedPreset;

import lombok.extern.log4j.Log4j2;

@Log4j2
@RestController
@RequestMapping("/user")
public class UserController {
	
	@Autowired
	private EncodingWithMESPredefinedPreset encodingWithMESPredefinedPreset;
	
	@PostMapping("/save")
	public SseEmitter save(@ModelAttribute  @Validated UserFormDto userFormDto) throws IOException {
		log.info("■■■INPUT チェック");
		log.info(userFormDto);
		log.info(userFormDto.getId());
		log.info(userFormDto.getFile());
		
		long timeOutForSseEmitter = 2 * 60 * 60 * 1000L;
		SseEmitter sseEmitter = new SseEmitter(timeOutForSseEmitter);
//		UserAsyncHelper userAsyncHelper = UserAsyncHelper.builder()
//		.sseEmitter(sseEmitter)
//		.userList(null)
//		.build();
//		Thread th = new Thread(userAsyncHelper);
		log.info("■■■処理開始前");
		sseEmitter.send("処理開始");
//		th.start();
		//userAsyncHelper.run();
		log.info("■■■処理開始後");
		encodingWithMESPredefinedPreset.runEncodingWithMESPredefinedPreset(userFormDto.getFile());
		
		return sseEmitter;
	}
}
    

⇧ 上記の変更で保存して、フロントエンド、サーバーサイドのアプリケーションのサーバーをそれぞれ起動して、

ブラウザで動画ファイルをアップロードします。

サーバーサイドで処理が行われ、

処理が正常に終了し、Azure Portalを確認すると、

ストリーミングの再生を確認するには、「ストリーミング ロケーター」も作成する必要がある模様。

⇧ 動画ファイルはアップロードできてるようです。「エンコード」の公式サンプルのソースコードを試してるので、「エンコード」もされてると信じたい。

2022年5月18日(水)追記:↓ ここから

手前味噌で恐縮ですが、

ts0818.hatenablog.com

⇧ 上記の記事で確認したところ、test-uml/src/main/java/com/example/demo/service/azure/media/EncodingWithMESPredefinedPreset.javaのcleanupメソッドの中で、エンコード後の結果が含まれたAssetを削除してたからエンコード後のファイル群が残らなかったようなので、該当箇所をコメントアウトすれば、Azure Media Services にエンコード後の結果のAssetが保存され、紐づくAzure Blob StorageのBLOBコンテナーにファイル群が保存されるようです。

2022年5月18日(水)追記:↑ ここまで

とりあえずは、動画ファイルがアップロードできてるところまでは確認できましたかね。

課金が発生すかどうかが分からなかったので、一応、「ストリーミングエンドポイント」は停止しておきました。

今回はこのへんで。