Tutorial: Análisis de vídeos con Media Services v3
¿Busca la documentación de Media Services, versión 2?
En este tutorial se muestra cómo analizar vídeos con Azure Media Services. Hay muchos escenarios en los que puede desear obtener información detallada sobre los vídeos grabados o el contenido de audio. Por ejemplo, para lograr una mayor satisfacción del cliente, las organizaciones pueden ejecutar el procesamiento de voz a texto para convertir las grabaciones de soporte técnico al cliente en un catálogo que permite búsquedas, con índices y paneles.
En este tutorial se muestra cómo realizar las siguientes acciones:
- Descargue la aplicación de ejemplo que se describe en el tema
- Examine el código que analiza el vídeo especificado.
- Ejecute la aplicación.
- Examine la salida.
- Limpieza de recursos.
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.
Cumplimiento, privacidad y seguridad
Como recordatorio importante, debe cumplir todas las leyes aplicables cuando use Azure Video Analyzer for Media (antes, Video Indexer). No debe usar Video Analyzer for Media ni ningún otro servicio de Azure de forma que infrinja los derechos de los demás. Antes de cargar vídeos que incluyan datos biométricos en el servicio Video Analyzer for Media para procesarlos y almacenarlos, debe tener todos los derechos apropiados (por ejemplo, todos los consentimientos adecuados) de las personas del vídeo. Para información sobre el cumplimiento, la privacidad y la seguridad de Video Analyzer for Media, consulte Términos de Cognitive Services de Azure. En lo que respecta a las obligaciones de privacidad de Microsoft y al control de los datos, consulte la declaración de privacidad, los Términos de los Servicios en Línea (OST) y el anexo de procesamiento de datos ("DPA") de Microsoft. Puede encontrar información adicional sobre la privacidad, como la retención, eliminación o destrucción de los datos, en los términos de OST y aquí. Con el uso de Video Analyzer for Media, acepta estar sujeto a los términos de Cognitive Services, OST, DPA y la declaración de privacidad.
Requisitos previos
- Instale Visual Studio Code para Windows, macOS o Linux o Visual Studio 2019 para Windows o Mac.
- Instale el SDK para .NET 5.0.
- Cree una cuenta de Media Services. Asegúrese de copiar los detalles de acceso a la API en formato JSON o de almacenar los valores necesarios para conectarse a la cuenta de Media Services en el formato de archivo .env que se usa en este ejemplo.
- Siga los pasos que se indican en Acceso a la API de Azure Media Services con la CLI de Azure y guarde las credenciales. Las necesitará para acceder a la API de este ejemplo, o bien deberá especificarlas en el formato de archivo .env.
Descarga y configuración del ejemplo
Clone un repositorio GitHub que contenga el ejemplo de .NET en la máquina mediante el siguiente comando:
git clone https://github.com/Azure-Samples/media-services-v3-dotnet-tutorials.git
El ejemplo se encuentra en la carpeta AnalyzeVideos.
Abra appsettings.json en el proyecto que ha descargado. Sustituya los valores por las credenciales que obtuvo de acceder a las API.
Nota
También puede usar el formato de archivo .env en la raíz del proyecto para establecer las variables de entorno solo una vez para todos los proyectos del repositorio de ejemplos de .NET. Solo tiene que copiar el archivo sample.env y rellenar la información que obtuvo de la página Acceso de API de Media Services en Azure Portal o la CLI de Azure. Cambie el nombre del archivo sample.env a solo .env para usarlo en todos los proyectos.
El archivo .gitignore ya está configurado para evitar su publicación en el repositorio bifurcado.
Examen del código que analiza el vídeo especificado
En esta sección se examinan las funciones definidas en el archivo Program.cs del proyecto AnalyzeVideos.
Este ejemplo realiza las acciones siguientes:
- Crea una transformación y un trabajo que analiza el vídeo.
- Crea un recurso de entrada y carga el vídeo en él. El recurso se usa como entrada del trabajo.
- Crea un recurso de salida que almacena la salida del trabajo.
- Envía el trabajo.
- Comprueba el estado del trabajo.
- Descarga los archivos resultantes de la ejecución del trabajo.
Empiece a usar las API de Media Services con el SDK de .NET.
Para empezar a usar las API de Media Services con. NET, debe crear un objeto AzureMediaServicesClient. Para crear el objeto, debe proporcionar las credenciales para que el cliente se conecte a Azure mediante Azure Active Directory. Otra opción es usar la autenticación interactiva, que se implementa en GetCredentialsInteractiveAuthAsync.
public static async Task<IAzureMediaServicesClient> CreateMediaServicesClientAsync(ConfigWrapper config, bool interactive = false)
{
ServiceClientCredentials credentials;
if (interactive)
credentials = await GetCredentialsInteractiveAuthAsync(config);
else
credentials = await GetCredentialsAsync(config);
return new AzureMediaServicesClient(config.ArmEndpoint, credentials)
{
SubscriptionId = config.SubscriptionId,
};
}
En el código que ha clonado al principio del artículo, la función GetCredentialsAsync crea el objeto ServiceClientCredentials en función de las credenciales proporcionadas en el archivo de configuración local (appsettings.json) o por medio del archivo de variables de entorno .env situado en la raíz del repositorio.
private static async Task<ServiceClientCredentials> GetCredentialsAsync(ConfigWrapper config)
{
// Use ConfidentialClientApplicationBuilder.AcquireTokenForClient to get a token using a service principal with symmetric key
var scopes = new[] { config.ArmAadAudience + "/.default" };
var app = ConfidentialClientApplicationBuilder.Create(config.AadClientId)
.WithClientSecret(config.AadSecret)
.WithAuthority(AzureCloudInstance.AzurePublic, config.AadTenantId)
.Build();
var authResult = await app.AcquireTokenForClient(scopes)
.ExecuteAsync()
.ConfigureAwait(false);
return new TokenCredentials(authResult.AccessToken, TokenType);
}
En el caso de la autenticación interactiva, la función GetCredentialsInteractiveAuthAsync crea el objeto ServiceClientCredentials en función de una autenticación interactiva y los parámetros de conexión proporcionados en el archivo de configuración local (appsettings.json) o a través del archivo de variables de entorno .env en la raíz del repositorio. En ese caso, AADCLIENTID y AADSECRET no son necesarios en el archivo de variables de entorno o de configuración.
private static async Task<ServiceClientCredentials> GetCredentialsInteractiveAuthAsync(ConfigWrapper config)
{
var scopes = new[] { config.ArmAadAudience + "/user_impersonation" };
// client application of Az Cli
string ClientApplicationId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46";
AuthenticationResult result = null;
IPublicClientApplication app = PublicClientApplicationBuilder.Create(ClientApplicationId)
.WithAuthority(AzureCloudInstance.AzurePublic, config.AadTenantId)
.WithRedirectUri("http://localhost")
.Build();
var accounts = await app.GetAccountsAsync();
try
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
try
{
result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
}
catch (MsalException maslException)
{
Console.Error.WriteLine($"ERROR: MSAL interactive authentication exception with code '{maslException.ErrorCode}' and message '{maslException.Message}'.");
}
}
catch (MsalException maslException)
{
Console.Error.WriteLine($"ERROR: MSAL silent authentication exception with code '{maslException.ErrorCode}' and message '{maslException.Message}'.");
}
return new TokenCredentials(result.AccessToken, TokenType);
}
Creación de un recurso de entrada y carga de un archivo local en él
La función CreateInputAsset crea un nuevo recurso de entrada y carga en él el archivo de vídeo local especificado. Este recurso se utiliza como entrada para el trabajo de codificación. En Media Services v3, la entrada a un trabajo puede ser un recurso, o puede ser contenido que se pone a disposición de la cuenta de Media Services a través de direcciones URL HTTPS. Para más información sobre cómo codificar desde una dirección URL HTTPS, consulte este artículo.
En Media Services v3, se utilizan las API de Azure Storage para cargar archivos. En el siguiente fragmento de código de .NET se muestra cómo hacerlo.
La función siguiente realiza estas acciones:
Crea un recurso.
Obtiene una dirección URL de SAS que se puede escribir para el contenedor de almacenamiento del recurso.
Si usa la función ListContainerSas de un recurso para obtener direcciones URL SAS, tenga en cuenta que la función devuelve varias URL de SAS, ya que hay dos claves para cada cuenta de almacenamiento. Las cuentas de almacenamiento tienen dos claves, ya que eso permite una rotación perfecta de las claves de cuenta de almacenamiento (por ejemplo, cambiar una mientras se usa la otra y, luego, empezar a usar la clave nueva y rotar la otra). La primera dirección URL de SAS representa clave de almacenamiento 1, mientras que la segunda representa clave de almacenamiento 2.
Carga el archivo en el contenedor de almacenamiento mediante la dirección URL de SAS.
private static async Task<Asset> CreateInputAssetAsync(
IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string assetName,
string fileToUpload)
{
// In this example, we are assuming that the asset name is unique.
//
// If you already have an asset with the desired name, use the Assets.Get method
// to get the existing asset. In Media Services v3, the Get method on entities returns null
// if the entity doesn't exist (a case-insensitive check on the name).
// 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 = await client.Assets.CreateOrUpdateAsync(resourceGroupName, accountName, assetName, new Asset());
// 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 exparation time for the SAS URL.
var response = await client.Assets.ListContainerSasAsync(
resourceGroupName,
accountName,
assetName,
permissions: AssetContainerPermission.ReadWrite,
expiryTime: DateTime.UtcNow.AddHours(4).ToUniversalTime());
var sasUri = new Uri(response.AssetContainerSasUrls.First());
// Use Storage API to get a reference to the Asset container
// that was created by calling Asset's CreateOrUpdate method.
BlobContainerClient container = new BlobContainerClient(sasUri);
BlobClient blob = container.GetBlobClient(Path.GetFileName(fileToUpload));
// Use Storage API to upload the file into the container in storage.
await blob.UploadAsync(fileToUpload);
return asset;
}
Creación de un recurso de salida para almacenar el resultado del trabajo
El recurso de salida almacena el resultado del trabajo. El proyecto define la función DownloadResults que descarga los resultados de este recurso de salida en la carpeta "output", para que se pueda ver lo que tiene.
private static async Task<Asset> CreateOutputAssetAsync(IAzureMediaServicesClient client, string resourceGroupName, string accountName, string assetName)
{
bool existingAsset = true;
Asset outputAsset;
try
{
// Check if an Asset already exists
outputAsset = await client.Assets.GetAsync(resourceGroupName, accountName, assetName);
}
catch (ErrorResponseException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
existingAsset = false;
}
Asset asset = new Asset();
string outputAssetName = assetName;
if (existingAsset)
{
// Name collision! In order to get the sample to work, let's just go ahead and create a unique asset name
// Note that the returned Asset can have a different name than the one specified as an input parameter.
// You may want to update this part to throw an Exception instead, and handle name collisions differently.
string uniqueness = $"-{Guid.NewGuid():N}";
outputAssetName += uniqueness;
Console.WriteLine("Warning – found an existing Asset with name = " + assetName);
Console.WriteLine("Creating an Asset with this name instead: " + outputAssetName);
}
return await client.Assets.CreateOrUpdateAsync(resourceGroupName, accountName, outputAssetName, asset);
}
Creación de una transformación y de un trabajo que analiza vídeos
Cuando se codifica o procesa contenido en Media Services, es un patrón común configurar los ajustes de codificación como una receta. Después, podría enviar un trabajo para aplicar esa receta a un vídeo. Al enviar nuevos trabajos en cada nuevo vídeo, está aplicando dicha receta a todos los vídeos de la biblioteca. Una receta en Media Services se llama transformación. Para más información, consulte Transformaciones y trabajos. En el ejemplo descrito en este tutorial se define una receta que analiza el vídeo especificado.
Transformación
Al crear una nueva instancia de la transformación, debe especificar qué desea originar como salida. TransformOutput es un parámetro necesario. Cada objeto TransformOutput contiene un valor preestablecido. El valor preestablecido describe las instrucciones paso a paso de las operaciones de procesamiento de vídeo o audio que se utilizarán para generar el objeto TransformOutput deseado. En este ejemplo, se utiliza el valor preestablecido VideoAnalyzerPreset y el idioma ("en-US") se pasa al constructor (new VideoAnalyzerPreset("en-US")). Este valor preestablecido permite extraer diversa información de audio y vídeo desde un vídeo. Puede usar el valor preestablecido de AudioAnalyzerPreset si necesita extraer diversa información de audio desde un vídeo.
Al crear una transformación, compruebe primero si ya existe una con el método Get, tal como se muestra en el código siguiente. En Media Services v3, los métodos Get en las entidades devuelven null si la entidad no existe (una comprobación sin distinción entre mayúsculas y minúsculas en el nombre).
private static async Task<Transform> GetOrCreateTransformAsync(IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string transformName,
Preset preset)
{
bool createTransform = false;
Transform transform = null;
try
{
// Does a transform already exist with the desired name? Assume that an existing Transform with the desired name
// also uses the same recipe or Preset for processing content.
transform = client.Transforms.Get(resourceGroupName, accountName, transformName);
}
catch (ErrorResponseException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
createTransform = true;
}
if (createTransform)
{
// Start by defining the desired outputs.
TransformOutput[] outputs = new TransformOutput[]
{
new TransformOutput(preset),
};
// Create the Transform with the output defined above
transform = await client.Transforms.CreateOrUpdateAsync(resourceGroupName, accountName, transformName, outputs);
}
return transform;
}
Trabajo
Como se mencionó anteriormente, el objeto Transform es la receta y un trabajo es la solicitud real a Media Services para aplicar que dicho objeto Transform a un determinado contenido de audio o vídeo de entrada. El trabajo especifica información como la ubicación del vídeo de entrada y la ubicación de la salida. Puede especificar la ubicación del vídeo mediante: direcciones URL HTTPS, direcciones URL de SAS o recursos que se encuentran en su cuenta de Media Services.
En este ejemplo, la entrada de trabajo es un vídeo local.
private static async Task<Job> SubmitJobAsync(IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string transformName,
string jobName,
JobInput jobInput,
string outputAssetName)
{
JobOutput[] jobOutputs =
{
new JobOutputAsset(outputAssetName),
};
// In this example, we are assuming that the job name is unique.
//
// If you already have a job with the desired name, use the Jobs.Get method
// to get the existing job. In Media Services v3, Get methods on entities returns null
// if the entity doesn't exist (a case-insensitive check on the name).
Job job = await client.Jobs.CreateAsync(
resourceGroupName,
accountName,
transformName,
jobName,
new Job
{
Input = jobInput,
Outputs = jobOutputs,
});
return job;
}
Espere a que el trabajo se complete
El trabajo tarda un tiempo en completarse. Cuando lo hace, desea recibir una notificación. Hay distintas opciones para obtener notificaciones sobre la realización del trabajo. La opción más sencilla (que se muestra aquí) consiste en utilizar el sondeo.
El sondeo no es un procedimiento recomendado para aplicaciones de producción debido a la posible latencia. El sondeo se puede limitar si se sobreutiliza en una cuenta. Los desarrolladores deben utilizar en su lugar Event Grid.
Event Grid está diseñado para una alta disponibilidad, un rendimiento consistente y una escala dinámica. Con Event Grid, sus aplicaciones pueden escuchar y reaccionar a eventos de casi todos los servicios de Azure y de orígenes personalizados. El control de eventos sencillo y reactivo basado en HTTP le ayuda a crear soluciones eficaces con filtrado y enrutamiento de eventos inteligente. Para más información, consulte Enrutamiento de eventos a un punto de conexión web personalizado.
El trabajo pasa normalmente por los siguientes estados: Programado, En cola, Procesando, Finalizado (el estado final). Si el trabajo ha encontrado un error, obtendrá el estado Error. Si el trabajo está en proceso de cancelación, obtendrá los mensajes Cancelando y Cancelado cuando haya terminado.
private static async Task<Job> WaitForJobToFinishAsync(IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string transformName,
string jobName)
{
const int SleepIntervalMs = 20 * 1000;
Job job;
do
{
job = await client.Jobs.GetAsync(resourceGroupName, accountName, transformName, jobName);
Console.WriteLine($"Job is '{job.State}'.");
for (int i = 0; i < job.Outputs.Count; i++)
{
JobOutput output = job.Outputs[i];
Console.Write($"\tJobOutput[{i}] is '{output.State}'.");
if (output.State == JobState.Processing)
{
Console.Write($" Progress (%): '{output.Progress}'.");
}
Console.WriteLine();
}
if (job.State != JobState.Finished && job.State != JobState.Error && job.State != JobState.Canceled)
{
await Task.Delay(SleepIntervalMs);
}
}
while (job.State != JobState.Finished && job.State != JobState.Error && job.State != JobState.Canceled);
return job;
}
Códigos de error de trabajo
Consulte Códigos de error.
Descarga del resultado del trabajo
La siguiente función descarga los resultados del recurso de salida en la carpeta "output", para que pueda examinar los resultados del trabajo.
private static async Task DownloadOutputAssetAsync(
IAzureMediaServicesClient client,
string resourceGroup,
string accountName,
string assetName,
string outputFolderName)
{
if (!Directory.Exists(outputFolderName))
{
Directory.CreateDirectory(outputFolderName);
}
AssetContainerSas assetContainerSas = await client.Assets.ListContainerSasAsync(
resourceGroup,
accountName,
assetName,
permissions: AssetContainerPermission.Read,
expiryTime: DateTime.UtcNow.AddHours(1).ToUniversalTime());
Uri containerSasUrl = new Uri(assetContainerSas.AssetContainerSasUrls.FirstOrDefault());
BlobContainerClient container = new BlobContainerClient(containerSasUrl);
string directory = Path.Combine(outputFolderName, assetName);
Directory.CreateDirectory(directory);
Console.WriteLine($"Downloading output results to '{directory}'...");
string continuationToken = null;
IList<Task> downloadTasks = new List<Task>();
do
{
var resultSegment = container.GetBlobs().AsPages(continuationToken);
foreach (Azure.Page<BlobItem> blobPage in resultSegment)
{
foreach (BlobItem blobItem in blobPage.Values)
{
var blobClient = container.GetBlobClient(blobItem.Name);
string filename = Path.Combine(directory, blobItem.Name);
downloadTasks.Add(blobClient.DownloadToAsync(filename));
}
// Get the continuation token and loop until it is empty.
continuationToken = blobPage.ContinuationToken;
}
} while (continuationToken != "");
await Task.WhenAll(downloadTasks);
Console.WriteLine("Download complete.");
}
Limpieza de los recursos en su cuenta de Media Services
Advertencia
Si ya no necesita los recursos, es mejor que los elimine, ya que, si no, se le facturarán.
Por lo general, debe limpiar todo excepto los objetos que piensa reutilizar (normalmente, reutilizará transformaciones y conservará objetos StreamingLocators). Si quiere que la cuenta esté limpia después de la experimentación, elimine los recursos que no tiene previsto reutilizar. Por ejemplo, el código siguiente elimina el recurso de trabajo y de salida:
Eliminación de recursos con código
private static async Task CleanUpAsync(
IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string transformName,
string jobName,
List<string> assetNames,
string contentKeyPolicyName = null
)
{
await client.Jobs.DeleteAsync(resourceGroupName, accountName, transformName, jobName);
foreach (var assetName in assetNames)
{
await client.Assets.DeleteAsync(resourceGroupName, accountName, assetName);
}
if (contentKeyPolicyName != null)
{
client.ContentKeyPolicies.Delete(resourceGroupName, accountName, contentKeyPolicyName);
}
}
También puede usar la CLI.
Eliminación de un grupo de recursos con la CLI
az group delete --name <your-resource-group-name>
Ejecutar la aplicación de ejemplo
Presione Ctrl+F5 para ejecutar la aplicación AnalyzeVideos.
Cuando se ejecuta el programa, el trabajo produce miniaturas para cada rostro que encuentra en el vídeo. También produce el archivo insights.json.
Examen de la salida
El archivo de salida de análisis de vídeos se denomina insights.json. Este archivo contiene información detallada sobre el vídeo. Puede encontrar la descripción de los elementos que se encuentran en el archivo json en el artículo Media intelligence (Inteligencia multimedia).
Subprocesamiento múltiple
Advertencia
Los SDK de Azure Media Services v3 no son seguros para subprocesos. Al trabajar con una aplicación que admite multithreading, debe generar un nuevo objeto AzureMediaServicesClient por subproceso.