Le Blog Amazon Web Services

Utiliser MediaInfo depuis une fonction Lambda

Les applications de traitement multimédia ont très souvent besoin d’informations sur la nature des codecs audio/vidéo ainsi que sur le format de fichier vidéo.
MediaInfo, avec son affichage simplifié et pertinent des données audio/vidéo, est un outil open source populaire qui permet de donner toutes ces précieuses informations.
Beaucoup de professionnels travaillant dans la vidéo, utilisent ce logiciel gratuit pour obtenir des métadonnées techniques sur les codecs audio/vidéo, la cadence d’images, le nombre de canaux audio, la durée, etc.

Cet article décrit comment utiliser MediaInfo comme fonction AWS Lambda afin que vous puissiez créer des applications serverless permettant une analyse rapide des métadonnées de fichiers vidéos.
A la suite de cet article, vous pourrez facilement intégrer une étape d’analyse vidéo dans vos workflows vidéo, que ce soit pour faire de l’analyse de type QC video ou pour préparer vos transcodages audio/vidéo ou bien d’autres applications.

Qu’est-ce que MediaInfo ?

MediaInfo est un outil très populaire pour les professionnels dans le domaine audiovisuel: du montage vidéo, au streaming et transcodage de fichiers. Il permet d’obtenir très rapidement les informations techniques dans un fichier audio ou vidéo, y compris comment ils sont encodés, les langues audio présentes, la taille de la résolution vidéo, etc.

Voici, un exemple de ce que MediaInfo fournit comme information (en mode Classique et mode “Texte”) :

mediaInfo - Information screenshot

La library MediaInfo peut être déployée sur AWS Lambda de deux manières :

  1. Comme une application de type “monolithe” avec tout le code applicatif écrit dans une seule fonction Lambda
  2. En séparant le code d’applicatif avec une couche AWS Lambda (layer) modulaire.

L’approche qui consiste à utiliser une couche AWS Lambda modulaire (layer) est plus séduisante car elle réduit la taille de la fonction Lambda et permet d’afficher plus de code applicatif directement dans la visionneuse de code Lambda, depuis la console AWS.

Vous trouverez, sur GitHub, comment déployer MediaInfo dans une seule fonction Lambda. Mais, dans cet article, nous détaillerons donc comment déployer MediaInfo en tant que couche AWS Lambda (layer).

Principe général

Les instructions qui suivent expliquent comment compiler le binaire MediaInfo à partir des sources pour en faire un package permettant la création d’une couche AWS Lambda. Cette couche AWS Lambda sera utilisée dans une fonction Lambda qui lit les balises de métadonnées d’un fichier vidéo stocké dans Amazon Simple Storage Service (Amazon S3).

Par simplicité, nous allons utiliser Docker pour compiler le binaire MediaInfo pour Amazon Linux. Une fois le binaire compilé, nous allons déployer et copier le mode compilé MediaInfo dans une couche AWS Lambda.
Toutes les fonctions Lambda pourront ainsi utiliser l’API MediaInfo, en chargeant depuis la couche AWS Lambda cette bibliothèque précompilée .

Le diagramme suivant illustre le fonctionnement des différents éléments AWS Lambda et la relation avec la libMediaInfo généré dans Docker :

Configuration étape par étape

  1. Installez Docker sur votre poste de travail.
  2. Créez les informations d’identification de configuration pour l’interface de ligne de commande AWS (reportez-vous au guide de l’utilisateur).
  3. Créez un rôle AWS Identity and Access Management (IAM) avec l’accès Lambda et Amazon Simple Storage Service (Amazon S3) en utilisant AWS CLI :
    # Cree un avec access a S3 et execution de Lambda
    ROLE_NAME=NOM_A_CHANGER #Spécifiez le nom du rôle 
    aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document '{"Version":"2012-10-17","Statement":{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}}'
    aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess --role-name $ROLE_NAME
    aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole --role-name $ROLE_NAME

Étape 1 : Créer MediaInfo pour AWS Lambda

Les fonctions AWS Lambda s’exécutent dans un environnement Amazon Linux, les librairies doivent donc être construites et packagées pour Amazon Linux. Vous pouvez compiler une bibliothèque pymediainfo pour Amazon Linux en utilisant le Docker fourni comme ceci :

git clone https://github.com/iandow/mediainfo_aws_lambda
cd mediainfo_aws_lambda
docker build --tag=pymediainfo-layer-factory:latest .
docker run --rm -it -v $(pwd):/data pymediainfo-layer-factory cp /packages/pymediainfo-python37.zip /data

Le fichier Dockerfile pour construire ce conteneur Docker est disponible sur GitHub.

Étape 2 : Publier MediaInfo en tant que couche AWS Lambda

Déployez la bibliothèque Python MediaInfo en tant que couche AWS Lambda. Notez que les redistributions de MediaInfo sous forme binaire sont nécessaires pour inclure le paramètre license-info.

ACCOUNT_ID=$(aws sts get-caller-identity | jq -r ".Account")
LAMBDA_LAYERS_BUCKET=lambda-layers-$ACCOUNT_ID
LAYER_NAME=pymediainfo
aws s3 mb s3://$LAMBDA_LAYERS_BUCKET
aws s3 cp pymediainfo-python37.zip s3://$LAMBDA_LAYERS_BUCKET
aws lambda publish-layer-version --layer-name $LAYER_NAME --description "pymediainfo" --content S3Bucket=$LAMBDA_LAYERS_BUCKET,S3Key=pymediainfo-python37.zip --compatible-runtimes python3.7 --license-info "This product uses MediaInfo (https://mediaarea.net/en/MediaInfo) library, Copyright (c) 2002-2020 MediaArea.net SARL."

Une fois votre couche AWS lambda publiée, voici ce que vous obtiendrez dans la console AWS :

AWS Console on MediaInfo Lambda Layer

Étape 3 : Déployer une fonction AWS Lambda utilisant MediaInfo

Vous trouverez sur GitHub une fonction Lambda qui affiche toutes les informations fournies par MediaInfo à partir d’un fichier vidéo . Pour déployer cette fonction, créez d’abord un fichier zip contenant uniquement le fichier app.py, comme ceci :

zip app.zip app.py

Déployez ensuite ce fichier en tant que fonction. Il est à noter qu’il est nécessaire d’avoir installer l’utilitaire jq au préalable :

BUCKET_NAME=pymediainfo-test
aws s3 mb s3://$BUCKET_NAME
# Création de la fonction lambda
FUNCTION_NAME=pymediainfo_layered
ACCOUNT_ID=$(aws sts get-caller-identity | jq -r ".Account")
aws s3 cp app.zip s3://$BUCKET_NAME
aws lambda create-function --function-name $FUNCTION_NAME --timeout 20 --role arn:aws:iam::${ACCOUNT_ID}:role/$ROLE_NAME --handler app.lambda_handler --region us-west-2 --runtime python3.7 --environment "Variables={BUCKET_NAME=$BUCKET_NAME,S3_KEY=$S3_KEY}" --code S3Bucket="$BUCKET_NAME",S3Key="app.zip"

Étape 4 : Attacher la couche MediaInfo Lambda à la fonction AWS Lambda

Attachez la couche AWS lambda pymediainfo à la fonction Lambda :

LAYER=$(aws lambda list-layer-versions --layer-name $LAYER_NAME | jq -r '.LayerVersions[0].LayerVersionArn')
aws lambda update-function-configuration --function-name $FUNCTION_NAME --layers $LAYER

Notre fonction Lambda peut maintenant utiliser MediaInfo.

Étape 5 : Test

Notre fonction Lambda nécessite une vidéo en paramètre d’entrée. Copiez donc une vidéo sur Amazon S3, comme par exemple :

# Téléchargement d'une vidéo de test pour être transféré vers le bucket s3
wget https://vjs.zencdn.net/v/oceans.mp4
aws s3 cp ./oceans.mp4 s3://$BUCKET_NAME/videos/oceans.mp4

Appelez ensuite la fonction Lambda :

aws lambda invoke --function-name $FUNCTION_NAME --log-type Tail outputfile.txt
cat outputfile.txt

Vous devriez obtenir la réponse en sortie suivante (NB: le résultat LogResult a été tronqué pour des raisons de clarté) :

{
    "LogResult": "U1RBUlQgU...",
    "ExecutedVersion": "$LATEST",
    "StatusCode": 200
}

Format de Sortie

Le fichier outputfile.txt contiendra toutes les métadonnées pour le fichier vidéo oceans.mp4 comme l’exemple suivant. (Note : pour des raisons de lisibilité. des sauts de ligne ont été ajoutés dans le JSON ci-dessous)

{
    "tracks": [
       {
        "track_type": "General",
        "count": "331",
        "count_of_stream_of_this_kind": "1",
        "kind_of_stream": "General",
        "other_kind_of_stream": ["General"],
        "stream_identifier": "0",
        "count_of_video_streams": "1",
        "count_of_audio_streams": "1",
        "video_format_list": "AVC",
        "video_format_withhint_list": "AVC",
        "codecs_video": "AVC",
        "audio_format_list": "AAC LC",
        "audio_format_withhint_list": "AAC LC",
        "audio_codecs": "AAC LC",
        "complete_name": "/root/oceans.mp4",
        "folder_name": "/root",
        "file_name_extension": "oceans.mp4",
        "file_name": "oceans","file_extension": "mp4","format": "MPEG-4","other_format": ["MPEG-4"],"format_extensions_usually_used": "braw mov mp4 m4v m4a m4b m4p m4r 3ga 3gpa 3gpp 3gp 3gpp2 3g2 k3g jpm jpx mqv ismv isma ismt f4a f4b f4v","commercial_name": "MPEG-4","format_profile": "Base Media","internet_media_type": "video/mp4","codec_id": "isom","other_codec_id": ["isom (isom/avc1)"],"codec_id_url": "http://www.apple.com/quicktime/download/standalone.html","codecid_compatible": "isom/avc1","file_size": 23014356,"other_file_size": ["21.9 MiB","22 MiB","22 MiB","21.9 MiB","21.95 MiB"],"duration": 46613,"other_duration": ["46 s 613 ms","46 s 613 ms","46 s 613 ms","00:00:46.613","00:00:46;12","00:00:46.613 (00:00:46;12)"],"overall_bit_rate_mode": "VBR","other_overall_bit_rate_mode": ["Variable"],"overall_bit_rate": 3949861,"other_overall_bit_rate": ["3 950 kb/s"],"frame_rate": "23.976","other_frame_rate": ["23.976 FPS"],"frame_count": "1116","stream_size": 16342,"other_stream_size": ["16.0 KiB (0%)","16 KiB","16 KiB","16.0 KiB","15.96 KiB","16.0 KiB (0%)"],"proportion_of_this_stream": "0.00071","headersize": "16334","datasize": "22998022","footersize": "0","isstreamable": "Yes","encoded_date": "UTC 2013-05-03 22:51:07","tagged_date": "UTC 2013-05-03 22:51:07","file_last_modification_date": "UTC 2013-05-08 00:34:04","file_last_modification_date__local": "2013-05-08 00:34:04"},{"track_type": "Video","count": "378","count_of_stream_of_this_kind": "1","kind_of_stream": "Video","other_kind_of_stream": ["Video"],"stream_identifier": "0","streamorder": "0","track_id": 1,"other_track_id": ["1"],"format": "AVC","other_format": ["AVC"],"format_info": "Advanced Video Codec","format_url": "http://developers.videolan.org/x264.html","commercial_name": "AVC","format_profile": "Baseline@L3","format_settings": "3 Ref Frames","format_settings__cabac": "No","other_format_settings__cabac": ["No"],"format_settings__reference_frames": 3,"other_format_settings__reference_frames": ["3 frames"],"internet_media_type": "video/H264","codec_id": "avc1","codec_id_info": "Advanced Video Coding","duration": 46545,"other_duration": ["46 s 545 ms","46 s 545 ms","46 s 545 ms","00:00:46.545","00:00:46;12","00:00:46.545 (00:00:46;12)"],"bit_rate": 3859631,"other_bit_rate": ["3 860 kb/s"],"maximum_bit_rate": 9263280,"other_maximum_bit_rate": ["9 263 kb/s"],"width": 960,"other_width": ["960 pixels"],"height": 400,"other_height": ["400 pixels"],"sampled_width": "960","sampled_height": "400","pixel_aspect_ratio": "1.000","display_aspect_ratio": "2.400","other_display_aspect_ratio": ["2.40:1"],"rotation": "0.000","frame_rate_mode": "CFR","other_frame_rate_mode": ["Constant"],"frame_rate": "23.976","other_frame_rate": ["23.976 (24000/1001) FPS"],"framerate_num": "24000","framerate_den": "1001","frame_count": "1116","color_space": "YUV","chroma_subsampling": "4:2:0","other_chroma_subsampling": ["4:2:0"],"bit_depth": 8,"other_bit_depth": ["8 bits"],"scan_type": "Progressive","other_scan_type": ["Progressive"],"bits__pixel_frame": "0.419","stream_size": 22456564,"other_stream_size": ["21.4 MiB (98%)","21 MiB","21 MiB","21.4 MiB","21.42 MiB","21.4 MiB (98%)"],"proportion_of_this_stream": "0.97576","writing_library": "Zencoder Video Encoding System","other_writing_library": ["Zencoder Video Encoding System"],"encoded_library_name": "Zencoder Video Encoding System","encoded_date": "UTC 2013-05-03 22:50:47","tagged_date": "UTC 2013-05-03 22:51:08","codec_configuration_box": "avcC"},{"track_type": "Audio","count": "280","count_of_stream_of_this_kind": "1","kind_of_stream": "Audio","other_kind_of_stream": ["Audio"],"stream_identifier": "0","streamorder": "1","track_id": 2,"other_track_id": ["2"],"format": "AAC","other_format": ["AAC LC"],"format_info": "Advanced Audio Codec Low Complexity","commercial_name": "AAC","format_settings__sbr": "No (Explicit)","other_format_settings__sbr": ["No (Explicit)"],"format_additionalfeatures": "LC","codec_id": "mp4a-40-2","duration": 46613,"other_duration": ["46 s 613 ms","46 s 613 ms","46 s 613 ms","00:00:46.613","00:00:46:23","00:00:46.613 (00:00:46:23)"],"bit_rate_mode": "VBR","other_bit_rate_mode": ["Variable"],"bit_rate": 92920,"other_bit_rate": ["92.9 kb/s"],"maximum_bit_rate": 104944,"other_maximum_bit_rate": ["105 kb/s"],"channel_s": 2,"other_channel_s": ["2 channels"],"channel_positions": "Front: L R","other_channel_positions": ["2/0/0"],"channel_layout": "L R","samples_per_frame": "1024","sampling_rate": 48000,"other_sampling_rate": ["48.0 kHz"],"samples_count": "2237424","frame_rate": "46.875","other_frame_rate": ["46.875 FPS (1024 SPF)"],"frame_count": "2185","compression_mode": "Lossy","other_compression_mode": ["Lossy"],"stream_size": 541450,"other_stream_size": ["529 KiB (2%)","529 KiB","529 KiB","529 KiB","528.8 KiB","529 KiB (2%)"],"proportion_of_this_stream": "0.02353","encoded_date": "UTC 2013-05-03 22:51:07","tagged_date": "UTC 2013-05-03 22:51:08"}]}

Suppression des ressources

Pour éviter de payer des coûts récurrents, utilisez le code suivant pour supprimer tout ce que vous venez juste de créer, même si la fonction AWS Lambda ne coute rien quand elle n’est pas invoquée.

aws s3 rm s3://$BUCKET_NAME/videos/oceans.mp4
aws s3 rb s3://$BUCKET_NAME/
aws s3 rm s3://$LAMBDA_LAYERS_BUCKET/pymediainfo-python37.zip
aws s3 rb s3://$LAMBDA_LAYERS_BUCKET
rm oceans.mp4
rm -rf ./app.zip ./python/
aws lambda delete-function --function-name $FUNCTION_NAME
LAYER_VERSION=$(aws lambda list-layer-versions --layer-name pymediainfo | jq -r '.LayerVersions[0].Version')
aws lambda delete-layer-version --layer-name pymediainfo --version-number $LAYER_VERSION
aws iam detach-role-policy --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole --role-name $ROLE_NAME
aws iam detach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess --role-name $ROLE_NAME
aws iam delete-role --role-name $ROLE_NAME

Conclusion

Vous venez de créer une application serverless d’analyse de metadonnées techniques d’un fichier vidéo. Tout cela grâce à une fonction Lambda simple qui appelle une couche AWS Lambda portant l’applicatif MediaInfo.
Vous pourrez lancer la fonction Lambda de façon automatique en utilisant par exemple les événements S3 dès qu’un nouvel objet est uploadé, utiliser API Gateway invoquant la fonction MediaInfo ou encore en écoutant une file d’attente Amazon Simple Queue Service (Amazon SQS).

Article original par Ian Downard, Technical Marketing Engineer pour AWS, et Alex Burkleaux, Technical Marketing Engineer pour AWS Elemental. Traduit de l’anglais par Raphaël Goldwaser, Specialist Solutions Architect dans le domaine Media et Divertissement.