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