SuperAPI AI Directory

golang-video-dash

Coding & development

View repository

Updated 10-15 20:31

About this skill

Golang Video DASH

Progressive MP4 is not adaptive streaming. Transcode off the HTTP request; serve .mpd + CMAF segments. Resumable upload: tus-golang-nextjs.

Stack and scope

Part Role
FFmpeg Decode, encode, muxer dash
ffprobe Duration, codecs, resolution
Job/worker Post-upload queue (golang-goroutines)
Object storage / disk DASH artifacts
Gin Static/range for segments (if the app serves them)

VOD (complete file) is the default. Live DASH/HLS is out of scope unless the project is already live.

Pipeline

MP4 (TUS complete) → probe → transcode DASH → store → publicar URL do .mpd
  1. Upload finishes (TUS hook PreFinish / PostFinish).
  2. Enqueue job with videoID; respond 202 — do not block the handler.
  3. Worker: probe → FFmpeg with timeout/context → write manifest.mpd + init-*.m4s + chunk-*.m4s.
  4. Mark status ready / failed; point the player only to the .mpd when ready.

Non-negotiable rules

  • FFmpeg via exec.CommandContext — cancel on shutdown and on job timeout.
  • Never transcode in the Gin handler goroutine without a queue + concurrency limit.
  • Paths: opaque IDs; never concatenate the client filename into exec.
  • Validate codec/container in the probe; reject unsupported formats before encoding.
  • Atomic segments + manifest: publish the .mpd only when the job completes (staging dir → rename/upload).
  • Correct MIME types: application/dash+xml (.mpd), video/iso.segment or video/mp4 (.m4s).

FFmpeg — MP4 → MPEG-DASH

Minimal ladder (adjust to the product): 360p / 720p / 1080p, stereo AAC, H.264 +faststart equivalent in CMAF (separate init).

ffmpeg -y -i "$IN" -hide_banner -loglevel error \
  -filter_complex "[0:v]split=3[v360][v720][v1080]; \
    [v360]scale=w=640:h=360:force_original_aspect_ratio=decrease:force_divisible_by=2[v360o]; \
    [v720]scale=w=1280:h=720:force_original_aspect_ratio=decrease:force_divisible_by=2[v720o]; \
    [v1080]scale=w=1920:h=1080:force_original_aspect_ratio=decrease:force_divisible_by=2[v1080o]" \
  -map "[v360o]" -map "[v720o]" -map "[v1080o]" -map 0:a:0? \
  -c:v libx264 -preset veryfast -profile:v high -pix_fmt yuv420p \
  -c:a aac -b:a 128k -ac 2 \
  -b:v:0 400k -maxrate:v:0 600k -bufsize:v:0 800k \
  -b:v:1 1500k -maxrate:v:1 2000k -bufsize:v:1 3000k \
  -b:v:2 4000k -maxrate:v:2 5000k -bufsize:v:2 8000k \
  -g 48 -keyint_min 48 -sc_threshold 0 \
  -adaptation_sets "id=0,streams=v id=1,streams=a" \
  -use_timeline 1 -use_template 1 -seg_duration 4 \
  -init_seg_name "init-\$RepresentationID\$.m4s" \
  -media_seg_name "chunk-\$RepresentationID\$-\$Number%05d\$.m4s" \
  -f dash "$OUT/manifest.mpd"
  • GOP aligned to seg_duration (e.g., 4s @ 24fps → -g 96 if fps is 24; derive from the probe).
  • No audio: omit -map 0:a based on the probe — do not let FFmpeg fail opaquely.
  • libx265/av1 only if the player and CPU cost are acceptable; H.264 is the compatibility default.
  • Keep the command in versioned config/templates; log the exit code and truncated stderr.

Job in Go

cmd := exec.CommandContext(ctx, "ffmpeg", args...)
cmd.Dir = workDir
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
    return fmt.Errorf("ffmpeg: %w: %s", err, truncate(stderr.String(), 4<<10))
}
  • SetLimit in the worker pool (1–N according to CPU/GPU).
  • Disk: quota per job; clean up workDir in the defer after uploading the artifacts.
  • Idempotency: the same videoID does not trigger two encodes (lock/status processing).
  • Optional progress: parse -progress pipe:1 if the UX asks for a percentage.

Serving

  • CDN / object storage in front of the .m4s; Go origin only if the volume is low.
  • CORS if the player (Next.js) is on another host: GET/HEAD on the artifacts.
  • Cache-Control: manifest.mpd short if the job can republish; immutable segments immutable.
  • Do not use http.ServeFile from a directory with a user path without an allowlist of videoID.
  • Player: dash.js / Shaka on the frontend; public URL only from .mpd (+ token if the VOD is private).

Anti-patterns

  • Synchronous transcode in the POST upload
  • ffmpeg with args interpolated from the original filename
  • A single “1080p” bitrate sold as DASH
  • Serving the original MP4 in the player “while DASH isn't ready” without an explicit product
  • Misaligned GOP/segments (broken seeking)
  • Unbounded FFmpeg queue

Definition of done

  • Asynchronous job with context, limit and status
  • .mpd + init/media segments published atomically
  • ABR ladder and documented codecs
  • Correct MIME, CORS and cache
  • No path traversal / injection in FFmpeg
  • Player can do ABR and seek on the generated artifact