From 9bddaed5d14b944987916608fd7e8cf5801dca86 Mon Sep 17 00:00:00 2001 From: jlightner Date: Sun, 29 Mar 2026 22:00:41 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20Created=20comprehensive=20README.md=20w?= =?UTF-8?q?ith=20architecture=20diagram,=20setup=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "README.md" - "tests/fixtures/sample_transcript.json" GSD-Task: S01/T05 --- README.md | 279 ++++++++++++++++++++++++++ tests/fixtures/sample_transcript.json | 148 ++++++++++++++ 2 files changed, 427 insertions(+) create mode 100644 README.md create mode 100644 tests/fixtures/sample_transcript.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbe58a1 --- /dev/null +++ b/README.md @@ -0,0 +1,279 @@ +# Chrysopedia + +> From *chrysopoeia* (alchemical transmutation of base material into gold) + *encyclopedia*. +> Chrysopedia transmutes raw video content into refined, searchable production knowledge. + +A self-hosted knowledge extraction and retrieval system for electronic music production content. Transcribes video libraries with Whisper, extracts key moments and techniques with LLM analysis, and serves a search-first web UI for mid-session retrieval. + +--- + +## Architecture + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Desktop (GPU workstation) │ +│ ┌──────────────┐ │ +│ │ whisper/ │ Transcribes video → JSON (Whisper large-v3) │ +│ │ transcribe.py │ Runs locally with CUDA, outputs to /data │ +│ └──────┬───────┘ │ +│ │ JSON transcripts │ +└─────────┼────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ Docker Compose (xpltd_chrysopedia) — Server (e.g. ub01) │ +│ │ +│ ┌────────────────┐ ┌────────────────┐ ┌──────────────────┐ │ +│ │ chrysopedia-db │ │chrysopedia-redis│ │ chrysopedia-api │ │ +│ │ PostgreSQL 16 │ │ Redis 7 │ │ FastAPI + Uvicorn│ │ +│ │ :5433→5432 │ │ │ │ :8000 │ │ +│ └────────────────┘ └────────────────┘ └────────┬─────────┘ │ +│ │ │ +│ ┌──────────────────┐ ┌──────────────────────┐ │ │ +│ │ chrysopedia-web │ │ chrysopedia-worker │ │ │ +│ │ React + nginx │ │ Celery (LLM pipeline)│ │ │ +│ │ :3000→80 │ │ │ │ │ +│ └──────────────────┘ └──────────────────────┘ │ │ +│ │ │ +│ Network: chrysopedia (172.24.0.0/24) │ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### Services + +| Service | Image / Build | Port | Purpose | +|----------------------|------------------------|---------------|--------------------------------------------| +| `chrysopedia-db` | `postgres:16-alpine` | `5433 → 5432` | Primary data store (7 entity schema) | +| `chrysopedia-redis` | `redis:7-alpine` | — | Celery broker / cache | +| `chrysopedia-api` | `docker/Dockerfile.api`| `8000` | FastAPI REST API | +| `chrysopedia-worker` | `docker/Dockerfile.api`| — | Celery worker for LLM pipeline stages 2-5 | +| `chrysopedia-web` | `docker/Dockerfile.web`| `3000 → 80` | React frontend (nginx) | + +### Data Model (7 entities) + +- **Creator** — artists/producers whose content is indexed +- **SourceVideo** — original video files processed by the pipeline +- **TranscriptSegment** — timestamped text segments from Whisper +- **KeyMoment** — discrete insights extracted by LLM analysis +- **TechniquePage** — synthesized knowledge pages (primary output) +- **RelatedTechniqueLink** — cross-references between technique pages +- **Tag** — hierarchical topic/genre taxonomy + +--- + +## Prerequisites + +- **Docker** ≥ 24.0 and **Docker Compose** ≥ 2.20 +- **Python 3.10+** (for the Whisper transcription script) +- **ffmpeg** (for audio extraction) +- **NVIDIA GPU + CUDA** (recommended for Whisper; CPU fallback available) + +--- + +## Quick Start + +### 1. Clone and configure + +```bash +git clone +cd content-to-kb-automator + +# Create environment file from template +cp .env.example .env +# Edit .env with your actual values (see Environment Variables below) +``` + +### 2. Start the Docker Compose stack + +```bash +docker compose up -d +``` + +This starts PostgreSQL, Redis, the API server, the Celery worker, and the web UI. + +### 3. Run database migrations + +```bash +# From inside the API container: +docker compose exec chrysopedia-api alembic upgrade head + +# Or locally (requires Python venv with backend deps): +alembic upgrade head +``` + +### 4. Verify the stack + +```bash +# Health check (with DB connectivity) +curl http://localhost:8000/health + +# API health (lightweight, no DB) +curl http://localhost:8000/api/v1/health + +# Docker Compose status +docker compose ps +``` + +### 5. Transcribe videos (desktop) + +```bash +cd whisper +pip install -r requirements.txt + +# Single file +python transcribe.py --input "path/to/video.mp4" --output-dir ./transcripts + +# Batch (all videos in a directory) +python transcribe.py --input ./videos/ --output-dir ./transcripts +``` + +See [`whisper/README.md`](whisper/README.md) for full transcription documentation. + +--- + +## Environment Variables + +Create `.env` from `.env.example`. All variables have sensible defaults for local development. + +### Database + +| Variable | Default | Description | +|--------------------|----------------|---------------------------------| +| `POSTGRES_USER` | `chrysopedia` | PostgreSQL username | +| `POSTGRES_PASSWORD`| `changeme` | PostgreSQL password | +| `POSTGRES_DB` | `chrysopedia` | Database name | +| `DATABASE_URL` | *(composed)* | Full async connection string | + +### Services + +| Variable | Default | Description | +|-----------------|------------------------------------|--------------------------| +| `REDIS_URL` | `redis://chrysopedia-redis:6379/0` | Redis connection string | + +### LLM Configuration + +| Variable | Default | Description | +|---------------------|-------------------------------------------|------------------------------------| +| `LLM_API_URL` | `https://friend-openwebui.example.com/api`| Primary LLM endpoint (OpenAI-compatible) | +| `LLM_API_KEY` | `sk-changeme` | API key for primary LLM | +| `LLM_MODEL` | `qwen2.5-72b` | Primary model name | +| `LLM_FALLBACK_URL` | `http://localhost:11434/v1` | Fallback LLM endpoint (Ollama) | +| `LLM_FALLBACK_MODEL`| `qwen2.5:14b-q8_0` | Fallback model name | + +### Embedding / Vector + +| Variable | Default | Description | +|-----------------------|-------------------------------|--------------------------| +| `EMBEDDING_API_URL` | `http://localhost:11434/v1` | Embedding endpoint | +| `EMBEDDING_MODEL` | `nomic-embed-text` | Embedding model name | +| `QDRANT_URL` | `http://qdrant:6333` | Qdrant vector DB URL | +| `QDRANT_COLLECTION` | `chrysopedia` | Qdrant collection name | + +### Application + +| Variable | Default | Description | +|--------------------------|----------------------------------|--------------------------------| +| `APP_ENV` | `production` | Environment (`development` / `production`) | +| `APP_LOG_LEVEL` | `info` | Log level | +| `APP_SECRET_KEY` | `changeme-generate-a-real-secret`| Application secret key | +| `TRANSCRIPT_STORAGE_PATH`| `/data/transcripts` | Transcript JSON storage path | +| `VIDEO_METADATA_PATH` | `/data/video_meta` | Video metadata storage path | +| `REVIEW_MODE` | `true` | Enable human review workflow | + +--- + +## Development Workflow + +### Local development (without Docker) + +```bash +# Create virtual environment +python -m venv .venv +source .venv/bin/activate + +# Install backend dependencies +pip install -r backend/requirements.txt + +# Start PostgreSQL and Redis (via Docker) +docker compose up -d chrysopedia-db chrysopedia-redis + +# Run migrations +alembic upgrade head + +# Start the API server with hot-reload +cd backend && uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +### Database migrations + +```bash +# Create a new migration after model changes +alembic revision --autogenerate -m "describe_change" + +# Apply all pending migrations +alembic upgrade head + +# Rollback one migration +alembic downgrade -1 +``` + +### Project structure + +``` +content-to-kb-automator/ +├── backend/ # FastAPI application +│ ├── main.py # App entry point, middleware, routers +│ ├── config.py # pydantic-settings configuration +│ ├── database.py # SQLAlchemy async engine + session +│ ├── models.py # 7-entity ORM models +│ ├── schemas.py # Pydantic request/response schemas +│ ├── routers/ # API route handlers +│ │ ├── health.py # /health (DB check) +│ │ ├── creators.py # /api/v1/creators +│ │ └── videos.py # /api/v1/videos +│ └── requirements.txt # Python dependencies +├── whisper/ # Desktop transcription script +│ ├── transcribe.py # Whisper CLI tool +│ ├── requirements.txt # Whisper + ffmpeg deps +│ └── README.md # Transcription documentation +├── docker/ # Dockerfiles +│ ├── Dockerfile.api # FastAPI + Celery image +│ ├── Dockerfile.web # React + nginx image +│ └── nginx.conf # nginx reverse proxy config +├── alembic/ # Database migrations +│ ├── env.py # Migration environment +│ └── versions/ # Migration scripts +├── config/ # Configuration files +│ └── canonical_tags.yaml # 6 topic categories + genre taxonomy +├── prompts/ # LLM prompt templates (editable) +├── frontend/ # React web UI (placeholder) +├── tests/ # Test fixtures and test suites +│ └── fixtures/ # Sample data for testing +├── docker-compose.yml # Full stack definition +├── alembic.ini # Alembic configuration +├── .env.example # Environment variable template +└── chrysopedia-spec.md # Full project specification +``` + +--- + +## API Endpoints + +| Method | Path | Description | +|--------|-----------------------------|---------------------------------| +| GET | `/health` | Health check with DB connectivity | +| GET | `/api/v1/health` | Lightweight health (no DB) | +| GET | `/api/v1/creators` | List all creators | +| GET | `/api/v1/creators/{slug}` | Get creator by slug | +| GET | `/api/v1/videos` | List all source videos | + +--- + +## XPLTD Conventions + +This project follows XPLTD infrastructure conventions: + +- **Docker project name:** `xpltd_chrysopedia` +- **Bind mounts:** persistent data stored under `/vmPool/r/services/` +- **Network:** dedicated bridge `chrysopedia` (`172.24.0.0/24`) +- **PostgreSQL host port:** `5433` (avoids conflict with system PostgreSQL on `5432`) diff --git a/tests/fixtures/sample_transcript.json b/tests/fixtures/sample_transcript.json new file mode 100644 index 0000000..2db471d --- /dev/null +++ b/tests/fixtures/sample_transcript.json @@ -0,0 +1,148 @@ +{ + "source_file": "Skope — Sound Design Masterclass pt1.mp4", + "creator_folder": "Skope", + "duration_seconds": 3847, + "segments": [ + { + "start": 0.0, + "end": 4.52, + "text": "Hey everyone welcome back to part one of this sound design masterclass.", + "words": [ + { "word": "Hey", "start": 0.0, "end": 0.28 }, + { "word": "everyone", "start": 0.32, "end": 0.74 }, + { "word": "welcome", "start": 0.78, "end": 1.12 }, + { "word": "back", "start": 1.14, "end": 1.38 }, + { "word": "to", "start": 1.40, "end": 1.52 }, + { "word": "part", "start": 1.54, "end": 1.76 }, + { "word": "one", "start": 1.78, "end": 1.98 }, + { "word": "of", "start": 2.00, "end": 2.12 }, + { "word": "this", "start": 2.14, "end": 2.34 }, + { "word": "sound", "start": 2.38, "end": 2.68 }, + { "word": "design", "start": 2.72, "end": 3.08 }, + { "word": "masterclass", "start": 3.14, "end": 4.52 } + ] + }, + { + "start": 5.10, + "end": 12.84, + "text": "Today we're going to be looking at how to create really aggressive bass sounds using Serum.", + "words": [ + { "word": "Today", "start": 5.10, "end": 5.48 }, + { "word": "we're", "start": 5.52, "end": 5.74 }, + { "word": "going", "start": 5.78, "end": 5.98 }, + { "word": "to", "start": 6.00, "end": 6.12 }, + { "word": "be", "start": 6.14, "end": 6.28 }, + { "word": "looking", "start": 6.32, "end": 6.64 }, + { "word": "at", "start": 6.68, "end": 6.82 }, + { "word": "how", "start": 6.86, "end": 7.08 }, + { "word": "to", "start": 7.12, "end": 7.24 }, + { "word": "create", "start": 7.28, "end": 7.62 }, + { "word": "really", "start": 7.68, "end": 8.02 }, + { "word": "aggressive", "start": 8.08, "end": 8.72 }, + { "word": "bass", "start": 8.78, "end": 9.14 }, + { "word": "sounds", "start": 9.18, "end": 9.56 }, + { "word": "using", "start": 9.62, "end": 9.98 }, + { "word": "Serum", "start": 10.04, "end": 12.84 } + ] + }, + { + "start": 13.40, + "end": 22.18, + "text": "So the first thing I always do is start with the init preset and then I'll load up a basic wavetable.", + "words": [ + { "word": "So", "start": 13.40, "end": 13.58 }, + { "word": "the", "start": 13.62, "end": 13.78 }, + { "word": "first", "start": 13.82, "end": 14.12 }, + { "word": "thing", "start": 14.16, "end": 14.42 }, + { "word": "I", "start": 14.48, "end": 14.58 }, + { "word": "always", "start": 14.62, "end": 14.98 }, + { "word": "do", "start": 15.02, "end": 15.18 }, + { "word": "is", "start": 15.22, "end": 15.38 }, + { "word": "start", "start": 15.44, "end": 15.78 }, + { "word": "with", "start": 15.82, "end": 16.02 }, + { "word": "the", "start": 16.06, "end": 16.18 }, + { "word": "init", "start": 16.24, "end": 16.52 }, + { "word": "preset", "start": 16.58, "end": 17.02 }, + { "word": "and", "start": 17.32, "end": 17.48 }, + { "word": "then", "start": 17.52, "end": 17.74 }, + { "word": "I'll", "start": 17.78, "end": 17.98 }, + { "word": "load", "start": 18.04, "end": 18.32 }, + { "word": "up", "start": 18.36, "end": 18.52 }, + { "word": "a", "start": 18.56, "end": 18.64 }, + { "word": "basic", "start": 18.68, "end": 19.08 }, + { "word": "wavetable", "start": 19.14, "end": 22.18 } + ] + }, + { + "start": 23.00, + "end": 35.42, + "text": "What makes this technique work is the FM modulation from oscillator B. You want to set the ratio to something like 3.5 and then automate the depth.", + "words": [ + { "word": "What", "start": 23.00, "end": 23.22 }, + { "word": "makes", "start": 23.26, "end": 23.54 }, + { "word": "this", "start": 23.58, "end": 23.78 }, + { "word": "technique", "start": 23.82, "end": 24.34 }, + { "word": "work", "start": 24.38, "end": 24.68 }, + { "word": "is", "start": 24.72, "end": 24.88 }, + { "word": "the", "start": 24.92, "end": 25.04 }, + { "word": "FM", "start": 25.10, "end": 25.42 }, + { "word": "modulation", "start": 25.48, "end": 26.12 }, + { "word": "from", "start": 26.16, "end": 26.38 }, + { "word": "oscillator", "start": 26.44, "end": 27.08 }, + { "word": "B", "start": 27.14, "end": 27.42 }, + { "word": "You", "start": 28.02, "end": 28.22 }, + { "word": "want", "start": 28.26, "end": 28.52 }, + { "word": "to", "start": 28.56, "end": 28.68 }, + { "word": "set", "start": 28.72, "end": 28.98 }, + { "word": "the", "start": 29.02, "end": 29.14 }, + { "word": "ratio", "start": 29.18, "end": 29.58 }, + { "word": "to", "start": 29.62, "end": 29.76 }, + { "word": "something", "start": 29.80, "end": 30.22 }, + { "word": "like", "start": 30.26, "end": 30.48 }, + { "word": "3.5", "start": 30.54, "end": 31.02 }, + { "word": "and", "start": 31.32, "end": 31.48 }, + { "word": "then", "start": 31.52, "end": 31.74 }, + { "word": "automate", "start": 31.80, "end": 32.38 }, + { "word": "the", "start": 32.42, "end": 32.58 }, + { "word": "depth", "start": 32.64, "end": 35.42 } + ] + }, + { + "start": 36.00, + "end": 48.76, + "text": "Now I'm going to add some distortion. OTT is great for this. Crank it to like 60 percent and then back off the highs a bit with a shelf EQ.", + "words": [ + { "word": "Now", "start": 36.00, "end": 36.28 }, + { "word": "I'm", "start": 36.32, "end": 36.52 }, + { "word": "going", "start": 36.56, "end": 36.82 }, + { "word": "to", "start": 36.86, "end": 36.98 }, + { "word": "add", "start": 37.02, "end": 37.28 }, + { "word": "some", "start": 37.32, "end": 37.58 }, + { "word": "distortion", "start": 37.64, "end": 38.34 }, + { "word": "OTT", "start": 39.02, "end": 39.42 }, + { "word": "is", "start": 39.46, "end": 39.58 }, + { "word": "great", "start": 39.62, "end": 39.92 }, + { "word": "for", "start": 39.96, "end": 40.12 }, + { "word": "this", "start": 40.16, "end": 40.42 }, + { "word": "Crank", "start": 41.02, "end": 41.38 }, + { "word": "it", "start": 41.42, "end": 41.56 }, + { "word": "to", "start": 41.60, "end": 41.72 }, + { "word": "like", "start": 41.76, "end": 41.98 }, + { "word": "60", "start": 42.04, "end": 42.38 }, + { "word": "percent", "start": 42.42, "end": 42.86 }, + { "word": "and", "start": 43.12, "end": 43.28 }, + { "word": "then", "start": 43.32, "end": 43.54 }, + { "word": "back", "start": 43.58, "end": 43.84 }, + { "word": "off", "start": 43.88, "end": 44.08 }, + { "word": "the", "start": 44.12, "end": 44.24 }, + { "word": "highs", "start": 44.28, "end": 44.68 }, + { "word": "a", "start": 44.72, "end": 44.82 }, + { "word": "bit", "start": 44.86, "end": 45.08 }, + { "word": "with", "start": 45.14, "end": 45.38 }, + { "word": "a", "start": 45.42, "end": 45.52 }, + { "word": "shelf", "start": 45.58, "end": 45.96 }, + { "word": "EQ", "start": 46.02, "end": 48.76 } + ] + } + ] +}