Video Merge — Dunorte

Acesso restrito · entre com suas credenciais

Video Merge

Concatenação de MP4 sem re-encode

Operacional
Selecione um clipe da timeline para visualizar
mín. 1 · máx. 10 · até 500 MB
Máscaras (sobreposição PNG)
Arraste blocos para reposicionar · bordas para redimensionar
0:00
Iniciando...
0%
Pré-visualização da requisição que será enviada para a API.

Visão geral

Esta API recebe um ou mais arquivos .mp4 e os concatena em um único vídeo. Pode aplicar máscaras PNG sobrepostas em momentos específicos da timeline ou em todo o projeto.

O processamento é seletivo: trechos sem máscara são copiados sem re-encode (rápido); trechos com máscara são re-encodados em H.264. Os jobs são assíncronos: você posta os arquivos, recebe um jobId, faz polling do progresso e baixa o resultado quando concluído.

Endpoints

Iniciar processamento

POST /merge

Envia os vídeos e (opcionalmente) máscaras via multipart/form-data. Retorna imediatamente um jobId. O processamento ocorre em background.

Campos do multipart

CampoTipoDescrição
videosobrigatório file (mp4) ×N 1 a 10 arquivos MP4. A ordem do upload define a ordem da concatenação.
masksopcional file (png) ×N Até 10 arquivos PNG com transparência. A escala é definida em mask_meta.
mask_metaopcional string (JSON) Array JSON com os parâmetros de cada máscara — mesma quantidade de itens que masks, na mesma ordem.

Resposta — 200 OK

{
  "jobId": "abc123def456",
  "totalDuration": 45.5,
  "hasMasks": true,
  "progressUrl": "/merge/progress/abc123def456",
  "resultUrl": "/merge/result/abc123def456"
}

Consultar progresso

GET /merge/progress/:jobId

Snapshot do estado atual. Faça polling enquanto status for queued ou running.

Resposta — 200 OK

{
  "id": "abc123def456",
  "status": "running",
  "stage": "re-encode seletivo (somente segmentos com máscara)",
  "progress": 0.42,
  "elapsedSec": 12.3,
  "etaSec": 17.8,
  "currentTime": 19.1,
  "totalDuration": 45.5,
  "speed": "1.5x",
  "fps": 28.4,
  "frame": 458,
  "bitrate": "2400kbits/s",
  "hasMasks": true,
  "inputCount": 3,
  "maskCount": 1,
  "error": ""
}

Status possíveis: queued, running, done, error.

Baixar resultado

GET /merge/result/:jobId

Retorna o arquivo merged.mp4 quando status === "done".

  • 200 — stream do vídeo (Content-Type: video/mp4, Content-Disposition: attachment)
  • 409 — job ainda não finalizado (consulte progresso primeiro)
  • 404 — job não existe ou já foi removido pelo cleanup automático
Jobs concluídos são removidos automaticamente após 30 minutos. Baixe o resultado dentro desse prazo.

Schema do mask_meta

Array JSON com um objeto por máscara, na mesma ordem dos arquivos enviados em masks.

CampoTipoDescrição
positionobrig. string Onde a máscara é ancorada no frame: top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right.
scaleobrig. number Largura da máscara como fração da largura do vídeo (0.05 a 1.0). Altura mantida proporcional ao PNG.
fullTimelineopc. boolean Se true, aplica em todos os clipes do início ao fim do vídeo final — independente das durações reais. Ignora start / end.
start number Segundos onde a máscara começa a aparecer (na timeline do projeto concatenado). Ignorado se fullTimeline=true.
end number Segundos onde a máscara para de aparecer. Deve ser maior que start. Ignorado se fullTimeline=true.

Exemplos com curl

1. Concat simples — 2 vídeos sem máscara

Caso mais rápido: apenas concat com -c copy, segundos para finalizar.

curl -X POST https://videos.dunortetecnologia.com.br/merge \
  -F "videos=@v1.mp4" \
  -F "videos=@v2.mp4"

2. 1 vídeo + máscara em toda a timeline

Logo permanente no canto superior direito ocupando 20% da largura do vídeo, do início ao fim — funciona com qualquer duração de input.

curl -X POST https://videos.dunortetecnologia.com.br/merge \
  -F "videos=@video.mp4" \
  -F "masks=@logo.png" \
  -F 'mask_meta=[{"position":"top-right","scale":0.2,"fullTimeline":true}]'

3. Vários vídeos + 2 máscaras (uma fixa + uma temporal)

Logo permanente no canto + frame decorativo central de 0:10 a 0:25 cobrindo metade do vídeo.

curl -X POST https://videos.dunortetecnologia.com.br/merge \
  -F "videos=@intro.mp4" \
  -F "videos=@conteudo.mp4" \
  -F "videos=@outro.mp4" \
  -F "masks=@logo.png" \
  -F "masks=@frame.png" \
  -F 'mask_meta=[
    {"position":"top-right","scale":0.15,"fullTimeline":true},
    {"position":"center","scale":0.5,"fullTimeline":false,"start":10,"end":25}
  ]'

4. Acompanhar progresso

Use o jobId da resposta anterior. Recomenda-se intervalo de ~600ms.

JOB_ID="abc123def456"
while true; do
  SNAP=$(curl -s https://videos.dunortetecnologia.com.br/merge/progress/$JOB_ID)
  STATUS=$(echo $SNAP | jq -r .status)
  PCT=$(echo $SNAP | jq -r '.progress * 100 | floor')
  echo "[$STATUS] $PCT%"
  case "$STATUS" in
    done|error) break ;;
  esac
  sleep 1
done

5. Baixar o resultado

curl -o merged.mp4 \
  https://videos.dunortetecnologia.com.br/merge/result/$JOB_ID

6. Fluxo completo (script bash)

#!/bin/bash
set -e
URL="https://videos.dunortetecnologia.com.br"

# 1) Submete o job
RESPONSE=$(curl -s -X POST "$URL/merge" \
  -F "videos=@v1.mp4" \
  -F "videos=@v2.mp4" \
  -F "masks=@logo.png" \
  -F 'mask_meta=[{"position":"top-right","scale":0.2,"fullTimeline":true}]')

JOB_ID=$(echo $RESPONSE | jq -r .jobId)
echo "Job iniciado: $JOB_ID"

# 2) Polling até finalizar
while true; do
  SNAP=$(curl -s "$URL/merge/progress/$JOB_ID")
  STATUS=$(echo $SNAP | jq -r .status)
  PCT=$(echo $SNAP | jq -r '.progress * 100 | floor')
  echo -ne "\r[$STATUS] $PCT%   "
  case "$STATUS" in
    done|error) echo; break ;;
  esac
  sleep 1
done

# 3) Baixa o resultado ou imprime erro
if [ "$STATUS" = "done" ]; then
  curl -o merged.mp4 "$URL/merge/result/$JOB_ID"
  echo "Salvo em merged.mp4"
else
  echo "Erro: $(echo $SNAP | jq -r .error)"
  exit 1
fi

Polling de progresso

O FFmpeg atualiza o progresso a aproximadamente 1Hz. Polling mais frequente que isso não traz benefícios — recomenda-se ~600ms. Sempre passe Cache-Control: no-cache ou similar para evitar respostas em cache de proxies.

O campo progress vai de 0 a 1; multiplique por 100 para porcentagem. etaSec só fica disponível durante running e após uma pequena janela inicial.

Limites e validações

  • Vídeos: mínimo 1, máximo 10 arquivos .mp4
  • Tamanho total do upload: máximo 500 MB
  • Máscaras: máximo 10 arquivos .png
  • mask_meta: deve ter o mesmo número de itens que masks
  • start/end: end deve ser maior que start (quando fullTimeline=false)
  • Timeout: 15 minutos por job
  • TTL do resultado: 30 minutos após o término
Para concat sem re-encode (sem máscaras), todos os vídeos devem ter o mesmo codec, resolução e taxa de frames. Quando há máscaras, o re-encode seletivo cuida da padronização dos segmentos afetados.
Visão geral do servidor · atualiza a cada 3s
Carregando...

Jobs recentes

Carregando...

Configuração

Exigir API Key em POST /merge
Quando ativado, requisições sem header X-API-Key válida são rejeitadas com 401. Admins via Basic Auth (UI) sempre podem submeter.

Criar nova API Key

Keys ativas

Carregando...

Configuração

Habilitar rate limit por IP
Quando ativado, cada IP só pode submeter um número limitado de requisições por minuto. Admins (Basic Auth) bypassam o limite.

IPs ativos no último minuto

Carregando...