Exportar (0) Imprimir
Expandir Tudo

Ingerir ativos em massa com o SDK do Media Services para .NET

Atualizado: agosto de 2014

O carregamento de grandes arquivos de ativo pode ser um afunilamento durante a criação de ativos. A ingestão de ativos em massa ou "ingestão em massa" envolve separar a criação do ativo do processo de carregamento. Para usar a abordagem de ingestão em massa, cria um manifesto (IngestManifest) que descreve o ativo e os seus arquivos associados. Em seguida, use o método de carregamento de sua escolha para carregar os arquivos associados no contêiner blob do manifesto. O Serviços de Mídia do Microsoft Azure observa o contêiner blob associado ao manifesto. Uma vez que o arquivo é carregado no contêiner blob, o Serviços de Mídia do Microsoft Azure conclui a criação do ativo com base na configuração do ativo no manifesto (IngestManifestAsset).

Este tópico descreve a ingestão de ativos em massa usando o SDK para .NET do Serviços de Mídia do Microsoft Azure. Para obter mais informações sobre a ingestão de ativos em massa usando a API REST, consulte Ingestão de ativos em massa com a API REST.

noteObservação
O Media Services utiliza o valor da propriedade IAssetFile.Name ao criar URLs para o conteúdo de streaming (por exemplo, http://{WAMSAccount}.origin.mediaservices.windows.net/{GUID}/{IAssetFile.Name}/streamingParameters.) Por esse motivo, a codificação por percentual não é permitida. O valor da propriedade Name não pode conter qualquer um dos seguintes caracteres reservados codificados por percentual: !*'();:@&=+$,/?%#[]". Além disso, pode somente haver um ‘.’ para cada extensão de nome de arquivo.

ImportantImportante
Se você ainda não baixou e configurou o projeto, consulte as instruções no tópico introdutório intitulado Building Applications with the Media Services SDK for .NET. Cada exemplo de código neste tópico é ilustrado com um ou mais métodos no projeto baixado (o arquivo de solução para o projeto é o MediaServicesSDKSamples.sln). Para encontrar o código para um determinado exemplo, procure o nome do método exemplo no projeto baixado. Para executar um exemplo de código específico, chame o método associado com os parâmetros necessários.

O fluxo de trabalho básico para a ingestão em massa é dividido nas seguintes seções:

noteObservação
Para desenvolver aplicativos com o SDK do Serviços de Mídia do Microsoft Azure, primeiro você deve ter uma referência ao contexto de servidor do Serviços de Mídia, conforme descrito no tópico Conectar ao Media Services com o SDK do Media Services. Nos exemplos de código a seguir o objeto de contexto é representado pela variável chamada context.

O IngestManifest representa um conjunto de ativos a ser criado por meio de uma ingestão em massa junto com os seus arquivos de ativo associados. Para criar um novo IngestManifest chame o método Create exposto pela coleção IngestManifests no contexto do servidor. Este método irá criar um novo IngestManifest com o nome de manifesto que você fornecer.

Um código de exemplo completo é fornecido no final deste tópico, na seção Código de exemplo.

IIngestManifest manifest = context.IngestManifests.Create(name);

Crie os ativos que serão associados ao IngestManifest em massa. Esses ativos são criados usando a coleção Assets no contexto do servidor. Configure as opções de criptografia desejadas no ativo para a ingestão em massa.

Um código de exemplo completo é fornecido no final deste tópico, na seção Código de exemplo.

// Create the assets that will be associated with this bulk ingest manifest
IAsset destAsset1 = _context.Assets.Create(name + "_asset_1", AssetCreationOptions.None);
IAsset destAsset2 = _context.Assets.Create(name + "_asset_2", AssetCreationOptions.None);

Um IngestManifestAsset associa um Asset ao IngestManifest em massa para a ingestão em massa. Associa também os AssetFiles que farão parte de cada Asset. Para criar um IngestManifestAsset, use o método Create no contexto do servidor.

O exemplo a seguir demonstra o adicionamento de dois novos IngestManifestAssets que associam os dois ativos criados anteriormente ao manifesto de ingestão em massa. Cada IngestManifestAsset associa também um conjunto de arquivos que serão carregados para cada ativo durante a ingestão em massa. Os caminhos de arquivo fazem parte do projeto MediaServiceSDKSamples.

Um código de exemplo completo é fornecido no final deste tópico, na seção Código de exemplo.


string filename1 = _singleInputMp4Path;
string filename2 = _primaryFilePath;
string filename3 = _singleInputFilePath;

IIngestManifestAsset bulkAsset1 =  manifest.IngestManifestAssets.Create(destAsset1, new[] { filename1 });
IIngestManifestAsset bulkAsset2 =  manifest.IngestManifestAssets.Create(destAsset2, new[] { filename2, filename3 });

É possível usar qualquer aplicativo cliente de alta velocidade capaz de carregar os arquivos de ativo para o URI do contêiner de armazenamento blob fornecido pela propriedade BlobStorageUriForUpload do IngestManifest. Um notável serviço de carregamento de alta velocidade é o Aspera On Demand for Azure Application. Também é possível gravar código para carregar os arquivos de ativos conforme mostrado no seguinte exemplo de código.

static void UploadBlobFile(string destBlobURI, string filename)
{
    Task copytask = new Task(() =>
    {
        var storageaccount = new CloudStorageAccount(new StorageCredentials(_storageAccountName, _storageAccountKey), true);
        CloudBlobClient blobClient = storageaccount.CreateCloudBlobClient();
        CloudBlobContainer blobContainer = blobClient.GetContainerReference(destBlobURI);

        string[] splitfilename = filename.Split('\\');
        var blob = blobContainer.GetBlockBlobReference(splitfilename[splitfilename.Length - 1]);

        using (var stream = System.IO.File.OpenRead(filename))
            blob.UploadFromStream(stream);

        lock (consoleWriteLock)
        {
            Console.WriteLine("Upload for {0} completed.", filename);
        }
    });

    copytask.Start();
}

O código para carregar os arquivos de ativos do exemplo usado neste tópico é mostrado no seguinte exemplo de código.


UploadBlobFile(manifest.BlobStorageUriForUpload, filename1);
UploadBlobFile(manifest.BlobStorageUriForUpload, filename2);
UploadBlobFile(manifest.BlobStorageUriForUpload, filename3);

É possível determinar o progresso da ingestão em massa para todos os ativos associados a um IngestManifest sondando a propriedade Statistics do IngestManifest. Para atualizar as informações de progresso, você deve usar um novo contexto de servidor para cada vez que sondar a propriedade Statistics.

O exemplo a seguir demonstra a sondagem de um IngestManifest pelo seu Id.

Um código de exemplo completo é fornecido no final deste tópico, na seção Código de exemplo.

static void MonitorBulkManifest(string manifestID)
{
   bool bContinue = true;
   while (bContinue)
   {
      CloudMediaContext context = GetContext();
      IIngestManifest manifest = context.IngestManifests.Where(m => m.Id == manifestID).FirstOrDefault();

      if (manifest != null)
      {
         lock(consoleWriteLock)
         {
            Console.WriteLine("\nWaiting on all file uploads.");
            Console.WriteLine("PendingFilesCount  : {0}", manifest.Statistics.PendingFilesCount);
            Console.WriteLine("FinishedFilesCount : {0}", manifest.Statistics.FinishedFilesCount);
            Console.WriteLine("{0}% complete.\n", (float)manifest.Statistics.FinishedFilesCount / (float)(manifest.Statistics.FinishedFilesCount + manifest.Statistics.PendingFilesCount) * 100);

            if (manifest.Statistics.PendingFilesCount == 0)
            {
               Console.WriteLine("Completed\n");
               bContinue = false;
            }
         }

         if (manifest.Statistics.FinishedFilesCount < manifest.Statistics.PendingFilesCount)
            Thread.Sleep(60000);
      }
      else //=== Manifest is null ===//
         bContinue = false;
   }
}

O exemplo de código a seguir é derivado do projeto MediaServicesSDKSample que está disponível para download aqui: Building Applications with the Media Services SDK for .NET.

using System;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.MediaServices.Client;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Auth;
using System.Collections.Generic;
using System.Reflection;

namespace Microsoft.Samples.WindowsAzureMediaServicesSDK.BulkIngest
{
    class Program
    {
        private static CloudMediaContext _context = null;
        private static readonly string _supportFiles = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\..\..\supportFiles";
        private static readonly string _singleInputFilePath =
            Path.GetFullPath(_supportFiles + @"\multifile\interview2.wmv");
        private static readonly string _singleInputMp4Path =
            Path.GetFullPath(_supportFiles + @"\multifile\BigBuckBunny.mp4");
        private static readonly string _primaryFilePath =
            Path.GetFullPath(_supportFiles + @"\multifile\interview1.wmv");

        private static System.Object consoleWriteLock = new Object();

        private static readonly string _accountName =
            ConfigurationManager.AppSettings["MediaServicesAccountName"];
        private static readonly string _accountKey =
            ConfigurationManager.AppSettings["MediaServicesAccountKey"];

        // Media Services storage account credentials.
        private static readonly string _storageAccountName =
            ConfigurationManager.AppSettings["MediaServicesStorageAccountName"];
        private static readonly string _storageAccountKey =
            ConfigurationManager.AppSettings["MediaServicesStorageAccountKey"];

        static void Main(string[] args)
        {
            _context = GetContext();
            string manifestName = "TestManifest";

            CreateBulkIngestManifest(manifestName);
            ListIngestManifests();
            DeleteBulkManifest(manifestName);
        }


        static CloudMediaContext GetContext()
        {
            return new CloudMediaContext(_accountName, _accountKey);
        }

        static IIngestManifest CreateBulkIngestManifest(string name)
        {
            Console.WriteLine("\n===============================================");
            Console.WriteLine("========[ CREATE BULK INGEST MANIFEST ]========");
            Console.WriteLine("===============================================\n");


            IIngestManifest manifest = _context.IngestManifests.Create(name);

            IAsset destAsset1 = _context.Assets.Create(name + "_asset_1", AssetCreationOptions.None);
            IAsset destAsset2 = _context.Assets.Create(name + "_asset_2", AssetCreationOptions.None);

            string filename1 = _singleInputMp4Path;
            string filename2 = _primaryFilePath;
            string filename3 = _singleInputFilePath;

            //=== Preently, each asset filename uploaded must be unique for an individual Bulk ingest manifest. So two assets can not have ===//
            //=== the same asset filename or an exception will be thrown for duplicate filename.                                           ===//
            IIngestManifestAsset bulkAsset1 = manifest.IngestManifestAssets.Create(destAsset1, new[] { filename1 });
            IIngestManifestAsset bulkAsset2 = manifest.IngestManifestAssets.Create(destAsset2, new[] { filename2, filename3 });

            ListIngestManifests(manifest.Id);

            Console.WriteLine("\n===============================================");
            Console.WriteLine("===[ BULK INGEST MANIFEST MONITOR FILE COPY]===");
            Console.WriteLine("===============================================\n");

            UploadBlobFile(manifest.BlobStorageUriForUpload, filename1);
            UploadBlobFile(manifest.BlobStorageUriForUpload, filename2);
            UploadBlobFile(manifest.BlobStorageUriForUpload, filename3);

            MonitorBulkManifest(manifest.Id);
            ListIngestManifests(manifest.Id);

            return manifest;
        }


        // Upload a file into Blob Storage
        static void UploadBlobFile(string destBlobURI, string filename)
        {
            Task copytask = new Task(() =>
            {
                var storageaccount = new CloudStorageAccount(new StorageCredentials(_storageAccountName, _storageAccountKey), true);
                CloudBlobClient blobClient = storageaccount.CreateCloudBlobClient();
                CloudBlobContainer blobContainer = blobClient.GetContainerReference(destBlobURI);

                string[] splitfilename = filename.Split('\\');
                var blob = blobContainer.GetBlockBlobReference(splitfilename[splitfilename.Length - 1]);

                using (var stream = System.IO.File.OpenRead(filename))
                    blob.UploadFromStream(stream);

                lock (consoleWriteLock)
                {
                    Console.WriteLine("Upload for {0} completed.", filename);
                }
            });

            copytask.Start();
        }

        static void DeleteBulkManifest(string name)
        {
            Console.WriteLine("\n===============================================");
            Console.WriteLine("=======[ DELETE BULK INGEST MANIFESTS ]========");
            Console.WriteLine("===============================================\n");

            var manifest = _context.IngestManifests.Where(c => c.Name == name).FirstOrDefault();
            DeleteBulkManifestAssets(manifest.Id);

            Console.WriteLine("Deleting Manifest...\n\tName : {0}\n\tManifest ID : {1}...", manifest.Name, manifest.Id);
            manifest.Delete();
            Console.WriteLine("Delete Complete.\n");
        }

        static void DeleteBulkManifestAssets(string manifestID)
        {
            Console.WriteLine("\n===============================================");
            Console.WriteLine("=====[ DELETE BULK INGEST MANIFEST ASSETS ]====");
            Console.WriteLine("===============================================\n");

            foreach (IIngestManifest manifest in _context.IngestManifests.Where(c => c.Id == manifestID))
            {
                Console.WriteLine("Deleting assets for manifest named : {0}...\n", manifest.Name);
                foreach (IIngestManifestAsset manifestAsset in manifest.IngestManifestAssets)
                {
                    foreach (ILocator locator in manifestAsset.Asset.Locators)
                    {
                        Console.WriteLine("Deleting locator {0} for asset {1}", locator.Path, manifestAsset.Asset.Id);
                        locator.Delete();
                    }
                    Console.WriteLine("Deleting asset {0}\n", manifestAsset.Asset.Id);
                    manifestAsset.Asset.Delete();
                }
            }
        }

        static void MonitorBulkManifest(string manifestID)
        {
            bool bContinue = true;
            while (bContinue)
            {
                //=== We need a new context here because IIngestManifestStatistics is considered an expensive ===//
                //=== property and not updated realtime for a context.                                        ===//
                CloudMediaContext context = GetContext();

                IIngestManifest manifest = context.IngestManifests.Where(m => m.Id == manifestID).FirstOrDefault();

                if (manifest != null)
                {
                    lock (consoleWriteLock)
                    {
                        Console.WriteLine("\nWaiting on all file uploads.");
                        Console.WriteLine("PendingFilesCount  : {0}", manifest.Statistics.PendingFilesCount);
                        Console.WriteLine("FinishedFilesCount : {0}", manifest.Statistics.FinishedFilesCount);
                        Console.WriteLine("{0}% complete.\n", (float)manifest.Statistics.FinishedFilesCount / (float)(manifest.Statistics.FinishedFilesCount + manifest.Statistics.PendingFilesCount) * 100);


                        if (manifest.Statistics.PendingFilesCount == 0)
                        {
                            Console.WriteLine("Completed\n");
                            bContinue = false;
                        }
                    }

                    if (manifest.Statistics.FinishedFilesCount < manifest.Statistics.PendingFilesCount)
                    {
                        Thread.Sleep(60000);
                    }
                }
                else //=== Manifest is null ===//
                    bContinue = false;
            }
        }

        static IQueryable<IIngestManifest> ListIngestManifests(string manifestId = "")
        {
            CloudMediaContext context = GetContext();

            Console.WriteLine("\n===============================================");
            Console.WriteLine("===========[ BULK INGEST MANIFESTS ]===========");
            Console.WriteLine("===============================================\n");

            IQueryable<IIngestManifest> manifests = null;

            //=== If an Id is supplied, list the manifest with that Id. Otherwise, list all manifests ===//
            if (manifestId == "")
                manifests = context.IngestManifests;
            else
                manifests = context.IngestManifests.Where(m => m.Id == manifestId);

            foreach (IIngestManifest manifest in manifests)
            {
                Console.WriteLine("Manifest Name  : {0}", manifest.Name);
                Console.WriteLine("Manifest State : {0}", manifest.State.ToString());
                Console.WriteLine("Manifest Id    : {0}", manifest.Id);
                Console.WriteLine("Manifest Last Modified      : {0}", manifest.LastModified.ToLocalTime().ToString());
                Console.WriteLine("Manifest PendingFilesCount  : {0}", manifest.Statistics.PendingFilesCount);
                Console.WriteLine("Manifest FinishedFilesCount : {0}", manifest.Statistics.FinishedFilesCount);
                Console.WriteLine("Manifest BLOB URI : {0}\n", manifest.BlobStorageUriForUpload);

                foreach (IIngestManifestAsset manifestasset in manifest.IngestManifestAssets)
                {
                    Console.WriteLine("\tAsset Name    : {0}", manifestasset.Asset.Name);
                    Console.WriteLine("\tAsset ID      : {0}", manifestasset.Asset.Id);
                    Console.WriteLine("\tAsset Options : {0}", manifestasset.Asset.Options.ToString());
                    Console.WriteLine("\tAsset State   : {0}", manifestasset.Asset.State.ToString());
                    Console.WriteLine("\tAsset Files....");

                    foreach (IIngestManifestFile assetfile in manifestasset.IngestManifestFiles)
                    {
                        Console.WriteLine("\t\t{0}\n\t\tFile State : {1}\n", assetfile.Name, assetfile.State.ToString());
                    }
                    Console.WriteLine("");
                }
            }

            return manifests;
        }
    }
}

Exemplo de arquivo App.Config

ImportantImportante
Antes de executar a amostra, é necessário adicionar valores para MediaServicesAccountName, MediaServicesAccountKey, MediaServicesStorageAccountName e MediaServicesStorageAccountKey. Para encontrar esses valores, vá para o Portal do Azure, selecione sua conta do Azure Media Service e clique no ícone Gerenciar Chaves. Faça o mesmo para a conta do Azure Storage.

<?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
  </startup>
  <appSettings>
    <add key="MediaServicesAccountName" value="Enter Your Azure Media Services Account Name " />
    <add key="MediaServicesAccountKey" value="Enter Your Azure Media Services Account Key " />
    <add key="MediaServicesStorageAccountName" value=" Enter the Storage Account Name used with Azure Media Services " />
    <add key="MediaServicesStorageAccountKey" value=" Enter the Storage Account Key used with Azure Media Services " />
  </appSettings>
</configuration>

Saída de exemplo do código de exemplo.


===============================================
========[ CREATE BULK INGEST MANIFEST ]========
===============================================


===============================================
===========[ BULK INGEST MANIFESTS ]===========
===============================================

Manifest Name  : TestManifest
Manifest State : Activating
Manifest Id    : nb:mid:UUID:47e2efdf-8ec8-6143-a4ac-bf43efba35c8
Manifest Last Modified      : 2/25/2013 1:50:09 PM
Manifest PendingFilesCount  : 3
Manifest FinishedFilesCount : 0
Manifest BLOB URI : https://mediasvcm13w6rsvm521t.blob.core.windows.net/manifest
-402dcea0-411c-48f2-90fc-805c63a92a72

        Asset Name    : TestManifest_asset_2
        Asset ID      : nb:cid:UUID:0dbfcaf9-e53c-4399-99c2-ba207b1821d9
        Asset Options : None
        Asset State   : Initialized
        Asset Files....
                interview2.wmv
                File State : Pending

                interview1.wmv
                File State : Pending


        Asset Name    : TestManifest_asset_1
        Asset ID      : nb:cid:UUID:7a7188b5-d39d-42de-b3c7-f418174b14f3
        Asset Options : None
        Asset State   : Initialized
        Asset Files....
                BigBuckBunny.mp4
                File State : Pending



===============================================
===[ BULK INGEST MANIFEST MONITOR FILE COPY]===
===============================================


Waiting on all file uploads.
PendingFilesCount  : 3
FinishedFilesCount : 0
0% complete.

Upload for c:\Projects\Media Services\BulkIngestTest\supportFiles\multifile\inte
rview2.wmv completed.
Upload for c:\Projects\Media Services\BulkIngestTest\supportFiles\multifile\BigB
uckBunny.mp4 completed.
Upload for c:\Projects\Media Services\BulkIngestTest\supportFiles\multifile\inte
rview1.wmv completed.

Waiting on all file uploads.
PendingFilesCount  : 0
FinishedFilesCount : 3
100% complete.

Completed


===============================================
===========[ BULK INGEST MANIFESTS ]===========
===============================================

Manifest Name  : TestManifest
Manifest State : Inactive
Manifest Id    : nb:mid:UUID:47e2efdf-8ec8-6143-a4ac-bf43efba35c8
Manifest Last Modified      : 2/25/2013 1:50:48 PM
Manifest PendingFilesCount  : 0
Manifest FinishedFilesCount : 3
Manifest BLOB URI : https://mediasvcm13w6rsvm521t.blob.core.windows.net/manifest
-402dcea0-411c-48f2-90fc-805c63a92a72

        Asset Name    : TestManifest_asset_2
        Asset ID      : nb:cid:UUID:0dbfcaf9-e53c-4399-99c2-ba207b1821d9
        Asset Options : None
        Asset State   : Initialized
        Asset Files....
                interview2.wmv
                File State : Finished

                interview1.wmv
                File State : Finished


        Asset Name    : TestManifest_asset_1
        Asset ID      : nb:cid:UUID:7a7188b5-d39d-42de-b3c7-f418174b14f3
        Asset Options : None
        Asset State   : Initialized
        Asset Files....
                BigBuckBunny.mp4
                File State : Finished



===============================================
===========[ BULK INGEST MANIFESTS ]===========
===============================================

Manifest Name  : TestManifest
Manifest State : Inactive
Manifest Id    : nb:mid:UUID:c44efbcc-8f6a-f94a-aa21-2d0c1166ecb7
Manifest Last Modified      : 2/25/2013 1:47:47 PM
Manifest PendingFilesCount  : 0
Manifest FinishedFilesCount : 0
Manifest BLOB URI : https://mediasvcm13w6rsvm521t.blob.core.windows.net/manifest
-f001a80c-3c37-4a31-8ffc-b4bfe98b56be

        Asset Name    : TestManifest_asset_1
        Asset ID      : nb:cid:UUID:350ceed6-086a-413d-9bb9-a8a2f07a60f5
        Asset Options : None
        Asset State   : Initialized
        Asset Files....

Manifest Name  : TestManifest
Manifest State : Inactive
Manifest Id    : nb:mid:UUID:47e2efdf-8ec8-6143-a4ac-bf43efba35c8
Manifest Last Modified      : 2/25/2013 1:50:48 PM
Manifest PendingFilesCount  : 0
Manifest FinishedFilesCount : 3
Manifest BLOB URI : https://mediasvcm13w6rsvm521t.blob.core.windows.net/manifest
-402dcea0-411c-48f2-90fc-805c63a92a72

        Asset Name    : TestManifest_asset_2
        Asset ID      : nb:cid:UUID:0dbfcaf9-e53c-4399-99c2-ba207b1821d9
        Asset Options : None
        Asset State   : Initialized
        Asset Files....
                interview2.wmv
                File State : Finished

                interview1.wmv
                File State : Finished


        Asset Name    : TestManifest_asset_1
        Asset ID      : nb:cid:UUID:7a7188b5-d39d-42de-b3c7-f418174b14f3
        Asset Options : None
        Asset State   : Initialized
        Asset Files....
                BigBuckBunny.mp4
                File State : Finished



===============================================
=======[ DELETE BULK INGEST MANIFESTS ]========
===============================================


===============================================
=====[ DELETE BULK INGEST MANIFEST ASSETS ]====
===============================================

Deleting assets for manifest named : TestManifest...

Deleting asset nb:cid:UUID:350ceed6-086a-413d-9bb9-a8a2f07a60f5

Deleting Manifest...
        Name : TestManifest
        Manifest ID : nb:mid:UUID:c44efbcc-8f6a-f94a-aa21-2d0c1166ecb7...
Delete Complete.


===============================================
=====[ DELETE BULK INGEST MANIFEST ASSETS ]====
===============================================

Deleting assets for manifest named : TestManifest...

Deleting asset nb:cid:UUID:0dbfcaf9-e53c-4399-99c2-ba207b1821d9

Deleting asset nb:cid:UUID:7a7188b5-d39d-42de-b3c7-f418174b14f3

Deleting Manifest...
        Name : TestManifest
        Manifest ID : nb:mid:UUID:47e2efdf-8ec8-6143-a4ac-bf43efba35c8...
Delete Complete.

Você está pronto para prosseguir para o próximo tópico: Empacotamento estático.

Consulte também

Mostrar:
© 2014 Microsoft