Skip to content

Video Upload Pipeline

Upload Flow

  1. File selection -- FileUploader.vue accepts video files via drag-and-drop or file picker
  2. Client-side validation -- checks file size (VIDEOSIZE.limit) and duration (VIDEODURATION.limit)
  3. Metadata extraction -- reads video duration, width, height, aspect ratio from the file
  4. Auto-streaming -- streaming is automatically enabled for videos longer than 4 seconds
  5. Bunny upload token -- chatbotService.getBunnyUploadToken() calls backend to get TUS upload credentials
  6. TUS upload -- file uploaded to https://video.bunnycdn.com/tusupload using TUS resumable protocol
  7. Transcoding -- Bunny CDN transcodes the video into multiple renditions (H.264 + AV1, various bitrates)
  8. Status polling -- videoProxy.ts polls /upload_video/status until encoding completes
  9. M3U8 parsing -- streamParser.ts fetches and parses the HLS master playlist to extract stream data

Bunny Transcoding Status Codes

CodeStatus
0Created
1Uploaded
2Processing
3Transcoding
4Finished
5Error
6Upload Failed
7JIT Segmenting
8JIT Playlists Created

Soft Progress Bar

The upload status polling in videoProxy.ts uses a "soft progress" algorithm. Instead of jumping between status checkpoints, the progress bar eases toward the actual target percentage and drifts slightly past it, giving a smooth visual experience. It never reaches 100% until the transcoding is actually finished.

Stream Parser Output

After transcoding, streamParser(streamId) fetches the HLS manifest and returns:

typescript
{
  baseUrl: string,
  length: number,           // duration in seconds
  bufferTime: number,
  audio: {
    codecs: string,
    bandwidth: number,
    timescale: number,
    duration: number,
    init: string,           // e.g. "aac_128k/init-stream0.m4s"
    media: string
  },
  video: [                  // sorted by bandwidth ascending
    {
      codecs: string,       // e.g. "avc1.64001f" (H.264) or "av01.0.08M.08" (AV1)
      bandwidth: number,
      height: number,
      width: number,
      duration: number,
      timescale: number,
      init: string,         // e.g. "avc1_300k/init-stream0.m4s" or "av01_300k/init-stream0.m4s"
      media: string
    },
    // ... more renditions at increasing bitrates
  ]
}

The init URI prefix reveals the codec and bitrate: avc1_300k/ = H.264 at 300kbps, av01_300k/ = AV1 at 300kbps. This is how the codebase distinguishes between codec types when filtering.

API Endpoints

POST   get_upload_url/bunny     -> { token, expiration, videoId, libraryId, collectionId }
DELETE /api/chatbot/video/{id}  -> removes video from Bunny library
POST   /creatives/{id}/migrate-videos -> backend migration (not yet used in AF)

Video Delete

When a video is removed from a block, chatbotService.deleteVideo(streamId) is called to clean up the Bunny library entry. This prevents orphaned videos from accumulating storage costs.

Internal documentation