Multimodal AI pipeline for automated video analysis, transcription, and semantic archive search — built for broadcast media and publishing industries.
| Service | URL |
|---|---|
| Streamlit UI | http://34.107.34.179:8501 |
| FastAPI API | http://34.107.34.179:8000 |
| API Docs | http://34.107.34.179:8000/docs |
Deployed on GCP VM — europe-west3 (Frankfurt, Germany)
Step 1 — Open the URL
Visit http://34.107.34.179:8501 in your browser. First load takes 20-30 seconds — models are loading on the server.
Step 2 — Upload a video
- Click Process Video in the sidebar
- Click Upload and select a video from your laptop
- Recommended: under 7 minutes, under 200MB
- Supported formats: MP4, MKV, AVI, MOV, WEBM
- Click ▶ Process Video
- Processing takes 1-2 minutes (transcription + analysis + indexing)
Step 3 — View results After processing you will see:
- Language detected (DE, EN, etc.)
- Broadcast type (news bulletin, interview, etc.)
- Full summary in English
- Main topics covered
- Key stories with exact timestamps
Step 4 — Search the archive
- Click Search Archive in the sidebar
- Your processed video is automatically selected
- Type any question in natural language or keywords
- Works in any language — English query finds German content
- Press Enter or click 🔍 Search
- You get a direct 🤖 Answer (2-3 sentences) + 📚 Sources with relevance scores and timestamps
Step 5 — Switch between videos
- Previously processed videos appear in Processed Videos section in sidebar
- Click any video to switch — search results update automatically
- Video history persists across browser sessions
Important notes:
- Answer generation uses Gemini API — limited to 20 calls/day on free tier
- If answer shows "API quota exceeded" — sources are still shown correctly
- Dangerous or off-topic queries are blocked by guardrails automatically
- All data persists on server — no need to re-upload previously processed videos
Media Intelligence Agent transforms raw broadcast video into structured, searchable knowledge. It combines speech recognition, computer vision, and large language models to automatically:
- Transcribe speech from any video in 99 languages
- Extract meaningful keyframes using multimodal scene detection
- Generate editorial summaries and identify key stories with timestamps
- Index all content for semantic search — supporting both natural language and keyword queries
- Block harmful or irrelevant queries using local semantic guardrails
- Expose everything via a production REST API and Streamlit UI
Tested on real Tagesschau (ARD) broadcasts — correctly identifies German language, topics and key stories with precise timestamps.
Video Upload (from browser or API)
│
├── FFmpeg ──────────────► Audio (WAV 16kHz mono)
│ │
│ Whisper ASR (local)
│ - 99 languages auto-detected
│ - Timestamped segments
│ │
├── OpenCV ─────────────► Keyframes (JPG)
│ Multimodal detection: │
│ - Visual: HSV histogram │
│ - Audio: speech boundaries │
│ │
│ Gemini 3 Flash Preview
│ Multimodal analysis:
│ - Overall summary
│ - Main topics
│ - Key stories + timestamps
│ - Broadcast type detection
│ - MD5 caching (instant on repeat)
│ │
│ Qdrant Hybrid Search Index
│ - Dense vectors (semantic, 384-dim)
│ - Sparse vectors (BM25 keyword)
│ - RRF fusion
│ - CrossEncoder reranking
│ - Multilingual search
│ │
│ LangGraph Orchestration
│ - 5 nodes with conditional edges
│ - Retry logic (3 attempts)
│ - Error handling + graceful stop
│ - LangSmith tracing
│ │
│ ┌────────────────┴────────────────┐
│ │ │
│ FastAPI REST API Streamlit UI
│ - POST /pipeline/upload - Upload video
│ - POST /pipeline/process - View results
│ - POST /search - Search archive
│ - GET /health - Video history
│ - Guardrails - Answer generation
│ - Rate limiting - Switch videos
│ - Input validation
| Component | File | Status |
|---|---|---|
| Audio Extraction | app/pipeline/audio_extractor.py |
✅ Tested |
| Speech Recognition | app/pipeline/transcriber.py |
✅ Tested |
| Frame Extraction | app/pipeline/frame_extractor.py |
✅ Tested |
| Video Analysis | app/pipeline/gemini_analyser.py |
✅ Tested |
| RAG Indexer | app/rag/indexer.py |
✅ Tested |
| RAGAS Evaluator | app/rag/evaluator.py |
✅ Tested |
| Pipeline State | app/agents/state.py |
✅ Tested |
| Pipeline Nodes | app/agents/nodes.py |
✅ Tested |
| Pipeline Graph | app/agents/graph.py |
✅ Tested |
| Pipeline Agent | app/agents/pipeline_agent.py |
✅ Tested |
| FastAPI REST API | app/api/main.py |
✅ Tested |
| API Routes (health, pipeline, search) | app/api/routes/ |
✅ Tested |
| Semantic Guardrails | app/api/guardrails.py |
✅ Tested |
| Streamlit UI | app/ui/main.py |
✅ Tested |
Multimodal Scene Detection Combines HSV histogram comparison with Whisper speech boundaries for keyframe extraction. In Tagesschau broadcasts — 84% of keyframes confirmed by both visual and audio signals.
Adaptive Processing
- Videos < 10 minutes → single Gemini call (faster, cheaper)
- Videos ≥ 10 minutes → chunked parallel processing + hierarchical summarisation
- MD5 caching — repeated video analysis returns instantly (0.04s)
Two-Stage Hybrid Search Stage 1: Hybrid retrieval — BM25 sparse + dense semantic vectors combined with RRF fusion — retrieves top 20 candidates. Stage 2: CrossEncoder reranking — reads query and document together for precise relevance scoring — selects top results.
Handles:
- Natural language: "minister who lied about toll road"
- Keywords: "Scheuer PKW Maut"
- Cross-language: English query → German content ✅
LangGraph Orchestration
- 5 nodes connected with conditional edges
- Retry logic — transient failures retried 3 times automatically
- Error handling — pipeline stops gracefully on failure
- LangSmith tracing — every node execution tracked with timing
Local Semantic Guardrails
- Uses existing sentence-transformers model — zero API calls
- Blocks harmful queries (weapons, hacking, illegal content)
- Blocks prompt injection attempts
- Returns clear user-friendly error messages
- Works 24/7 regardless of API quota status
Streamlit UI
- Upload any video from laptop directly
- Process video — see summary, topics, stories with timestamps
- Search archive with natural language or keywords
- Generated answers using Gemini (2-3 sentences, based only on video content)
- Video history in sidebar — switch between multiple processed videos
- Persistent history — survives browser refresh
Production REST API
POST /api/pipeline/upload— upload and process video filePOST /api/pipeline/process— process video by file pathPOST /api/search— search indexed archiveGET /api/health— health check for monitoring- Input validation — 422 for invalid requests
- Rate limiting — 10 requests per minute per IP
- OpenAPI docs at
/docs
Evaluated on real Tagesschau (ARD) German broadcast content using RAGAS framework.
| Metric | Score | Meaning |
|---|---|---|
| Faithfulness | 1.000 | Zero hallucination — answer contains only retrieved content ✅ |
| Context Precision | 1.000 | All retrieved chunks are relevant to the query ✅ |
| Context Recall | 1.000 | All relevant content is retrieved — nothing missed ✅ |
| Answer Relevancy | 0.808 | Answer is relevant to question ✅ |
Faithfulness: 1.000 Answer is built directly from retrieved content — Gemini adds nothing from its own training data. Zero hallucination. Most critical metric for a media archive system where accuracy is essential.
Context Precision: 1.000 — improved from 0.887 Before CrossEncoder reranking — some irrelevant documents appeared in top results. After CrossEncoder two-stage retrieval — all returned documents are genuinely relevant.
Context Recall: 1.000 — improved from 0.500 Before CrossEncoder — hybrid search returned top 5 candidates directly. Relevant content ranked 6th-10th was never seen. After CrossEncoder — system fetches top 20 candidates first, then reranks — nothing relevant is missed.
Answer Relevancy: 0.808 Answer generated by Gemini from retrieved context — focused 2-3 sentence response directly addressing the question. Improved from 0.637 (raw context) to 0.808 (generated answer).
| Category | Technology |
|---|---|
| Speech Recognition | OpenAI Whisper (local, base model) |
| Multimodal LLM | Google Gemini 3 Flash Preview |
| Vector Database | Qdrant (local mode) |
| Embeddings | paraphrase-multilingual-MiniLM-L12-v2 (384 dim) |
| Keyword Search | BM25 (rank-bm25, persisted to disk) |
| Reranking | CrossEncoder ms-marco-TinyBERT-L-2-v2 |
| Agent Orchestration | LangGraph |
| Observability | LangSmith |
| REST API | FastAPI + uvicorn |
| UI | Streamlit |
| Guardrails | Local sentence-transformers (zero API calls) |
| Evaluation | RAGAS |
| Rate Limiting | slowapi |
| Testing | pytest (75 tests) |
| Deployment | GCP VM — europe-west3 Frankfurt |
| Process Manager | Supervisor (auto-restart) |
| Containerisation | Docker (Dockerfile included) |
- Python 3.10+
- FFmpeg (
conda install -c conda-forge ffmpegon Mac) - Google Gemini API key — free at aistudio.google.com
- LangSmith API key — free at smith.langchain.com
git clone https://github.com/Keshav0781/media-intelligence-agent.git
cd media-intelligence-agent
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Add your API keys to .envGEMINI_API_KEY=your_gemini_api_key
GEMINI_MODEL=models/gemini-3-flash-preview
LANGSMITH_API_KEY=your_langsmith_api_key
LANGSMITH_PROJECT=media-intelligence-agent
LANGCHAIN_TRACING_V2=true# Run all tests except Gemini analyser (saves API quota)
python -m pytest -v --ignore=app/pipeline/test_gemini_analyser.py
# Run all tests including Gemini analyser
python -m pytest -v# Terminal 1 — API server
python -m uvicorn app.api.main:app --host 0.0.0.0 --port 8000 --reload
# Terminal 2 — Streamlit UI
streamlit run app/ui/main.py- UI: http://localhost:8501
- API: http://localhost:8000
- API Docs: http://localhost:8000/docs
# Upload and process a video file
curl -X POST http://localhost:8000/api/pipeline/upload \
-F "file=@data/test_video.mp4"
# Process by file path
curl -X POST http://localhost:8000/api/pipeline/process \
-H "Content-Type: application/json" \
-d '{"video_path": "data/test_video.mp4"}'curl -X POST http://localhost:8000/api/search \
-H "Content-Type: application/json" \
-d '{"video_hash": "YOUR_VIDEO_HASH", "query": "What happened with Scheuer?", "limit": 5}'media-intelligence-agent/
├── app/
│ ├── pipeline/
│ │ ├── audio_extractor.py
│ │ ├── transcriber.py
│ │ ├── frame_extractor.py
│ │ ├── gemini_analyser.py
│ │ ├── test_audio_extractor.py
│ │ ├── test_transcriber.py
│ │ ├── test_frame_extractor.py
│ │ └── test_gemini_analyser.py
│ ├── rag/
│ │ ├── indexer.py
│ │ ├── evaluator.py
│ │ └── test_indexer.py
│ ├── agents/
│ │ ├── state.py
│ │ ├── nodes.py
│ │ ├── graph.py
│ │ ├── pipeline_agent.py
│ │ └── test_pipeline_agent.py
│ ├── api/
│ │ ├── main.py
│ │ ├── guardrails.py
│ │ ├── test_api.py
│ │ ├── routes/
│ │ │ ├── health.py
│ │ │ ├── pipeline.py
│ │ │ └── search.py
│ │ └── models/
│ │ ├── requests.py
│ │ └── responses.py
│ ├── ui/
│ │ └── main.py
│ ├── config.py
│ └── logger.py
├── tests/
│ └── test_pipeline_integration.py
├── config.yaml
├── conftest.py
├── pytest.ini
├── Dockerfile
├── requirements.txt
└── .env.example
All settings centralized in config.yaml — no hardcoded values in code:
pipeline:
frames:
histogram_threshold: 0.4
min_interval_seconds: 2.0
gemini:
chunk_size_seconds: 30.0
direct_analysis_threshold: 600.0
rag:
embedding_model: paraphrase-multilingual-MiniLM-L12-v2
vector_dim: 384
reranker_model: cross-encoder/ms-marco-TinyBERT-L-2-v2
reranker_candidates: 20GET /api/health
POST /api/pipeline/process
Body: {"video_path": "data/test_video.mp4"}
POST /api/pipeline/upload
Form: file=<video file>
Max size: 200MB
Supported: .mp4, .mkv, .avi, .mov, .webm
POST /api/search
Body: {
"video_hash": "03b26d23864237a264d0e262ccbb655c",
"query": "What happened with Scheuer?",
"limit": 5,
"content_type": "story" // optional: segment, story, summary, topic
}
Keshav Jha — AI Engineer, Erlangen Germany LinkedIn | GitHub