Instrument x Seth Akkerman
Projects AI Video Ad Performance Analyzer

AI Video Ad Performance Analyzer

A full-stack platform that ingests video ad creatives from YouTube or Google Drive, runs computer vision analysis on each asset, and correlates visual and audio features with real performance metrics from Google Ads and Meta.

Creative teams have always had to guess at why one ad outperforms another. This tool makes that legible. Video Analyzer is an invite-only platform that takes a CSV export from Google Ads or Meta, pulls in the corresponding video or image assets, and runs a battery of computer vision and audio analysis — then surfaces which creative elements actually correlate with performance.

How it works

The architecture is a three-layer stack: a Next.js frontend deployed on Netlify, a Python backend running on Modal.com for serverless GPU-accelerated processing, and Supabase for auth, database, and team management. When a user uploads a CSV, the backend spins up containers that download each asset via yt-dlp, then runs OpenCV, MediaPipe, and EasyOCR across frames — extracting dominant colors, brightness, face counts, on-screen text, motion energy, and audio sentiment via librosa and TextBlob. Results are stored and joined back to the performance data in Supabase.

What you actually see

The dashboard surfaces the analysis in layers: top-level creative performance metrics, a breakdown by campaign, asset-level analysis with the ability to click through and watch each video inline, and an AI-generated observations and recommendations panel. Teams can be organized by brand workspace, with role-based access and invite flows — so an agency can manage multiple clients in one place.

Why it matters

This kind of tooling has historically lived inside expensive enterprise platforms or required a data science team to build and maintain. By connecting off-the-shelf computer vision libraries to a serverless Python backend and a clean authenticated frontend, this becomes something a lean creative team can actually run themselves. The same pattern applies far beyond ads: content performance analysis for organic video, brand consistency audits across campaigns, or competitive creative intelligence — anywhere you need to go from raw video to structured creative insight at scale.

Applying this to your project

  1. Clone the two halves separately. Copy frontend/ and backend/ as independent units — they only talk to each other over HTTP (Netlify → Modal), so you can swap either one out without touching the other. Discard frontend/.next, frontend/node_modules, and backend/__pycache__ — these are build artifacts, not source.
  2. Set up three accounts before touching code: Supabase (database + auth), Modal.com (Python backend hosting), and Netlify (frontend hosting). Copy frontend/.env.example to frontend/.env.local and fill in NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, and MODAL_ENDPOINT. Note: the checked-in code/README.md says to create a Modal secret named supabase-credentials, but the actual code in backend/modal_app.py reads from a secret named video-analyzer-secrets — use the name from the code, not the README.
  3. Run the SQL migrations first. database/migrations/001_initial_schema.sql and 002_user_management.sql must be run in order in the Supabase SQL Editor before anything else works — the app assumes tables like brands, assets, and brand_members already exist.
  4. Swap the analysis logic, keep the plumbing. backend/utils.py and backend/utils_image.py contain the actual video/image scoring logic (color, faces, OCR, hook score, CTA strength). If you’re adapting this for a different kind of analysis, replace the functions inside those two files but keep backend/modal_app.py’s endpoint structure (/analyze, /analyze-image, /aggregate-analysis) and its X-API-Key auth check intact.
  5. Your data plugs in through the CSV upload. The integration point for a new client’s content is the CSV format described in code/README.md — column names like Link, Campaign, Cost, CTR. If your data doesn’t look like ad-performance metrics, you’ll need to rewrite the CSV column-matching logic in the upload page (frontend/src/app/dashboard/[brandId]/upload/page.tsx) rather than the backend.
  6. Rebrand in frontend/src/app/layout.tsx and globals.css. Tailwind classes are used inline throughout, so there’s no central theme file — search-and-replace color utility classes (e.g. blue-500) if you want a different palette.
  7. Gotcha: recharts is installed but unused. The dependency is in package.json, but every chart in the current dashboard (gauges, color bars, composition grids) is hand-rolled inline SVG. If you want real chart components, either wire up recharts properly or keep extending the custom SVG approach already in frontend/src/app/dashboard/[brandId]/asset/[assetId]/page.tsx.
  8. Gotcha: the supabase Python package in requirements.txt is dead weight. The backend actually talks to Supabase via raw httpx REST calls, not the official client library — don’t be surprised when you can’t find a create_client() call in modal_app.py.
  9. Gotcha: npm run dev lies about correctness. It skips TypeScript type-checking; only npm run build (what Netlify actually runs) will catch type errors. Always run npm run build locally before pushing.
  10. Regenerate the shared secret if you rotate keys. MODAL_INTERNAL_API_KEY must be identical in both the Netlify environment variables and the Modal secret — generate it with openssl rand -hex 32 and update both places together, or the backend will reject every request with “Invalid or missing API key.”

Other ways to repurpose this

Product Design

Web Design

Marketing

Branding

Dependencies

next 14.1.0 App Router framework powering the dashboard, upload flow, and all API routes (e.g. /api/aggregate, /api/webhook) that proxy requests to the Modal backend.
react 18.3.1 UI runtime for every dashboard, upload, and asset-detail screen.
react-dom 18.3.1 DOM renderer paired with React 18 for the Next.js app.
@supabase/ssr 0.1.0 Creates the browser-side Supabase client (lib/supabase.ts) used for session-aware auth and data reads directly from the client.
@supabase/supabase-js 2.91.0 Server-side Supabase client used inside API routes and lib/auth.ts to verify sessions, check brand membership, and read/write brand, invite, and asset records.
papaparse 5.5.3 Parses the user-uploaded CSV of ad performance metrics (cost, impressions, CTR, CPI, etc.) in the upload page before rows are dispatched for analysis.
recharts 2.15.4 Listed as a charting dependency, but the current dashboard and asset-detail visualizations (gauges, color bars, composition grids) are hand-rolled inline SVG rather than Recharts components — effectively unused in this snapshot.
@netlify/plugin-nextjs 5.15.5 Netlify build plugin that adapts the Next.js app (API routes, SSR) to Netlify's hosting model, configured in netlify.toml.
typescript 5.9.3 Type-checks the frontend; next build (what Netlify runs) fails on type errors that npm run dev silently skips.
tailwindcss 3.4.19 Utility-CSS framework used for all dashboard and form styling.
autoprefixer 10.4.23 PostCSS plugin adding vendor prefixes to the Tailwind-generated CSS during build.
opencv-python-headless 4.9.0.80 Core image/video decoding and pixel analysis (color, brightness, saturation, contrast) for both video frames and static images.
mediapipe 0.10.11 Face/people detection used to compute the "people_metrics" (face count and location) in both video and image analysis.
easyocr 1.7.1 Extracts on-screen text from video frames and images for the OCR/text-analysis and CTA-detection features.
scikit-image 0.22.0 Supplies saliency/measure utilities used for negative-space and composition scoring.
scikit-learn 1.4.0 KMeans clustering to extract dominant color palettes from frames and images.
librosa 0.10.1 Audio feature extraction (tempo, energy) feeding the "audio_analysis" and tone classification for video assets.
pydub 0.25.1 Extracts and manipulates the audio track from downloaded videos before librosa analysis.
scipy 1.12.0 Pearson correlation calculations (safe_correlation) powering the /aggregate-analysis endpoint's spend-vs-performance correlations.
textblob 0.18.0 Sentiment scoring of OCR-extracted text for the tone/CTA analysis.
spacy 3.7.4 NLP processing of extracted ad copy text for language/content analysis.
langdetect 1.0.9 Detects the language of OCR-extracted text before sentiment/NLP scoring.
pandas 2.2.0 Loads and transforms the uploaded CSV rows into the dataset used for per-asset analysis and aggregate stats.
numpy 1.26.4 Underlying numeric arrays for all image/audio processing; results must be explicitly cast to native Python types (convert_numpy_types) before JSON/DB storage.
yt-dlp >=2024.1.0 Downloads YouTube-hosted ad creatives so they can be analyzed frame-by-frame (currently blocked by YouTube bot detection for most videos).
google-api-python-client 2.118.0 Searches shared Google Drive folders for the matching creative file (e.g. 1x1.mp4/1x1.png) referenced in the uploaded CSV.
google-auth 2.28.0 Authenticates the Modal backend against Google Drive using a service-account credential.
httpx 0.27.0 Makes direct REST calls to Supabase's PostgREST API from the Modal backend (used instead of the supabase Python client, which is listed in requirements.txt but not actually imported).
Role
Design Engineer
Tools
Next.js TypeScript Tailwind CSS Modal.com Supabase OpenCV MediaPipe EasyOCR Python Netlify yt-dlp
Video thumbnail for AI Video Ad Performance Analyzer