From 4a103511daccff0fa3d82159f203d21a65c3a2dd Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:52:34 -0800 Subject: [PATCH] feat: Add video integration - display videos on performance pages and indicators - Add YouTubeEmbed to performance detail page when youtube_link exists - Add YouTube icon indicator on setlist items that have videos - Add YouTube badge on show cards in archive when full show video exists - Add youtube_link to ShowRead and PerformanceRead schemas - Add VIDEO_INTEGRATION_SPEC.md documentation --- backend/schemas.py | 2 + docs/VIDEO_INTEGRATION_SPEC.md | 177 ++++++++++++++++++++++++ frontend/app/performances/[id]/page.tsx | 21 ++- frontend/app/shows/[id]/page.tsx | 8 ++ frontend/app/shows/page.tsx | 10 +- 5 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 docs/VIDEO_INTEGRATION_SPEC.md diff --git a/backend/schemas.py b/backend/schemas.py index 2dbaa17..2946e92 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -91,6 +91,7 @@ class PerformanceRead(PerformanceBase): slug: Optional[str] = None song: Optional["SongRead"] = None nicknames: List["PerformanceNicknameRead"] = [] + youtube_link: Optional[str] = None class PerformanceReadWithShow(PerformanceRead): show_date: datetime @@ -151,6 +152,7 @@ class ShowRead(ShowBase): tour: Optional["TourRead"] = None tags: List["TagRead"] = [] performances: List["PerformanceRead"] = [] + youtube_link: Optional[str] = None class ShowUpdate(SQLModel): date: Optional[datetime] = None diff --git a/docs/VIDEO_INTEGRATION_SPEC.md b/docs/VIDEO_INTEGRATION_SPEC.md new file mode 100644 index 0000000..9819f11 --- /dev/null +++ b/docs/VIDEO_INTEGRATION_SPEC.md @@ -0,0 +1,177 @@ +# Video Integration Specification + +**Date:** 2025-12-22 +**Status:** In Progress + +## Overview + +This spec outlines the complete video integration for elmeg.xyz, ensuring YouTube videos are properly displayed and discoverable across the application. + +--- + +## Current State + +### Database Schema ✅ + +- `Performance.youtube_link` - Individual performance video URL +- `Show.youtube_link` - Full show video URL +- `Song.youtube_link` - Studio/canonical video URL + +### Import Pipeline ✅ + +- `import_youtube.py` processes `youtube_videos.json` +- Handles: single songs, sequences (→), and full shows +- Sequences link the SAME video to ALL performances in the sequence + +### Frontend Display (Current) + +| Page | Video Display | Status | +|------|--------------|--------| +| Show Page | Full show video (`show.youtube_link`) | ✅ Working | +| Song Page | Top performance video or song video | ✅ Working | +| Performance Page | Should show `performance.youtube_link` | ❌ MISSING | +| Videos Page | Lists all videos | ✅ Working | + +### Visual Indicators (Current) + +| Location | Indicator | Status | +|----------|-----------|--------| +| Setlist items | Video icon for performances with video | ❌ MISSING | +| Archive/Show list | Video badge for shows with video | ❌ MISSING | + +--- + +## Implementation Plan + +### Phase 1: Performance Page Video Display ⚡ HIGH PRIORITY + +**File:** `frontend/app/performances/[id]/page.tsx` + +**Requirements:** + +1. Import `YouTubeEmbed` component +2. Add video section ABOVE the "Version Timeline" card when `performance.youtube_link` exists +3. Style consistently with show page video section + +**UI Placement:** + +``` +[Hero Banner] +[VIDEO EMBED] <-- NEW: Only when youtube_link exists +[Version Timeline] +[About This Performance] +[Comments] +[Reviews] +``` + +### Phase 2: Setlist Video Indicators + +**File:** `frontend/app/shows/[id]/page.tsx` + +**Requirements:** + +1. Add small YouTube icon (📹 or ``) next to song title when `perf.youtube_link` exists +2. Make icon clickable - links to performance page (where video is embedded) +3. Use red color for YouTube brand recognition + +**Visual Design:** + +``` +1. Dramophone 📹 > +2. The Empress of Organos 📹 +``` + +### Phase 3: Archive Video Badge + +**File:** `frontend/app/archive/page.tsx` (or show list component) + +**Requirements:** + +1. Add video badge to show cards that have: + - `show.youtube_link` (full show video), OR + - Any `performance.youtube_link` in their setlist +2. API enhancement: Add `has_videos` or `video_count` to show list endpoint + +**Backend Enhancement:** + +```python +# In routers/shows.py - list_shows endpoint +# Add computed field: has_videos = show.youtube_link is not None or any performance has youtube_link +``` + +**Visual Design:** + +- Small YouTube icon in corner of show card +- Tooltip: "Full show video available" or "X song videos available" + +--- + +## Data Flow + +``` +YouTube Video → import_youtube.py → Database + ↓ + ┌──────────────┼──────────────┐ + ↓ ↓ ↓ + Show.youtube_link Performance.youtube_link Song.youtube_link + ↓ ↓ ↓ + Show Page Performance Page Song Page +``` + +--- + +## API Changes Required + +### 1. Shows List Enhancement (Phase 3) + +**Endpoint:** `GET /shows/` + +**New Response Fields:** + +```json +{ + "id": 123, + "date": "2025-12-13T00:00:00", + "has_video": true, // NEW: true if show.youtube_link OR any perf.youtube_link + "video_count": 3 // NEW: count of performances with videos +} +``` + +### 2. Performance Detail (Already Exists) + +**Endpoint:** `GET /performances/{id}` + +**Verify Field Included:** + +```json +{ + "youtube_link": "https://www.youtube.com/watch?v=zQI6-LloYwI" +} +``` + +--- + +## Testing Checklist + +- [ ] Dramophone 2025-12-13 shows video on performance page +- [ ] Empress of Organos 2025-12-13 shows SAME video on performance page +- [ ] Setlist on 2025-12-13 show shows video icons for both songs +- [ ] Archive view shows video indicator for 2025-12-13 show +- [ ] Video page accurately reflects all linked videos + +--- + +## Files Modified + +### Phase 1 + +- `frontend/app/performances/[id]/page.tsx` - Add video embed + +### Phase 2 + +- `frontend/app/shows/[id]/page.tsx` - Add video icons to setlist + +### Phase 3 + +- `backend/routers/shows.py` - Add has_videos to list response +- `frontend/app/archive/page.tsx` - Add video badge to cards diff --git a/frontend/app/performances/[id]/page.tsx b/frontend/app/performances/[id]/page.tsx index c971c47..82db1f6 100644 --- a/frontend/app/performances/[id]/page.tsx +++ b/frontend/app/performances/[id]/page.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { ArrowLeft, Calendar, MapPin, ChevronRight, ChevronLeft, Music, Clock, Hash, Play, ExternalLink, Sparkles } from "lucide-react" +import { ArrowLeft, Calendar, MapPin, ChevronRight, ChevronLeft, Music, Clock, Hash, Play, ExternalLink, Sparkles, Youtube } from "lucide-react" import Link from "next/link" import { notFound } from "next/navigation" import { getApiUrl } from "@/lib/api-config" @@ -9,6 +9,7 @@ import { EntityReviews } from "@/components/reviews/entity-reviews" import { SocialWrapper } from "@/components/social/social-wrapper" import { EntityRating } from "@/components/social/entity-rating" import { Badge } from "@/components/ui/badge" +import { YouTubeEmbed } from "@/components/ui/youtube-embed" async function getPerformance(id: string) { try { @@ -141,6 +142,24 @@ export default async function PerformanceDetailPage({ params }: { params: Promis + {/* Video Section - Show when performance has a video */} + {performance.youtube_link && ( + + + + + Video + + + + + + + )} +
{/* Version Navigation - Prominent */} diff --git a/frontend/app/shows/[id]/page.tsx b/frontend/app/shows/[id]/page.tsx index 0008fac..026f8d9 100644 --- a/frontend/app/shows/[id]/page.tsx +++ b/frontend/app/shows/[id]/page.tsx @@ -190,6 +190,14 @@ export default async function ShowDetailPage({ params }: { params: Promise<{ id: )} + {perf.youtube_link && ( + + + + )} {perf.segue && >}
diff --git a/frontend/app/shows/page.tsx b/frontend/app/shows/page.tsx index a0f84ae..1708c58 100644 --- a/frontend/app/shows/page.tsx +++ b/frontend/app/shows/page.tsx @@ -4,7 +4,7 @@ import { useEffect, useState, Suspense } from "react" import { getApiUrl } from "@/lib/api-config" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import Link from "next/link" -import { Calendar, MapPin, Loader2 } from "lucide-react" +import { Calendar, MapPin, Loader2, Youtube } from "lucide-react" import { Skeleton } from "@/components/ui/skeleton" import { useSearchParams } from "next/navigation" @@ -12,6 +12,7 @@ interface Show { id: number slug?: string date: string + youtube_link?: string venue: { id: number name: string @@ -84,7 +85,12 @@ function ShowsContent() {
{shows.map((show) => ( - + + {show.youtube_link && ( +
+ +
+ )}