Eyevinn Auto Subtitles

How It's Built

This demo site was built from create-next-app to production in a single session using Claude Code and deployed on Eyevinn Open Source Cloud.

The Team

RoleAgentFocus
DeveloperClaude Opus 4Full-stack implementation, debugging, deployment
HumanbirmeArchitecture decisions, bug identification, OSC service management

By the Numbers

2,965

Lines of code

22

Commits

28

Source files

6

OSC services

~5h

Wall-clock time

1

Session

From npx create-next-app to production deploy in a single afternoon.

Architecture

Browser
  │
  ├─ Upload video ──────► MinIO (uploads bucket)
  │    (presigned URLs,       │
  │     20MB chunks)          │
  │                           ▼
  ├─ POST /api/process ──► Pipeline
  │                           │
  │                    ┌──────┴──────┐
  │                    ▼             ▼
  │              eyevinn-       eyevinn-
  │              ffmpeg-s3      ffmpeg-s3
  │              (clip)         (thumbnail)
  │                    │             │
  │                    ▼             ▼
  │              MinIO clips   MinIO thumbnails
  │                    │
  │                    ▼
  │              eyevinn-auto-subtitles
  │              (Whisper / GPT-4o)
  │                    │
  │                    ▼
  │              MinIO subtitles + jobs
  │                    │
  ├─ GET /result/id ◄──┘
  │    (video + VTT + metrics
  │     + standard scores)
  │
  └─ Share link ──► OG image from thumbnail

Open Source Cloud Services

ServicePurpose
eyevinn-web-runnerHosts the Next.js app from a GitHub repo. Rebuilds on push.
eyevinn-auto-subtitlesAI transcription service. Accepts video URL, returns WebVTT via Whisper/GPT-4o.
eyevinn-ffmpeg-s3Ephemeral FFmpeg instances that read/write S3. Used for clip extraction and thumbnail generation.
minio-minioS3-compatible object storage. 5 buckets: uploads, clips, subtitles, thumbnails, jobs, feedback.
eyevinn-app-config-svcParameter store for runtime config. Bypasses broken shell export of JWTs in web-runner entrypoint.
valkey-io-valkeyBacking store for app-config-svc.

Features Delivered

  • Video upload with chunked multipart (20MB parts, presigned URLs direct to MinIO)
  • URL input for online videos
  • Video preview with scrubbing to select clip start position
  • 3-minute clip extraction via eyevinn-ffmpeg-s3
  • AI transcription via eyevinn-auto-subtitles (Whisper-1 / GPT-4o)
  • WebVTT subtitle generation with download
  • In-browser video player with synced subtitle track
  • Quality scoring engine (CPS, CPL, timing, overlap analysis)
  • Side-by-side compliance scoring against Netflix, BBC, and EBU standards
  • Shareable result pages with 4-day retention
  • Dynamic OG images extracted from video for social sharing
  • Thumbnail extraction at 5s mark via FFmpeg
  • User feedback (thumbs up/down + comment) stored in MinIO
  • Automatic housekeeping of old clips/subtitles/thumbnails/jobs every hour
  • Job persistence to MinIO surviving app restarts
  • SEO: JSON-LD structured data, FAQ schema, robots.txt, sitemap, canonical URLs
  • Source file cleanup after clip extraction
  • 14 language support

Key Debugging Moments

Shell-corrupted JWT tokens

The web-runner entrypoint shell export mangled the OSC access token (336 chars instead of 308). The config loader was set to skip already-set env vars. Fix: always override from app-config-svc.

FFmpeg can't detect .mp4 from presigned URLs

Presigned URLs have query parameters that obscure the file extension. FFmpeg couldn't auto-detect the output format. Went through three iterations: -f mp4, then frag_keyframe+empty_moov, then mpegts, before the upstream fix landed.

ffmpeg-s3 staging directory bug

The service's rewriteCmdString() replaced the S3 output URL with a presigned URL before the local staging path replacement could match it. Output went directly to S3 but the service expected it in a local directory. Filed issue #7; maintainer fixed it same day.

Auto-subtitles 401 Unauthorized

The transcription service requires a Service Access Token (SAT) via x-jwt: Bearer header, obtained from the OSC token endpoint. Initial implementation was missing this auth layer entirely.

Uppercase S3:// workaround that didn't work

Tried S3:// (uppercase) to bypass the presigned URL bug. It bypassed the check but toUrl() treated it as a file:// protocol. Upload to S3 silently failed. Waited for upstream fix instead.

CORS on direct MinIO uploads

Initially tried proxying uploads through the backend, but web-runner has a 5MB body limit. Switched to presigned URL pattern (browser uploads directly to MinIO) with chunked multipart, matching the STTV+ approach.

In-memory job loss on restart

Jobs were stored in a JavaScript Map and lost on every deploy. Shared result URLs would 404 after restart. Added MinIO-backed persistence for completed jobs while keeping the Map as a hot cache for active processing.

Tech Stack

Frontend

Next.js 16, React 19, Tailwind CSS 4

Backend

Next.js API Routes, TypeScript 5

Storage

MinIO (S3-compatible), AWS SDK

AI

OpenAI Whisper-1, GPT-4o Transcribe

Media Processing

FFmpeg via eyevinn-ffmpeg-s3

SDK

@osaas/client-core

Built with AI + Open Source

This entire application was built by a Claude Code agent with human guidance in a single session. The infrastructure runs entirely on open source services via Eyevinn Open Source Cloud.