GO - Função Lambda para converter audio a partir do S3(AWS) - ffmpeg
Mark Gerald Martins
Posted on April 17, 2023
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:
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:
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:
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/*"
}
]
}
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
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 .
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:
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
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() {
}
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)
}
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)
}
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
Em seguida, vamos empacotar o arquivo em um arquivo zip, para realizar o upload:
zip main.zip main
Agora vamos ao console, realizar o upload:
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:
Neste ponto, vamos selecionar o bucket dos arquivos brutos:
Em seguida selecionar todos os tipos de entrada de arquivos
Por final, vamos deixar um filtro para somente iniciar a lambda, para arquivos .wav
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.
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:
Entrando em nosso log, vamos procurar se há a mensagem "Downloaded NOMEDOARQUIVO", como a seguir:
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)
}
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)
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)
}
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
Agora, voltando a nossa função lambda, vamos acessar o menu Add Destination.
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)
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
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
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
November 29, 2024