Sign inSign up

gabrielsv01/ffmpeg-api

By gabrielsv01

•Updated 11 months ago

Image
Web servers
0

1.1K

gabrielsv01/ffmpeg-api repository overview

⁠FFmpeg API Documentation

⁠🆕 Novidades

  • Sistema de Fila de Jobs: Jobs assíncronos agora são controlados por uma fila, com limite de processamento simultâneo configurável.
  • Limite de Concorrência Configurável: Defina o número máximo de jobs simultâneos via variável de ambiente MAX_CONCURRENT_JOBS.
  • Visualização da Fila: Endpoints e UI mostram jobs em processamento, em espera, concluídos e com falha.

Uma API REST completa para processamento de vídeo e áudio usando FFmpeg em containers Docker.

⁠📋 Visão Geral

Esta API fornece uma interface HTTP para executar comandos FFmpeg, gerenciar arquivos de entrada e saída, e monitorar jobs de processamento. É ideal para automação de processamento de mídia em ambientes containerizados.

⁠🌟 Características Principais
  • API REST Completa: Endpoints para todas as operações FFmpeg
  • Sistema de Jobs: Processamento assíncrono com heartbeat monitoring
  • Fila de Jobs: Jobs aguardam na fila se o limite de concorrência for atingido
  • Configuração via Ambiente: Limite de jobs simultâneos ajustável com MAX_CONCURRENT_JOBS
  • Upload Flexível: Suporte a base64 e multipart/form-data até 500MB
  • Gerenciamento de Arquivos: Upload, download, listagem e informações de mídia
  • Validação de Segurança: Path traversal protection e validação de arquivos
  • Documentação Automática: README servido como HTML na rota raiz

⁠🚀 Início Rápido

⁠Pré-requisitos
  • Docker e Docker Compose
  • Node.js 18+ (para desenvolvimento)
⁠Instalação
git clone <repository>
cd gabriel-store-ffmpeg
docker-compose up -d

A API estará disponível em http://localhost:5135

⁠📖 Endpoints da API

⁠🆕 Endpoints de Fila
⁠GET /queue/status

Retorna o status atual da fila de jobs assíncronos.

Exemplo:

curl http://localhost:5135/queue/status

Resposta:

{
  "queue": {
    "maxConcurrentJobs": 3,
    "currentlyProcessing": {
      "count": 2,
      "jobs": [
        {
          "id": "jobId1",
          "status": "running",
          "command": "ffmpeg ...",
          "startTime": "2025-11-12T10:00:00.000Z",
          "progress": null
        },
        {
          "id": "jobId2",
          "status": "running",
          "command": "ffmpeg ...",
          "startTime": "2025-11-12T10:01:00.000Z",
          "progress": null
        }
      ]
    },
    "waiting": {
      "count": 2,
      "jobs": [
        {
          "id": "jobId3",
          "status": "queued",
          "command": "ffmpeg ...",
          "startTime": "2025-11-12T10:02:00.000Z",
          "positionInQueue": 1
        },
        {
          "id": "jobId4",
          "status": "queued",
          "command": "ffmpeg ...",
          "startTime": "2025-11-12T10:03:00.000Z",
          "positionInQueue": 2
        }
      ]
    }
  },
  "statistics": {
    "totalJobs": 10,
    "completedJobs": 5,
    "failedJobs": 2,
    "runningJobs": 2,
    "queuedJobs": 2
  },
  "timestamp": "2025-11-12T10:05:00.000Z"
}

Notas:

  • O valor de maxConcurrentJobs pode ser alterado via variável de ambiente MAX_CONCURRENT_JOBS.
  • Jobs em currentlyProcessing.jobs estão sendo executados; jobs em waiting.jobs aguardam vaga na fila.
⁠🔍 Status e Monitoramento
⁠GET /status

Verifica o status dos diretórios compartilhados e containers.

Parâmetros:

  • Nenhum parâmetro necessário

Exemplo:

curl http://localhost:5135/status

Resposta de Sucesso:

{
  "status": "ok",
  "directories": "total 8\ndrwxr-xr-x 2 abc abc 4096 Jan 15 10:30 input\ndrwxr-xr-x 2 abc abc 4096 Jan 15 10:30 output"
}

Resposta de Erro (container FFmpeg não encontrado):

{
  "status": "error",
  "error": "Error: No such container: ffmpeg"
}

Resposta de Erro (diretórios não acessíveis):

{
  "status": "error", 
  "error": "docker: Error response from daemon: container ffmpeg is not running"
}
⁠POST /init

Cria os diretórios necessários se não existirem.

Parâmetros:

  • Nenhum parâmetro necessário

Exemplo:

curl -X POST http://localhost:5135/init

Resposta de Sucesso:

{
  "success": true,
  "message": "Diretórios criados/verificados"
}

Resposta de Erro (container não acessível):

{
  "error": "Error: No such container: ffmpeg"
}

Resposta de Erro (permissão negada):

{
  "error": "docker: Error response from daemon: container ffmpeg is not running"
}
⁠📁 Gerenciamento de Arquivos
⁠GET /files/:type

Lista arquivos em um diretório específico.

Parâmetros:

  • type: input ou output

Exemplo:

curl http://localhost:5135/files/input

Resposta de Sucesso:

{
  "type": "input",
  "count": 2,
  "files": [
    {
      "name": "1642254000000-video.mp4",
      "size": 15728640,
      "sizeFormatted": "15.00 MB",
      "date": "Jan 15 10:30",
      "permissions": "-rw-r--r--",
      "isMedia": true,
      "downloadUrl": null,
      "directUrl": null
    },
    {
      "name": "1642254000001-audio.mp3",
      "size": 5242880,
      "sizeFormatted": "5.00 MB",
      "date": "Jan 15 10:32",
      "permissions": "-rw-r--r--",
      "isMedia": true,
      "downloadUrl": null,
      "directUrl": null
    }
  ]
}

Resposta (diretório vazio):

{
  "type": "input",
  "count": 0,
  "files": []
}

Resposta de Erro:

{
  "error": "Tipo de diretório inválido. Use 'input' ou 'output'"
}
⁠GET /info/:type/:filename

Obtém informações detalhadas de um arquivo de mídia usando ffprobe.

Parâmetros:

  • type: input ou output
  • filename: nome do arquivo

Exemplo:

curl http://localhost:5135/info/input/video.mp4

Resposta de Sucesso:

{
  "filename": "1642254000000-video.mp4",
  "type": "input",
  "format": {
    "formatName": "mov,mp4,m4a,3gp,3g2,mj2",
    "formatLongName": "QuickTime / MOV",
    "duration": 120.5,
    "durationFormatted": "2:00",
    "size": 15728640,
    "sizeFormatted": "15.00 MB",
    "bitRate": 1045000
  },
  "video": {
    "codec": "h264",
    "codecLongName": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
    "width": 1920,
    "height": 1080,
    "resolution": "1920x1080",
    "frameRate": 30,
    "bitRate": 1000000,
    "pixelFormat": "yuv420p"
  },
  "audio": {
    "codec": "aac",
    "codecLongName": "AAC (Advanced Audio Coding)",
    "sampleRate": 48000,
    "channels": 2,
    "bitRate": 128000
  },
  "downloadUrl": null,
  "directUrl": null
}

Resposta de Erro (arquivo não encontrado):

{
  "error": "Arquivo não encontrado",
  "filename": "inexistente.mp4",
  "type": "input"
}

Resposta de Erro (não é arquivo de mídia):

{
  "error": "Não foi possível obter informações do arquivo. Certifique-se de que é um arquivo de mídia válido",
  "filename": "documento.txt",
  "type": "input"
}
⁠📤 Upload de Arquivos
⁠POST /upload

Upload via multipart/form-data (recomendado para arquivos grandes).

Parâmetros:

  • Form Data: file - arquivo a ser enviado (obrigatório)
  • Headers: Content-Type: multipart/form-data (automático)

Exemplo:

curl -X POST http://localhost:5135/upload \
  -F "[email protected]"

Resposta de Sucesso:

{
  "success": true,
  "message": "Arquivo enviado com sucesso",
  "file": {
    "originalName": "video.mp4",
    "savedName": "1642254000000-video.mp4",
    "size": 15728640,
    "sizeFormatted": "15.00 MB",
    "path": "/shared/input/1642254000000-video.mp4",
    "mimetype": "video/mp4"
  }
}

Resposta de Erro (nenhum arquivo):

{
  "error": "Nenhum arquivo enviado"
}

Resposta de Erro (erro do sistema):

{
  "error": "Erro ao fazer upload do arquivo",
  "details": "ENOSPC: no space left on device, write '/shared/input/temp'"
}
⁠POST /upload-json

Upload via base64 (até 500MB).

Parâmetros:

  • Body JSON:
    • data (string, obrigatório): arquivo codificado em base64
    • filename (string, obrigatório): nome do arquivo com extensão

Headers necessários:

  • Content-Type: application/json

Exemplo:

curl -X POST http://localhost:5135/upload-json \
  -H "Content-Type: application/json" \
  -d '{
    "data": "data:video/mp4;base64,AAAAHGZ0eXBpc29...",
    "filename": "video.mp4"
  }'

Resposta de Sucesso:

{
  "success": true,
  "message": "Arquivo enviado com sucesso",
  "file": {
    "originalName": "video.mp4",
    "savedName": "1642254000000-video.mp4",
    "size": 15728640,
    "sizeFormatted": "15.00 MB",
    "path": "/shared/input/1642254000000-video.mp4"
  }
}

Resposta de Erro (dados faltando):

{
  "error": "Dados ou nome do arquivo não fornecidos"
}

Resposta de Erro (base64 inválido):

{
  "error": "Erro ao processar dados base64",
  "details": "Invalid character in base64 string"
}

Resposta de Erro (arquivo muito grande):

{
  "error": "Payload too large",
  "details": "Arquivo excede o limite de 500MB para upload JSON"
}
```{
  {
    "originalName": "video.mp4",
    "savedName": "1642254000000-video.mp4",
    "size": 15728640,
    "sizeFormatted": "15.00 MB",
    "path": "/shared/input/1642254000000-video.mp4",
    "mimetype": "video/mp4"
  }
}
⁠🎬 Processamento FFmpeg
⁠POST /ffmpeg

Executa comandos FFmpeg síncronos (timeout: 5 minutos).

Parâmetros:

  • Body JSON:
    • command (string, obrigatório): comando FFmpeg completo

Headers necessários:

  • Content-Type: application/json

Observações:

  • Parâmetro -y é adicionado automaticamente
  • Timeout de 5 minutos (300 segundos)
  • Processamento síncrono (bloqueia até conclusão)

Exemplo:

curl -X POST http://localhost:5135/ffmpeg \
  -H "Content-Type: application/json" \
  -d '{
    "command": "ffmpeg -i /shared/input/video.mp4 -c:v libx264 -crf 23 /shared/output/compressed.mp4"
  }'

Resposta de Sucesso:

{
  "success": true,
  "stdout": "ffmpeg version 4.4.2-0ubuntu0.20.04.4 Copyright (c) 2000-2021 the FFmpeg developers\nbuilt with gcc 9 (Ubuntu 9.4.0-1ubuntu1~20.04.1)\n...\nframe= 3600 fps= 45 q=23.0 size=   15360kB time=00:02:00.00 bitrate=1024.0kbits/s speed=1.5x\nvideo:14080kB audio:1280kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%",
  "stderr": "",
  "outputFile": "compressed.mp4",
  "downloadUrl": "/download/compressed.mp4",
  "directUrl": "/files/compressed.mp4"
}

Resposta de Erro (comando vazio):

{
  "success": false,
  "error": "Comando não fornecido"
}

Resposta de Erro (arquivo não encontrado):

{
  "success": false,
  "stdout": "",
  "stderr": "/shared/input/inexistente.mp4: No such file or directory",
  "error": "Erro na execução do FFmpeg"
}

Resposta de Erro (timeout):

{
  "success": false,
  "error": "Comando cancelado por timeout (5 minutos)"
}
⁠POST /ffmpeg-async

Executa comandos FFmpeg assíncronos com sistema de jobs avançado.

Novidade: Agora o endpoint controla a fila de jobs. Se o limite de concorrência for atingido, o job é adicionado à fila de espera e processado assim que possível.

Como executar por filas: Para que o job seja processado usando o sistema de filas, envie o parâmetro useQueue como true no corpo da requisição JSON:

curl -X POST http://localhost:5135/ffmpeg-async \
  -H "Content-Type: application/json" \
  -d '{
    "command": "ffmpeg -i /shared/input/video.mp4 -c:v libx264 -crf 23 /shared/output/compressed.mp4",
    "useQueue": true
  }'

Se useQueue não for enviado ou for false, o job será executado diretamente (sem controle de fila).

Parâmetros:

  • Body JSON:
    • command (string, obrigatório): comando FFmpeg completo

Headers necessários:

  • Content-Type: application/json

Observações:

  • Parâmetro -y é adicionado automaticamente
  • Sem timeout (monitored via heartbeat)
  • Processamento assíncrono (retorna job ID imediatamente)
  • Job é monitorado via heartbeat system
  • Controle de Fila: Se o número de jobs em processamento atingir o limite (MAX_CONCURRENT_JOBS), o job será adicionado à fila de espera e processado assim que possível.
⁠📥 Download
⁠GET /download/:filename

Download direto de arquivos processados com headers apropriados para download.

Parâmetros:

  • filename (path, obrigatório): nome do arquivo no diretório /shared/output/

Observações:

  • Arquivo deve existir no diretório output
  • Valida contra path traversal attacks
  • Define Content-Disposition para forçar download

Exemplo:

curl -O http://localhost:5135/download/compressed.mp4

Headers de Resposta:

Content-Type: video/mp4
Content-Disposition: attachment; filename="compressed.mp4"
Content-Length: 15728640

Resposta de Erro (404):

{
  "error": "Arquivo não encontrado",
  "filename": "inexistente.mp4"
}
⁠GET /files/:filename

Acesso direto a arquivos para visualização/streaming (servidos estaticamente).

Parâmetros:

  • filename (path, obrigatório): nome do arquivo no diretório /shared/output/

Observações:

  • Servido via express.static
  • Suporte a range requests (streaming)
  • Sem Content-Disposition (navegador decide)

Exemplo:

curl http://localhost:5135/files/compressed.mp4

Diferenças do /download:

  • Sem Content-Disposition: Navegador decide se baixa ou visualiza
  • Streaming Friendly: Suporte a range requests para vídeo
  • Cache Headers: Headers de cache otimizados
⁠GET /files/:type/:filename

Acesso direto a arquivos por tipo (input/output) para visualização/streaming ou download.

Parâmetros:

  • type (path, obrigatório): input ou output
  • filename (path, obrigatório): nome do arquivo

Observações:

  • Permite acesso a arquivos tanto do diretório input quanto output
  • Validação de segurança contra path traversal
  • Suporte a range requests para streaming
  • Headers apropriados baseados no tipo de arquivo

Exemplo:

# Acessar arquivo do diretório input
curl http://localhost:5135/files/input/video.mp4

# Acessar arquivo do diretório output
curl http://localhost:5135/files/output/processed.mp4

Resposta de Sucesso:

  • Arquivo servido diretamente com headers apropriados
  • Content-Type baseado na extensão do arquivo
  • Suporte a partial content (range requests)

Resposta de Erro (404):

{
  "error": "Arquivo não encontrado",
  "type": "input",
  "filename": "inexistente.mp4"
}

Resposta de Erro (tipo inválido):

{
  "error": "Tipo de diretório inválido. Use 'input' ou 'output'"
}
⁠📚 Documentação
⁠GET /

Serve esta documentação como HTML estilizado.

Parâmetros:

  • Nenhum parâmetro necessário

Exemplo:

curl http://localhost:5135/
⁠GET /ui

Interface web moderna para gerenciamento visual.

Parâmetros:

  • Nenhum parâmetro necessário

Exemplo:

curl http://localhost:5135/ui

⁠🔧 Workflow Completo

# 1. Upload de arquivos
curl -X POST http://localhost:5135/upload -F "[email protected]"
curl -X POST http://localhost:5135/upload -F "[email protected]"

# 2. Verificar arquivos
curl http://localhost:5135/files/input

# 3. Obter informações do vídeo
curl http://localhost:5135/info/input/1642254000000-video.mp4

# 4. Processar (assíncrono)
RESPONSE=$(curl -X POST http://localhost:5135/ffmpeg-async \
  -H "Content-Type: application/json" \
  -d '{
    "command": "ffmpeg -i /shared/input/1642254000000-video.mp4 -i /shared/input/1642254000001-audio.mp3 -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 /shared/output/resultado.mp4"
  }')

# 5. Extrair jobId da resposta
JOB_ID=$(echo $RESPONSE | jq -r '.jobId')

# 6. Monitorar progresso
curl http://localhost:5135/job/$JOB_ID

# 7. Listar jobs
curl http://localhost:5135/jobs

# 8. Download do resultado
curl -O http://localhost:5135/download/resultado.mp4

⁠🏗️ Arquitetura

⁠Estrutura de Containers
  • ffmpeg: Container LinuxServer FFmpeg para processamento
  • ffmpeg-api: API Node.js/TypeScript que controla o FFmpeg
⁠Sistema de Jobs com Heartbeat

O sistema de jobs assíncronos inclui:

  • Heartbeat Monitoring: Verifica processos a cada 30s
  • Orphan Job Detection: Detecta jobs órfãos e marca como falhou
  • Auto Cleanup: Remove jobs antigos automaticamente (24h)
  • Process Validation: Confirma que processos FFmpeg estão realmente rodando

⁠🔐 Segurança e Validação

⁠Validações Implementadas
  1. Path Traversal Protection: Validação de nomes de arquivo
  2. Directory Type Validation: Apenas 'input' e 'output' permitidos
  3. Command Timeout: 5 minutos máximo para comandos síncronos
  4. File Size Limits: 500MB para uploads JSON
  5. Process Isolation: Execução em containers separados

⁠⚙️ Configurações

⁠Variáveis de Ambiente
PORT=3001                    # Porta da API (padrão: 3001)
MAX_CONCURRENT_JOBS=3        # Máximo de jobs FFmpeg assíncronos em processamento simultâneo (padrão: 3)
⁠Limites e Timeouts
  • Upload JSON: 500MB máximo
  • Upload Multipart: Sem limite específico
  • Command Timeout: 5 minutos (300 segundos)
  • Job Heartbeat: 30 segundos de intervalo
  • Job Max Silent: 2 minutos sem atividade
  • Job Concorrentes: Definido por MAX_CONCURRENT_JOBS (default: 3)
⁠Health Check
# Verificar saúde da API
curl http://localhost:5135/status

# Verificar se containers estão rodando
docker ps | grep ffmpeg

⁠⚠️ Notas Importantes

  1. Ambiente Controlado: Use apenas em ambientes seguros
  2. Caminhos Absolutos: Sempre use /shared/input/ e /shared/output/
  3. Parâmetro -y: Adicionado automaticamente aos comandos ffmpeg
  4. Formatos Suportados: Todos os formatos do FFmpeg (MP4, AVI, MOV, MKV, WebM, MP3, WAV, AAC, FLAC, etc.)
  5. Docker Socket: API precisa de acesso ao socket Docker
  6. Monitoring: Jobs são monitorados via heartbeat para detectar falhas

⁠🛠️ Desenvolvimento

⁠Setup Local
cd code
npm install
npm run dev
export MAX_CONCURRENT_JOBS=5 # Exemplo para rodar com 5 jobs simultâneos

⁠🤝 Contribuindo

  1. Fork o projeto
  2. Crie uma branch para sua feature
  3. Commit suas mudanças
  4. Push para a branch
  5. Abra um Pull Request

⁠🏷️ Tags

ffmpeg api typescript docker video audio conversion multimedia rest-api node.js jobs heartbeat async media-processing file-management


📖 Documentação⁠ • 📊 Status⁠ • 📁 Arquivos Input⁠ • 📁 Arquivos Output⁠ • 👷 Jobs⁠

Desenvolvido com ❤️ usando TypeScript, Express e Docker

Tag summary

Content type

Image

Digest

sha256:26d272426…

Size

53.1 MB

Last updated

11 months ago

docker pull gabrielsv01/ffmpeg-api