Key Design
Video Splitting
Video files are split into HLS segments via AWS Lambda for streaming playback.
Pipeline
Raw video in staging
│
├─ 1. Download from S3: {owner}/{project}/staging/{hash}
├─ 2. Probe fps with ffprobe
├─ 3. Split with ffmpeg (copy codec, no re-encode)
│ -c:v copy -an -f hls -hls_time 6
├─ 4. Parse segment durations from ffmpeg m3u8
├─ 5. Hash each .ts segment (SHA256[:16])
│ Upload to chunks/{hash}.ts (skip if exists = dedup)
├─ 6. Build stream m3u8 with bare hex hashes
├─ 6.5. Generate thumbnail + sprite sheet (non-fatal)
├─ 7. Upload playlist: videos/{id}/stream/{streamHash}.m3u8
├─ 8. Update videos/{id}/meta.json
└─ 9. Callback PATCH to BSSffmpeg Command
ffmpeg -i input.mp4 \
-c:v copy \ # no re-encode — fast
-an \ # strip audio (separate pipeline)
-f hls \
-hls_time 6 \ # 6-second segments
-hls_list_size 0 \ # keep all segments
-hls_segment_type mpegts \
-hls_flags independent_segments \
-hls_segment_filename seg_%05d.ts \
out.m3u8Key: -c:v copy means no transcoding — the Lambda just repackages frames into TS segments. This is fast and lossless.
Chunk Deduplication
Each TS segment is content-addressed:
hash = SHA256(segment_bytes)[:16] # 16 hex chars
key = chunks/{hash}.tsBefore uploading, Lambda checks if the chunk already exists via HeadObject. Duplicate segments (common in re-uploads or similar content) are skipped. Chunks are uploaded with Cache-Control: public, max-age=31536000, immutable.
M3U8 Format
Stream playlists store bare hex hashes — no URLs, no extensions:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:6.000,
a1b2c3d4e5f67890
#EXTINF:4.500,
1234567890abcdef
#EXT-X-ENDLISTBSS's rewriteM3u8() replaces each hash with a full CDN URL at serving time. This keeps playlists portable across CDN configurations.
Thumbnail & Sprite
Generated alongside HLS segments (non-fatal — skipped on error):
| Asset | Resolution | Timing | S3 Path |
|---|---|---|---|
| Thumbnail | 640px wide | Single frame at ~10% of duration | videos/{id}/thumb.jpg |
| Sprite | 160x90 tiles, max 10 columns | 1 frame every 5 seconds | videos/{id}/sprite.jpg |
Meta.json After Splitting
{
"length": 750,
"fps": 30.0,
"durationSec": 25.0,
"streams": ["abc123def456"],
"thumbnailUrl": "https://cdn.../thumb.jpg",
"spriteUrl": "https://cdn.../sprite.jpg",
"spriteInterval": 5,
"spriteCols": 5,
"spriteRows": 1,
"spriteWidth": 160,
"spriteHeight": 90,
"updatedAt": "2026-04-14T..."
}