diff --git a/AudioAnalytics/AudioAnalyzer/README.md b/AudioAnalytics/AudioAnalyzer/README.md new file mode 100644 index 0000000..82619c1 --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/README.md @@ -0,0 +1,84 @@ +--- +topic: sample +languages: + - java +products: + - azure-media-services +--- + +# Analyze a media file with a audio analyzer preset + +This sample demonstrates how to analyze audio in a media file. It shows how to perform the following tasks: + +1. Creates a transform that uses a audio analyzer preset. +1. Uploads a media file to an input asset. +1. Submits an analyzer job. +1. Downloads the output asset for verification. + +## Prerequisites + +* Java JDK 1.8 or newer installed +* Maven installed +* An Azure Media Services account. See the steps described in [Create a Media Services account](https://docs.microsoft.com/azure/media-services/latest/create-account-cli-quickstart). + +## Running the example + +### Configure `appsettings.json` with appropriate access values + + Get credentials needed to use Media Services APIs by following [Access APIs](https://docs.microsoft.com/azure/media-services/latest/access-api-cli-how-to). Open the `src/main/resources/conf/appsettings.json` configuration file and paste the values in the file. + +### Clean and build the project + + Open a terminal window, go to the root folder of this project, run `mvn clean compile`. + +### Run this project + + Execute `mvn exec:java`, then follow the instructions in the output console. + +### Optional, do the following steps if you want to use Event Grid for job monitoring + +Please note, there are costs for using Event Hub. For more details, refer [Event Hubs pricing](https://azure.microsoft.com/en-in/pricing/details/event-hubs/) and [FAQ](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-faq#pricing) + +* Enable Event Grid resource provider + + `az provider register --namespace Microsoft.EventGrid` + +* To check if registered, run the next command. You should see "Registered" + + `az provider show --namespace Microsoft.EventGrid --query "registrationState"` + +* Create an Event Hub + + `namespace=`\ + `hubname=`\ + `az eventhubs namespace create --name $namespace --resource-group `\ + `az eventhubs eventhub create --name $hubname --namespace-name $namespace --resource-group ` + +* Subscribe to Media Services events + + `hubid=$(az eventhubs eventhub show --name $hubname --namespace-name $namespace --resource-group --query id --output tsv)`\ + `amsResourceId=$(az ams account show --name --resource-group --query id --output tsv)`\ + `az eventgrid event-subscription create --resource-id $amsResourceId --name --endpoint-type eventhub --endpoint $hubid` + +* Create a storage account and container for Event Processor Host if you don't have one + [Create a storage account for Event Processor Host + ](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-dotnet-standard-getstarted-send#create-a-storage-account-for-event-processor-host) + +* Update appsettings.json with your Event Hub and Storage information + StorageAccountName: The name of your storage account.\ + StorageAccountKey: The access key for your storage account. Navigate to Azure portal, "All resources", search your storage account, then "Access keys", copy key1.\ + StorageContainerName: The name of your container. Click Blobs in your storage account, find you container and copy the name.\ + EventHubConnectionString: The Event Hub connection string. search your namespace you just created. <your namespace> -> Shared access policies -> RootManageSharedAccessKey -> Connection string-primary key.\ + EventHubName: The Event Hub name. <your namespace> -> Event Hubs. + +## Key concepts + +* [Analyzing video and audio files](https://docs.microsoft.com/azure/media-services/latest/analyzing-video-audio-files-concept) +* [Transforms and Jobs](https://docs.microsoft.com/azure/media-services/latest/transforms-jobs-concept) +* [Standard Encoder formats and codecs](https://docs.microsoft.com/azure/media-services/latest/media-encoder-standard-formats) +* [Media Services job error codes](https://docs.microsoft.com/azure/media-services/latest/job-error-codes) + +## Next steps + +* [Azure Media Services pricing](https://azure.microsoft.com/pricing/details/media-services/) +* [Azure Media Services v3 Documentation](https://docs.microsoft.com/azure/media-services/latest/) diff --git a/AudioAnalytics/AudioAnalyzer/pom.xml b/AudioAnalytics/AudioAnalyzer/pom.xml new file mode 100644 index 0000000..3cf0926 --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/pom.xml @@ -0,0 +1,98 @@ + + 4.0.0 + sample + AudioAnalyzer + jar + 1.0-SNAPSHOT + AudioAnalyzer + http://maven.apache.org + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + com.microsoft.azure.mediaservices.v2018_07_01 + azure-mgmt-media + 1.0.0-beta-4 + + + com.microsoft.rest + client-runtime + 1.6.8 + + + com.microsoft.azure + azure-client-authentication + 1.6.8 + + + com.microsoft.azure + azure-storage + 8.3.0 + + + com.microsoft.azure + azure-arm-client-runtime + 1.6.8 + + + com.microsoft.azure + azure-client-runtime + 1.6.8 + + + com.microsoft.azure + azure-storage-blob + 11.0.1 + + + com.microsoft.azure + azure-eventhubs + 2.3.2 + + + com.microsoft.azure + azure-eventhubs-eph + 2.5.2 + + + org.slf4j + slf4j-api + 1.7.5 + + + org.slf4j + slf4j-log4j12 + 1.7.5 + + + com.fasterxml.jackson.core + jackson-databind + 2.9.9.3 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + org.codehaus.mojo + exec-maven-plugin + 1.4.0 + + sample.AudioAnalyzer + + + + + diff --git a/AudioAnalytics/AudioAnalyzer/src/main/java/sample/AudioAnalyzer.java b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/AudioAnalyzer.java new file mode 100644 index 0000000..ee82667 --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/AudioAnalyzer.java @@ -0,0 +1,520 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package sample; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Scanner; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.Callable; +import java.util.Arrays; + +import com.microsoft.azure.AzureEnvironment; +import com.microsoft.azure.credentials.ApplicationTokenCredentials; +import com.microsoft.azure.eventprocessorhost.EventProcessorHost; +import com.microsoft.azure.eventprocessorhost.EventProcessorOptions; +import com.microsoft.azure.management.mediaservices.v2018_07_01.Asset; +import com.microsoft.azure.management.mediaservices.v2018_07_01.AssetContainerPermission; +import com.microsoft.azure.management.mediaservices.v2018_07_01.AssetContainerSas; +import com.microsoft.azure.management.mediaservices.v2018_07_01.InsightsType; +import com.microsoft.azure.management.mediaservices.v2018_07_01.Job; +import com.microsoft.azure.management.mediaservices.v2018_07_01.JobInput; +import com.microsoft.azure.management.mediaservices.v2018_07_01.JobInputAsset; +import com.microsoft.azure.management.mediaservices.v2018_07_01.JobOutput; +import com.microsoft.azure.management.mediaservices.v2018_07_01.JobOutputAsset; +import com.microsoft.azure.management.mediaservices.v2018_07_01.JobState; +import com.microsoft.azure.management.mediaservices.v2018_07_01.ListContainerSasInput; +import com.microsoft.azure.management.mediaservices.v2018_07_01.Preset; +import com.microsoft.azure.management.mediaservices.v2018_07_01.Transform; +import com.microsoft.azure.management.mediaservices.v2018_07_01.TransformOutput; +import com.microsoft.azure.management.mediaservices.v2018_07_01.VideoAnalyzerPreset; +import com.microsoft.azure.management.mediaservices.v2018_07_01.implementation.MediaManager; +import com.microsoft.azure.storage.ResultContinuation; +import com.microsoft.azure.storage.ResultSegment; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.blob.BlobListingDetails; +import com.microsoft.azure.storage.blob.CloudBlobContainer; +import com.microsoft.azure.storage.blob.CloudBlockBlob; +import com.microsoft.azure.storage.blob.ListBlobItem; +import com.microsoft.azure.storage.blob.CloudBlobClient; +import com.microsoft.azure.storage.blob.CloudBlob; +import com.microsoft.azure.storage.CloudStorageAccount; +import com.microsoft.rest.LogLevel; + +import org.joda.time.DateTime; + +public class AudioAnalyzer { + private static final String AUDIO_ANALYZER_TRANSFORM_NAME = "MyAudioAnalyzerTransformName"; + private static final String INPUT_MP4_RESOURCE = "video/ignite.mp4"; + private static final String OUTPUT_FOLDER_NAME = "Output"; + + public static void main(String[] args) { + // Please make sure you have set configurations in resources/conf/appsettings.json + ConfigWrapper config = new ConfigWrapper(); + runAnalyzer(config); + + config.close(); + System.exit(0); + } + + /** + * Run the sample. + * @param config This param is of type ConfigWrapper, which reads values from a + * local configuration file. + */ + private static void runAnalyzer(ConfigWrapper config) { + // Connect to media services, please see https://docs.microsoft.com/en-us/azure/media-services/latest/configure-connect-java-howto + // for details. + ApplicationTokenCredentials credentials = new ApplicationTokenCredentials(config.getAadClientId(), + config.getAadTenantId(), config.getAadSecret(), AzureEnvironment.AZURE); + credentials.withDefaultSubscriptionId(config.getSubscriptionId()); + + // Get MediaManager, the entry point to Azure Media resource management. + MediaManager manager = MediaManager.configure().withLogLevel(LogLevel.BODY_AND_HEADERS) + .authenticate(credentials, credentials.defaultSubscriptionId()); + // Signed in. + + // Create a unique suffix so that we don't have name collisions if you run the sample + // multiple times without cleaning up. + UUID uuid = UUID.randomUUID(); + String uniqueness = uuid.toString(); + String jobName = "job-" + uniqueness; + String outputAssetName = "output-" + uniqueness; + String inputAssetName = "input-" + uniqueness; + + Scanner scanner = new Scanner(System.in); + + try { + // Create a preset with audio insights. + Preset preset = new VideoAnalyzerPreset().withInsightsToExtract(InsightsType.AUDIO_INSIGHTS_ONLY) + .withAudioLanguage("en-US"); + + // Ensure that you have the desired encoding Transform. This is really a one + // time setup operation. + Transform audioAnalyzerTransform = getOrCreateTransform(manager, config.getResourceGroup(), + config.getAccountName(), AUDIO_ANALYZER_TRANSFORM_NAME, preset); + + // Create a new input Asset and upload the specified local video file into it. + createInputAsset(manager, config.getResourceGroup(), config.getAccountName(), inputAssetName, + INPUT_MP4_RESOURCE); + + // Output from the encoding Job must be written to an Asset, so let's create one + Asset outputAsset = createOutputAsset(manager, config.getResourceGroup(), config.getAccountName(), + outputAssetName); + + Job job = submitJob(manager, config.getResourceGroup(), config.getAccountName(), + audioAnalyzerTransform.name(), jobName, inputAssetName, outputAsset.name()); + + long startedTime = System.currentTimeMillis(); + + EventProcessorHost eventProcessorHost = null; + try { + // First we will try to process Job events through Event Hub in real-time. If this fails for any reason, + // we will fall-back on polling Job status instead. + System.out.println("Creating an event processor host to process events from Event Hub..."); + + String storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=" + + config.getStorageAccountName() + + ";AccountKey=" + config.getStorageAccountKey(); + + // Cleanup storage container. We will config Event Hub to use the storage container configured in appsettings.json. + // All the blobs in /$Default will be deleted. + CloudStorageAccount account = CloudStorageAccount.parse(storageConnectionString); + CloudBlobClient client = account.createCloudBlobClient(); + CloudBlobContainer container = client.getContainerReference(config.getStorageContainerName()); + for (ListBlobItem item : container.listBlobs("$Default/", true)) { + if (item instanceof CloudBlob) { + CloudBlob blob = (CloudBlob) item; + blob.delete(); + } + } + + // Create a event processor host to process events from Event Hub. + eventProcessorHost = new EventProcessorHost( + EventProcessorHost.createHostName(null), + config.getEventHubName(), + "$Default", // The name of the consumer group to use when receiving from the Event Hub. DefaultConsumerGroupName is used here. + config.getEventHubConnectionString(), + storageConnectionString, + config.getStorageContainerName() + ); + + Object monitor = new Object(); + CompletableFuture registerResult = eventProcessorHost + .registerEventProcessorFactory(new MediaServicesEventProcessorFactory(jobName, monitor), EventProcessorOptions .getDefaultOptions()); + registerResult.get(); + + // Define a task to wait for the job to finish. + Callable jobTask = () -> { + synchronized(monitor) { + monitor.wait(); + } + return "Job"; + }; + + // Define another task + Callable timeoutTask = () -> { + TimeUnit.MINUTES.sleep(30); + return "Timeout"; + }; + + ExecutorService executor = Executors.newFixedThreadPool(2); + List> tasks = Arrays.asList(jobTask, timeoutTask); + + String result = executor.invokeAny(tasks); + if (result.equalsIgnoreCase("Job")) { + // Job finished. Shutdown timeout. + executor.shutdownNow(); + } + else { + // Timeout happened. Switch to polling method. + synchronized(monitor) { + monitor.notify(); + } + + throw new Exception("Timeout happened."); + } + + // Get the latest status of the job. + job = manager.jobs().getAsync(config.getResourceGroup(), config.getAccountName(), AUDIO_ANALYZER_TRANSFORM_NAME, jobName).toBlocking().first(); + } + catch (Exception e) + { + // if Event Grid or Event Hub is not configured, We will fall-back on polling instead. + // 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. + job = waitForJobToFinish(manager, config.getResourceGroup(), config.getAccountName(), + AUDIO_ANALYZER_TRANSFORM_NAME, jobName); + } + finally { + if (eventProcessorHost != null) + { + System.out.println("Job final state received, unregistering event processor..."); + + // Disposes of the Event Processor Host. + eventProcessorHost.unregisterEventProcessor(); + System.out.println(); + } + } + + 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(); + + File outputFolder = new File(OUTPUT_FOLDER_NAME); + if (outputFolder.exists() && !outputFolder.isDirectory()) { + outputFolder = new File(OUTPUT_FOLDER_NAME + uniqueness); + } + + if (!outputFolder.exists()) { + outputFolder.mkdir(); + } + + // Download the result to output folder. + downloadOutputAsset(manager, config.getResourceGroup(), config.getAccountName(), outputAsset.name(), + outputFolder); + } + + System.out.println("Press ENTER to continue."); + scanner.nextLine(); + } catch (Exception e) { + System.out.println(e); + e.printStackTrace(); + } finally { + if (scanner != null) { + scanner.close(); + } + + cleanup(manager, config.getResourceGroup(), config.getAccountName(), AUDIO_ANALYZER_TRANSFORM_NAME, jobName, + inputAssetName, outputAssetName); + } + } + + /** + * If the specified transform exists, get that transform. If it does not + * exist, creates a new transform with the specified output. In this case, the + * output is set to encode a video using the encoding preset created earlier. + * + * @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 preset The preset. + * @return The transform found or created. + */ + private static Transform getOrCreateTransform(MediaManager manager, String resourceGroup, String accountName, + String transformName, Preset preset) { + Transform transform; + try { + // Does a Transform already exist with the desired name? Assume that an existing + // Transform with the desired name + transform = manager.transforms().getAsync(resourceGroup, accountName, transformName).toBlocking().first(); + } catch (NoSuchElementException e) { + transform = null; // In case an exception is thrown + } + + if (transform == null) { + TransformOutput transformOutput = new TransformOutput().withPreset(preset); + List outputs = new ArrayList(); + outputs.add(transformOutput); + + // Create the Transform with the outputs defined above + System.out.println("Creating a transform..."); + transform = manager.transforms().define(transformName) + .withExistingMediaservice(resourceGroup, accountName) + .withOutputs(outputs) + .create(); + } + + return transform; + } + + /** + * 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 asset name. + * @param InputMP4FileName The file you want to upload into the asset. + * @return The asset. + */ + private static Asset createInputAsset(MediaManager manager, String resourceGroupName, String accountName, + String assetName, String videoResource) throws Exception { + // In this example, we are assuming that the asset name is unique. + // 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. + System.out.println("Creating an input asset..."); + Asset asset = manager.assets().define(assetName).withExistingMediaservice(resourceGroupName, accountName) + .create(); + + ListContainerSasInput parameters = new ListContainerSasInput() + .withPermissions(AssetContainerPermission.READ_WRITE).withExpiryTime(DateTime.now().plusHours(4)); + + // 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. + AssetContainerSas response = manager.assets() + .listContainerSasAsync(resourceGroupName, accountName, assetName, parameters).toBlocking().first(); + + URI sasUri = new URI(response.assetContainerSasUrls().get(0)); + + // Use Storage API to get a reference to the Asset container. + // That was created by calling Asset's create() method. + CloudBlobContainer container = new CloudBlobContainer(sasUri); + + String fileToUpload = AudioAnalyzer.class.getClassLoader().getResource(videoResource).getPath(); + File file = new File(fileToUpload); + CloudBlockBlob blob = container.getBlockBlobReference(file.getName()); + + // Use Storage API to upload the file into the container in storage. + System.out.println("Uploading a media file to the asset..."); + blob.uploadFromFile(fileToUpload); + + return asset; + } + + /** + * Creates an output asset. The output from the encoding Job must be written to + * an Asset. + * + * @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 output asset name. + * @return + */ + private static Asset createOutputAsset(MediaManager manager, String resourceGroupName, String accountName, + String assetName) { + // In this example, we are assuming that the asset name is unique. + System.out.println("Creating an output asset..."); + Asset outputAsset = manager.assets().define(assetName).withExistingMediaservice(resourceGroupName, accountName) + .create(); + + return outputAsset; + } + + /** + * Submits a request to Media Services to apply the specified Transform to a + * given input video. + * + * @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 transformName The name of the transform. + * @param jobName The (unique) name of the job. + * @param inputAssetName The name of the input asset. + * @param outputAssetName The (unique) name of the output asset that will + * store the result of the encoding job. + * @return The job created + */ + private static Job submitJob(MediaManager manager, String resourceGroupName, String accountName, + String transformName, String jobName, String inputAssetName, String outputAssetName) { + JobInput jobInput = new JobInputAsset().withAssetName(inputAssetName); + + // Call Media Services API to create a JobOutput and add it to a list. + JobOutput output = new JobOutputAsset().withAssetName(outputAssetName); + List jobOutputs = new ArrayList<>(); + jobOutputs.add(output); + + // Call Media Services API to create the job. + System.out.println("Creating a job..."); + Job job = manager.jobs().define(jobName) + .withExistingTransform(resourceGroupName, 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 you submitted. + * @return The job object. + */ + private static Job waitForJobToFinish(MediaManager manager, String resourceGroup, String accountName, + String transformName, String jobName) { + final int SLEEP_INTERVAL = 60 * 1000; // 1 minute. + + Job job = null; + boolean exit = false; + + do { + job = manager.jobs().getAsync(resourceGroup, accountName, transformName, jobName).toBlocking().first(); + + 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; + } + + /** + * Downloads the results from the specified output asset, so you can see what you get. + * + * @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 output asset. + * @param outputFolder The name of the folder into which to download the results. + * @throws URISyntaxException + * @throws StorageException + * @throws IOException + */ + private static void downloadOutputAsset(MediaManager manager, String resourceGroup, String accountName, + String assetName, File outputFolder) throws URISyntaxException, StorageException, IOException { + final int LIST_BLOBS_SEGMENT_MAX_RESULT = 5; + + // Specify read permission and 1 hour expiration time for the SAS URL. + ListContainerSasInput parameters = new ListContainerSasInput() + .withPermissions(AssetContainerPermission.READ) + .withExpiryTime(DateTime.now().plusHours(1)); + + // Call Media Services API to get SAS URLs. + AssetContainerSas assetContainerSas = manager.assets() + .listContainerSasAsync(resourceGroup, accountName, assetName, parameters) + .toBlocking().first(); + + // Use Storage API to get a reference to the Asset container. + URI containerSasUrl = new URI(assetContainerSas.assetContainerSasUrls().get(0)); + CloudBlobContainer container = new CloudBlobContainer(containerSasUrl); + + File directory = new File(outputFolder, assetName); + directory.mkdirs(); + + System.out.println("Downloading output results to " + directory.getPath() + "..."); + System.out.println(); + + // A continuation token for listing operations. Continuation tokens are used in methods that return a ResultSegment object. + ResultContinuation continuationToken = null; + + do { + ResultSegment segment = container.listBlobsSegmented(null, true, + EnumSet.noneOf(BlobListingDetails.class), LIST_BLOBS_SEGMENT_MAX_RESULT, continuationToken, null, + null); + + for (ListBlobItem blobItem : segment.getResults()) { + if (blobItem instanceof CloudBlockBlob) { + CloudBlockBlob blob = (CloudBlockBlob) blobItem; + File downloadTo = new File(directory, blob.getName()); + + // Use Storage API to download the file. + blob.downloadToFile(downloadTo.getPath()); + } + } + continuationToken = segment.getContinuationToken(); + } while (continuationToken != null); + + System.out.println("Downloading completed."); + System.out.println("Please check the result files in " + directory.getPath() + "."); + System.out.println(); + } + + /** + * 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 inputAssetName The input asset name. + * @param outputAssetName The output asset name. + */ + private static void cleanup(MediaManager manager, String resourceGroupName, String accountName, + String transformName, String jobName, String inputAssetName, String outputAssetName) { + if (manager == null) { + return; + } + + manager.jobs().deleteAsync(resourceGroupName, accountName, transformName, jobName).await(); + manager.assets().deleteAsync(resourceGroupName, accountName, inputAssetName).await(); + manager.assets().deleteAsync(resourceGroupName, accountName, outputAssetName).await(); + } +} + diff --git a/AudioAnalytics/AudioAnalyzer/src/main/java/sample/ConfigWrapper.java b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/ConfigWrapper.java new file mode 100644 index 0000000..0c7c1b7 --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/ConfigWrapper.java @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package sample; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +/** + * 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. + */ +public class ConfigWrapper { + private static final String AAD_CLIENT_ID = "AadClientId"; + private static final String AAD_ENDPOINT = "AadEndpoint"; + private static final String AAD_SECRET = "AadSecret"; + private static final String AAD_TENANT_ID = "AadTenantId"; + private static final String ACCOUNT_NAME = "AccountName"; + private static final String ARM_AAD_AUDIENCE = "ArmAadAudience"; + private static final String ARM_ENDPOINT = "ArmEndpoint"; + private static final String REGION = "Region"; + private static final String RESOURCE_GROUP = "ResourceGroup"; + private static final String SUBSCRIPTION_ID = "SubscriptionId"; + private static final String CONF_JSON = "conf/appsettings.json"; + private static final String NAMESPACE_NAME = "NamespaceName"; + private static final String EVENT_HUB_CONNECTION_STRING = "EventHubConnectionString"; + private static final String EVENT_HUB_NAME = "EventHubName"; + private static final String STORAGE_CONTAINER_NAME = "StorageContainerName"; + private static final String STORAGE_ACCOUNT_NAME = "StorageAccountName"; + private static final String STORAGE_ACCOUNT_KEY = "StorageAccountKey"; + + 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); + } + + try { + inStream.close(); + } catch (IOException e) { } + jsonObject = (JSONObject) obj; + } + + public void close() { + try { + if (isReader != null) { + isReader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public String getAadClientId() { + return (String)jsonObject.get(AAD_CLIENT_ID); + } + + public String getAadEndpoint() { + return (String)jsonObject.get(AAD_ENDPOINT); + } + + public String getAadSecret() { + return (String)jsonObject.get(AAD_SECRET); + } + + public String getAadTenantId() { + return (String)jsonObject.get(AAD_TENANT_ID); + } + + public String getAccountName() { + return (String)jsonObject.get(ACCOUNT_NAME); + } + + public String getArmAadAudience() { + return (String)jsonObject.get(ARM_AAD_AUDIENCE); + } + + public String getArmEndpoint() { + return (String)jsonObject.get(ARM_ENDPOINT); + } + + public String getRegion() { + return (String)jsonObject.get(REGION); + } + + public String getResourceGroup() { + return (String)jsonObject.get(RESOURCE_GROUP); + } + + public String getSubscriptionId() { + return (String)jsonObject.get(SUBSCRIPTION_ID); + } + + public String getNamespaceName() { + return (String)jsonObject.get(NAMESPACE_NAME); + } + + public String getEventHubConnectionString() { + return (String)jsonObject.get(EVENT_HUB_CONNECTION_STRING); + } + + public String getEventHubName() { + return (String)jsonObject.get(EVENT_HUB_NAME); + } + + public String getStorageContainerName() { + return (String)jsonObject.get(STORAGE_CONTAINER_NAME); + } + + public String getStorageAccountName() { + return (String)jsonObject.get(STORAGE_ACCOUNT_NAME); + } + + public String getStorageAccountKey() { + return (String)jsonObject.get(STORAGE_ACCOUNT_KEY); + } +} diff --git a/AudioAnalytics/AudioAnalyzer/src/main/java/sample/MediaServicesEventProcessor.java b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/MediaServicesEventProcessor.java new file mode 100644 index 0000000..d30b1ee --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/MediaServicesEventProcessor.java @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package sample; + +import com.microsoft.azure.eventprocessorhost.IEventProcessor; +import com.microsoft.azure.eventprocessorhost.PartitionContext; +import com.microsoft.azure.eventhubs.EventData; +import com.microsoft.azure.eventprocessorhost.CloseReason; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.JSONArray; + +/** + * Implementation of IEventProcessor to handle events from Event Hub. + */ +public class MediaServicesEventProcessor implements IEventProcessor { + private final Object MONITOR; + private final String JOB_NAME; + private final String LIVE_EVENT_NAME; + + public MediaServicesEventProcessor(String jobName, Object monitor, String liveEventName) + { + if (jobName != null) { + this.JOB_NAME = jobName.replaceAll("-", ""); + } + else { + this.JOB_NAME = null; + } + + this.MONITOR = monitor; + + if (liveEventName != null) { + this.LIVE_EVENT_NAME = liveEventName.replaceAll("-", ""); + } + else { + this.LIVE_EVENT_NAME = null; + } + } + + public MediaServicesEventProcessor() { + this.JOB_NAME = null; + this.MONITOR = null; + this.LIVE_EVENT_NAME = null; + } + + /** + * OnOpen is called when a new event processor instance is created by the host. + */ + @Override + public void onOpen(PartitionContext context) throws Exception + { + System.out.println("Partition " + context.getPartitionId() + " is opening"); + } + + /** + * OnClose is called when an event processor instance is being shut down. + */ + @Override + public void onClose(PartitionContext context, CloseReason reason) throws Exception + { + System.out.println("Partition " + context.getPartitionId() + " is closing for reason " + reason.toString()); + } + + /** + * onError is called when an error occurs in EventProcessorHost code that is tied to this partition, such as a receiver failure. + */ + @Override + public void onError(PartitionContext context, Throwable error) + { + System.out.println("Partition " + context.getPartitionId() + " onError: " + error.toString()); + } + + /** + * onEvents is called when events are received on this partition of the Event Hub. + */ + @Override + public void onEvents(PartitionContext context, Iterable events) throws Exception + { + for (EventData eventData: events) { + printEvent(context, eventData); + } + + context.checkpoint().get(); + } + + /** + * Parse and print Media Services events. + * @param context partition-related information. + * @param eventData Event Hub event data. + */ + private void printEvent(PartitionContext context, EventData eventData) { + try + { + String data = new String(eventData.getBytes(), "UTF8"); + JSONParser parser = new JSONParser(); + Object obj = parser.parse(data); + if (obj instanceof JSONArray) { + JSONArray jArr = (JSONArray)obj; + for (Object element: jArr) { + if (element instanceof JSONObject) { + JSONObject jObj = (JSONObject)element; + String eventType = (String)jObj.get("eventType"); + String subject = (String)jObj.get("subject"); + String eventName = subject.replaceFirst("^.*/", "").replaceAll("-", ""); + + // Only these events from registered job or live event. + if (!eventName.equals(JOB_NAME) && !eventName.equals(LIVE_EVENT_NAME)) + { + return; + } + + JSONObject jobOrLiveEventData = (JSONObject)jObj.get("data"); + switch (eventType) { + // Job state change events + case "Microsoft.Media.JobStateChange": + case "Microsoft.Media.JobScheduled": + case "Microsoft.Media.JobProcessing": + case "Microsoft.Media.JobCanceling": + case "Microsoft.Media.JobFinished": + case "Microsoft.Media.JobCanceled": + case "Microsoft.Media.JobErrored": + System.out.println("Job state changed for JobId: " + eventName + + " PreviousState: " + jobOrLiveEventData.get("previousState") + + ", State: " + jobOrLiveEventData.get("state")); + if (eventType.equals("Microsoft.Media.JobFinished") || eventType.equals("Microsoft.Media.JobCanceled") || + eventType.equals("Microsoft.Media.JobErrored")) + { + // Job finished, send a message. + if (MONITOR != null) + { + synchronized(MONITOR) { + MONITOR.notify(); + } + } + } + break; + + // Job output state change events + case "Microsoft.Media.JobOutputStateChange": + case "Microsoft.Media.JobOutputScheduled": + case "Microsoft.Media.JobOutputProcessing": + case "Microsoft.Media.JobOutputCanceling": + case "Microsoft.Media.JobOutputFinished": + case "Microsoft.Media.JobOutputCanceled": + case "Microsoft.Media.JobOutputErrored": + JSONObject outputObj = (JSONObject)jobOrLiveEventData.get("output"); + System.out.println("Job output state changed for JobId:" + eventName + + " PreviousState: " + jobOrLiveEventData.get("previousState") + + ", State: " + outputObj.get("state") + " Progress: " + outputObj.get("progress") + "%"); + break; + + // Job output progress event + case "Microsoft.Media.JobOutputProgress": + System.out.println("Job output progress changed for JobId: " + eventName + + " Progress: " + jobOrLiveEventData.get("progress") + "%"); + break; + + // LiveEvent Stream-level events + case "Microsoft.Media.LiveEventConnectionRejected": + System.out.println("LiveEvent connection rejected. IngestUrl: " + jobOrLiveEventData.get("ingestUrl") + + " StreamId: " + jobOrLiveEventData.get("streamId") + + " EncoderIp: " + jobOrLiveEventData.get("encoderIp") + + " EncoderPort: " + jobOrLiveEventData.get("encoderPort")); + break; + + case "Microsoft.Media.LiveEventEncoderConnected": + System.out.println("LiveEvent encoder connected. IngestUrl: " + jobOrLiveEventData.get("ingestUrl") + + " StreamId: " + jobOrLiveEventData.get("streamId") + + " EncoderIp: " + jobOrLiveEventData.get("encoderIp") + + " EncoderPort: " + jobOrLiveEventData.get("encoderPort")); + break; + + case "Microsoft.Media.LiveEventEncoderDisconnected": + System.out.println("LiveEvent encoder disconnected. IngestUrl: " + jobOrLiveEventData.get("ingestUrl") + + " StreamId: " + jobOrLiveEventData.get("streamId") + + " EncoderIp: " + jobOrLiveEventData.get("encoderIp") + + " EncoderPort: " + jobOrLiveEventData.get("encoderPort")); + break; + + // LiveEvent Track-level events + case "Microsoft.Media.LiveEventIncomingDataChunkDropped": + System.out.println("LiveEvent data chunk dropped. LiveEventId: " + eventName + + " ResultCode: " + jobOrLiveEventData.get("resultCode")); + break; + + case "Microsoft.Media.LiveEventIncomingStreamReceived": + System.out.println("LiveEvent incoming stream received. IngestUrl: " + jobOrLiveEventData.get("ingestUrl") + + " EncoderIp: " + jobOrLiveEventData.get("encoderIp") + + " EncoderPort: " + jobOrLiveEventData.get("encoderPort")); + break; + + case "Microsoft.Media.LiveEventIncomingStreamsOutOfSync": + System.out.println("LiveEvent incoming audio and video streams are out of sync. LiveEventId: " + eventName); + break; + + case "Microsoft.Media.LiveEventIncomingVideoStreamsOutOfSync": + System.out.println("LiveEvent incoming video streams are out of sync. LiveEventId: " + eventName); + break; + + case "Microsoft.Media.LiveEventIngestHeartbeat": + System.out.println("LiveEvent ingest heart beat. TrackType: " + jobOrLiveEventData.get("trackType") + + " State: " + jobOrLiveEventData.get("state") + + " Healthy: " + jobOrLiveEventData.get("healthy")); + break; + + case "Microsoft.Media.LiveEventTrackDiscontinuityDetected": + System.out.println("LiveEvent discontinuity in the incoming track detected. LiveEventId: " + eventName + + " TrackType: " + jobOrLiveEventData.get("trackType") + + " Discontinuity gap: " + jobOrLiveEventData.get("discontinuityGap")); + break; + } + } + } + } + } + catch (Exception e) + { + System.out.println("Processing failed for an event: " + e.toString()); + } + } +} diff --git a/AudioAnalytics/AudioAnalyzer/src/main/java/sample/MediaServicesEventProcessorFactory.java b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/MediaServicesEventProcessorFactory.java new file mode 100644 index 0000000..782a8d3 --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/src/main/java/sample/MediaServicesEventProcessorFactory.java @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package sample; + +import com.microsoft.azure.eventprocessorhost.IEventProcessorFactory; +import com.microsoft.azure.eventprocessorhost.PartitionContext; + +/** + * Factory class for creating custom EventProcessor. + */ +public class MediaServicesEventProcessorFactory implements IEventProcessorFactory { + private final Object MONITOR; + private final String JOB_NAME; + private final String LIVE_EVENT_NAME; + + public MediaServicesEventProcessorFactory(String jobName, Object monitor) { + this.JOB_NAME = jobName; + this.MONITOR = monitor; + this.LIVE_EVENT_NAME = null; + } + + public MediaServicesEventProcessorFactory(String liveEventName) { + this.JOB_NAME = null; + this.MONITOR = null; + this.LIVE_EVENT_NAME = liveEventName; + } + + @Override + public MediaServicesEventProcessor createEventProcessor(PartitionContext context) throws Exception { + return new MediaServicesEventProcessor(JOB_NAME, MONITOR, LIVE_EVENT_NAME); + } +} diff --git a/AudioAnalytics/AudioAnalyzer/src/main/resources/conf/appsettings.json b/AudioAnalytics/AudioAnalyzer/src/main/resources/conf/appsettings.json new file mode 100644 index 0000000..08110da --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/src/main/resources/conf/appsettings.json @@ -0,0 +1,19 @@ +{ + "_comment": "For security reasons, please do not check in this file to source control.", + "AadClientId": "00000000-0000-0000-0000-000000000000", + "AadEndpoint": "https://login.microsoftonline.com", + "AadSecret": "00000000-0000-0000-0000-000000000000", + "AadTenantId": "00000000-0000-0000-0000-000000000000", + "AccountName": "amsaccount", + "ArmAadAudience": "https://management.core.windows.net/", + "ArmEndpoint": "https://management.azure.com/", + "Region": "West US 2", + "ResourceGroup": "amsResourceGroup", + "SubscriptionId": "00000000-0000-0000-0000-000000000000", + "NamespaceName": "", + "EventHubConnectionString": "", + "EventHubName": "", + "StorageContainerName": "", + "StorageAccountName": "", + "StorageAccountKey": "" +} diff --git a/AudioAnalytics/AudioAnalyzer/src/main/resources/log4j.properties b/AudioAnalytics/AudioAnalyzer/src/main/resources/log4j.properties new file mode 100644 index 0000000..f6d5338 --- /dev/null +++ b/AudioAnalytics/AudioAnalyzer/src/main/resources/log4j.properties @@ -0,0 +1,10 @@ +# Define the root logger with appender file +log4j.rootLogger = DEBUG, FILE + +# Define the file appender +log4j.appender.FILE=org.apache.log4j.FileAppender +log4j.appender.FILE.File=logs/log.txt + +# Define the layout for file appender +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.conversionPattern=%m%n diff --git a/AudioAnalytics/AudioAnalyzer/src/main/resources/video/ignite.mp4 b/AudioAnalytics/AudioAnalyzer/src/main/resources/video/ignite.mp4 new file mode 100644 index 0000000..832b0c6 Binary files /dev/null and b/AudioAnalytics/AudioAnalyzer/src/main/resources/video/ignite.mp4 differ