PWA Angular com WebAssembly que converte vídeos em gifs sem upload

Hoje já é possível criar aplicativos PWA que funcionam dentro do browser e sem acesso à Internet. Entretanto costumava ser difícil realizar tarefas pesadas dentro do contexto do browser. Por exemplo, editar vídeos é mais fácil acessando alguma API que esteja rodando em um servidor remoto. O WebAssembly veio para suprir essa e outras necessidades, pois com ele é possível executar binários através do JavaScript direto do browser.

Com o WebAssembly é possível usar o pacote ffmpeg para converter vídeos em gifs sem a necessidade de qualquer servidor remoto. Incrível, né?

Vamos lá! Comecei criando um novo projeto via Angular CLI e depois instalei via NPM os pacotes do ffmpeg.

ng new wasm-video-to-gif
cd wasm-video-to-gif
npm install @ffmpeg/ffmpeg @ffmpeg/core --save

Também adicionei “node” na lista types dentro de compilerOptions do arquivo tsconfig.app.json.

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": [
      "node"
    ]
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.d.ts"
  ]
}

Para utilizar o ffmpeg é necessário primeiro carregar ele na memória.

import { Component, OnInit } from '@angular/core';
import { createFFmpeg, FFmpeg } from '@ffmpeg/ffmpeg';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

  ffmpeg: FFmpeg;
  isReady = false;

  async ngOnInit() {
    this.ffmpeg = createFFmpeg({ log: true });
    await this.ffmpeg.load();
    // Flag que indica que está pronto.
    this.isReady = true;
  }

}

A primeira parte é permitir que o usuário selecione um arquivo de vídeo. Para isso eu utilizei o elemento <input type=”file”> e o evento change apontando para uma função chamada selectedFile.

<input *ngIf="isReady" type="file" (change)="selectedFile($event)">
// Atualizei os imports adicionando o fetchFile
import { createFFmpeg, fetchFile, FFmpeg } from '@ffmpeg/ffmpeg';

gifUrlData: string;

async selectedFile(event){
    // Pegando arquivo do evento 'change'
    const videoFile: File = event.target.files?.item(0);
    // Carregando o arquivo de vídeo na memória em 'video.mp4'
    this.ffmpeg.FS('writeFile', 'video.mp4', await fetchFile(videoFile));
	// Executando o comando do FFMpeg para converter 'video.mp4' para 'video.gif' (2,5 segundos de duração)
  	await this.ffmpeg.run('-i', 'video.mp4', '-t', '2.5', '-ss', '2.0', '-f', 'gif', 'video.gif');
 	// Lendo resultado de 'video.gif'
    const gifData = this.ffmpeg.FS('readFile', 'video.gif');
    // Criando uma URL com dados do gif
    this.gifUrlData = URL.createObjectURL(new Blob([gifData.buffer], { type: 'image/gif' }));
}

O progresso da conversão aparece no console do browser.

Ao executar a função, a propriedade gifUrlData ficou com a URL do blob do gif. Para renderizar o gif utilizei o elemento img, mas, para poder usar essa URL do gif no Angular , eu tive que “higienizar” (sanatize) ela antes.

// ...
import { DomSanitizer } from '@angular/platform-browser';
// ...
export class AppComponent implements OnInit {
  // ...
  constructor(
  private domSanitizer: DomSanitizer
  ) { }
  // ...
  sanitize(url: string): SafeUrl {
    return this.domSanitizer.bypassSecurityTrustUrl(url);
  }
}
<img *ngIf="gifUrlData" [src]="sanitize(gifUrlData)" style="max-width: 500px;">

Para que a aplicação funcione como um PWA eu tive que adicionar via Angular CLI a lib @angular/pwa.

ng add @angular/pwa --project wasm-video-to-gif

Ficou pronto! Agora falta só publicar em algum ambiente HTTPS.

Com WebAssembly é possível realizar processamentos pesados direto do browser. É possível, por exemplo, utilizar ffmpeg para criar um editor de vídeos mais completo que funcione no navegador e sem servidor remoto. Ao transformar a aplicação em PWA é possível que o usuário utilize mesmo off-line.

O repositório da aplicação está disponível no GitHub.

Veja funcionando você mesmo clicando aqui.

Espero ter ajudado! 😉

2 comentários

  1. Texto objetivo e didático ao mesmo tempo, parabéns pela iniciativa em dispensar um tempo para passar conhecimento adiante e pela clareza e competência com que o faz 🙂

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *