GO - Função Lambda para converter audio a partir do S3(AWS) - ffmpeg

markgerald

Mark Gerald Martins

Posted on April 17, 2023

GO - Função Lambda para converter audio a partir do S3(AWS) - ffmpeg

Atuei em um projeto pessoal, onde necessitei fazer a conversão de um arquivo wav(audio de qualidade maxima, sem compressão) para mp3.

Neste post, vou demonstrar o passo a passo, como fazer de forma "assíncrona", a partir de um upload para um bucket no S3(serviço de armazenamento de arquivos) da AWS.

No desenho abaixo, segue um contexto do que será feito e explicado neste post:

Image description

Criando Buckets

Em seu console da AWS, vá até o service S3, crie 2 novos buckets. O primeiro bucket, será o destino do upload do arquivo de áudio "bruto", no format .wav. O segundo, será para o arquivo comprimido e convertido para .mp3. No meu caso, usei estes nomes:

Image description

Criando Lambda

Sim, vamos já proceder na criação da função lambda que será responsável por converter os arquivos de audio bruto, para formado de audio compactado. Para isso, em seu AWS Console, na busca procuro por Lambda. Clique em Create Function

Vamos utilizar a linguagem GO, conforme a tela abaixo:

Image description

Permissões de acesso Lambda - S3

Será necessário a criação de uma Police, e atrelá-la a Role criada na criação da lambda.

Para isso acessaremos o console do IAM, sem seguida vamos ao menu Polices > Add Police. Mudaremos o modo para JSON, e vamos inserir o código a seguir:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::mgm-wav-files/*"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::mgm-mp3-files/*"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

A política acima dá permissão de leitura no bucket de arquivos wav e escrita no bucket para arquivos mp3, convertidos. Vamos salvar essa Police.

No console do IAM, vamos ao menu Roles, vamos selecionar a Role criada para a lambda. Em seguida, vamos em Add Permissions > atacch policies. Na buscar, buscar pelo nome da política criada e salvar.

Run time Lambda. O que é isso?

As funções lambda, na linguagem Go(entre outras), rodam em um contexto de ambiente Linux, no caso da distribuição AWS Linux. Nesse contexto, precisamos "carregar" uma lib, que será a responsável por fazer a conversão de áudio.

Essa lib é o FFMPEG. Que trata de vários formatos de mídias, audio, video.

Para isso, utilizaremos uma feature das funções lambda na AWS, chamada Layer, traduzindo: camada

Com uma layer, conseguimos rodar algumas libs em tempo de execução de uma ou mais funções lambda. Como?

Criando Layer FFMPEG

Vamos baixar os binários desta lib em nossa máquina local, e transferir apenas o necessário para um arquivo ZIP, que será inserido em um Lambda Layer.

Para isso, vou utilizar o exemplo abaixo, de ambiente Linux/MacOS:

wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz

wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz.md5

md5sum -c ffmpeg-release-amd64-static.tar.xz.md5

tar xvf ffmpeg-release-amd64-static.tar.xz
Enter fullscreen mode Exit fullscreen mode

Após o download e descompactar o arquivo, vamos criar uma nova basta, para copiar o conteudo do binario do ffmpeg, e zipar o conteúdo:

mkdir -p ffmpeg/bin

cp ffmpeg-4.3.1-amd64-static/ffmpeg ffmpeg/bin/

cd ffmpeg

zip -r ../ffmpeg.zip .
Enter fullscreen mode Exit fullscreen mode

Agora, vamos criar uma nova Layer em nosso ambiente de Lambdas na AWS, anexando o arquivo zip criado no passo acima como no exemplo a seguir:

Image description

Anexando Layer FFMPEG a nossa função Lambda

Vamos voltar ao menu Functions dentro de Lambda em nosso Console. Na aba code vamos descer a tela, e ir até a sessão Layers e clicar em Add Layer. Vamos preencher e selecionar o formulário, como na imagem abaixo

Image description

E (FINALMENTE) vamos ao nosso código!

Para a linguagem Go, não temos um editor online em funções lambda na AWS. Por isso, vamos gerar nosso código localmente, buildá-lo e subir um zip do mesmo via console.

Início de tudo

package main

func main() {
}
Enter fullscreen mode Exit fullscreen mode

Em nosso ambiente local de desenvolvimento em Go, vamos criar uma nova pasta. Nesta pasta, iniciaremos o projeto com o famoso comando "go mod" para criar nosso arquivo de controle de módulos go do Projeto.

Após, iremos criar o arquivo main.go com o seguinte conteúdo:

package main

import "github.com/aws/aws-lambda-go/lambda"

func handler()  {

}
func main() {
    lambda.Start(handler)
}
Enter fullscreen mode Exit fullscreen mode

Acima, importamos a biblioteca oficial lambda, para utilização em Go. Criamos uma função chamada handler onde vamos inserir nossa futura estrutura de conversão de áudio, e abaixo a função main, apenas executa a função handler em ambiente de execução do Lambda.

Agora vamos carregar a biblioteca oficial do s3, para conectar no bucket que irá iniciar a função(essa informação é importante, pois o start traz informações do bucket para a lambda). Em seguida, vamos criar um diretório temporário, dentro do ambiente de execução da lambda, para fazer download do arquivo, onde possa ser manipulado. Em seguida realizar o download, utilizando um log para verificar:

package main

import (
    "context"
    "fmt"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/s3"
    "github.com/aws/aws-sdk-go/service/s3/s3manager"
    "log"
    "os"
)

func handler(_ context.Context, s3Event events.S3Event) {
    record := s3Event.Records[0]
    key := record.S3.Object.Key
    sess, _ := session.NewSession(&aws.Config{Region: &record.AWSRegion})
    downloader := s3manager.NewDownloader(sess)
    file, err := os.Create(fmt.Sprintf("/tmp/%s", key))
    if err != nil {
        panic(err)
    }
    defer file.Close()
    _, err = downloader.Download(file,
        &s3.GetObjectInput{
            Bucket: &record.S3.Bucket.Name,
            Key:    &key,
        })
    if err != nil {
        panic(err)
    }
    log.Printf("Downloaded %s", file.Name())
}

func main() {
    lambda.Start(handler)
}
Enter fullscreen mode Exit fullscreen mode

Nossa função handler, recebe 2 parâmetros vindos de Context e events, libs da AWS que trazem informações a respeito do bucket que inicia a lambda.
Neste ponto, é interessante que façamos o primeiro upload do código buildado, para realizar um teste "manual". Primeiro vamos criar o build dessa aplicação com o comando:

go build -o main
Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos empacotar o arquivo em um arquivo zip, para realizar o upload:

zip main.zip main
Enter fullscreen mode Exit fullscreen mode

Agora vamos ao console, realizar o upload:

Image description

Criando Trigger

Antes de testarmos o código, precisamos criar a trigger ou gatilho, que dará inicio a lambda que criamos.

Dentro da nossa função, no console AWS vamos clicar em Add Trigger.

Vamos preencher o formulário, conforme as 3 imagens abaixo:

Image description

Neste ponto, vamos selecionar o bucket dos arquivos brutos:
Image description

Em seguida selecionar todos os tipos de entrada de arquivos
Image description

Por final, vamos deixar um filtro para somente iniciar a lambda, para arquivos .wav
Image description

Primeiro teste

Agora, vamos ao nosso bucket, fazer um upload de arquivo .wav, e verificar o status no CloudWatch, e posteriormente verificar se o arquivo mp3 foi criado no outro bucket.

Image description

Acima vemos os 2 buckets utilizados em nosso projeto. Vamos acessar o mgm-wav-files para realizar o upload. Após o upload, vamos acessar o CloudWatch, ir ao meno Logs > Logs Groups. Lá haverá um grupo de log, com nome de nossa Lambda. Em nosso casso está assim:

Image description

Entrando em nosso log, vamos procurar se há a mensagem "Downloaded NOMEDOARQUIVO", como a seguir:

Image description

Neste caso, tudo certo, vamos continuar!

Convertando arquivo .wav com FFMPEG

Vamos utilizar a funciona a lib nativa Go OS, para utilizar a função exec.Command, que nos possibilita rodar comandos em nível de bash. E a partir dele, usar o ffmpeg para convertar o arquivo .wav para .mp3, gravando-o no diretório temporário criado anteriormente:

outputFile := strings.Replace(file.Name(), filepath.Ext(file.Name()), ".mp3", 1)
    cmd := exec.Command("ffmpeg", "-i", file.Name(), outputFile)
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s\n", err)
    }
    log.Printf("Execution output:\n%s\n", string(out))
    output, err := os.Open(outputFile)
    if err != nil {
        panic(err)
    }
Enter fullscreen mode Exit fullscreen mode

Gravando arquivo convertido em outro bucket

Agora, vamos acessar o arquivo criado, e fazer o upload para o bucket criado, apenas para receber os arquivos convertidos. O código exemplo, existe na documentação da AWS:

//put mp3 file in converted bucket
    destinationBucket := "mgm-mp3-files"
    _, err = s3.New(sess).PutObject(&s3.PutObjectInput{
        Bucket: &destinationBucket,
        Key:    aws.String(filepath.Base(outputFile)),
        Body:   output,
    })
    log.Printf("Copied %s to %s", outputFile, record.S3.Bucket.Name)
Enter fullscreen mode Exit fullscreen mode

Código Final

package main

import (
    "context"
    "fmt"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/s3"
    "github.com/aws/aws-sdk-go/service/s3/s3manager"
    "log"
    "os"
    "os/exec"
    "path/filepath"
    "strings"
)

func handler(_ context.Context, s3Event events.S3Event) {
    record := s3Event.Records[0]
    key := record.S3.Object.Key
    sess, _ := session.NewSession(&aws.Config{Region: &record.AWSRegion})

    //Download file to a temporary folder
    downloader := s3manager.NewDownloader(sess)
    file, err := os.Create(fmt.Sprintf("/tmp/%s", key))
    if err != nil {
        panic(err)
    }
    defer file.Close()
    _, err = downloader.Download(file,
        &s3.GetObjectInput{
            Bucket: &record.S3.Bucket.Name,
            Key:    &key,
        })
    if err != nil {
        panic(err)
    }
    log.Printf("Downloaded %s", file.Name())

    //transform wav file to a compress mp3 file
    outputFile := strings.Replace(file.Name(), filepath.Ext(file.Name()), ".mp3", 1)
    cmd := exec.Command("ffmpeg", "-i", file.Name(), outputFile)
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s\n", err)
    }
    log.Printf("Execution output:\n%s\n", string(out))
    output, err := os.Open(outputFile)
    if err != nil {
        panic(err)
    }

    //put mp3 file in converted bucket
    destinationBucket := "mgm-mp3-files"
    _, err = s3.New(sess).PutObject(&s3.PutObjectInput{
        Bucket: &destinationBucket,
        Key:    aws.String(filepath.Base(outputFile)),
        Body:   output,
    })
    log.Printf("Copied %s to %s", outputFile, record.S3.Bucket.Name)
}

func main() {
    lambda.Start(handler)
}
Enter fullscreen mode Exit fullscreen mode

PLUS - Mensagem assíncrona de fim da atividade

Por final, vamos fazer com que, ao final da execução do lambda, em caso de sucesso uma fila SQS seja abastecida com uma mensagem, para que consumidores(aplicativos, web, etc..) possam ser avisados do final do processo e disponibilização do arquivo mp3 convertido.

Vamos agora ao console AWS, procurar por SQS. O serviço simples de fila da própria Amazon. Vamos criar um tópico, chamado mp3-ready

Image description

Agora, voltando a nossa função lambda, vamos acessar o menu Add Destination.

Image description

Nosso formulário deve ser preenchido destga maneira, escolhendo a fila SQS criada(A mensagem de falta de permissão para uso do SQS pelo lambda, será solucionada no próximo passo)

Image description

Agora com nosso destino de lambda criado, precisamos dar permissão no IAM para nossa role de execução da lambda, criar mensagem no tópico criado no SQS

Image description

Permissão lambda > SQS

No console do IAM, clicar no menu ROLES. Procurar pela role criada para a lambda. Dentro dessa role, vamos no menu Add Permissions > Atacch polices. Na busca, procurar por SQS, e escolher AmazonSQSFullAccess(sabemos que não é o ideal rs.) e clicar em Add Permissions.

Repositório

Repositorio Github

💖 💪 🙅 🚩
markgerald
Mark Gerald Martins

Posted on April 17, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related