Cornocopia is a web-based music player application that combines classic iTunes-inspired interface design with modern AI-powered playlist generation and music recommendation capabilities. The system consists of a React-based frontend and an Express.js backend that integrates with the Anthropic Claude API for intelligent music curation.
iTunes-inspired interface with draggable window, LCD display, and brushed metal controls
Automatically generated playlists organized by genre, year, and energy
Real-time notification when AI selects the next track (20 seconds before transition)
Album grid view with hover interactions
The application follows a client-server architecture with the following stack:
Frontend:
- React 18.2.0 with functional components and hooks
- Vite 5.0 as build tool and development server
- Tailwind CSS 3.3.5 for utility-first styling
- Lucide React 0.292.0 for iconography
Backend:
- Node.js with Express 4.18.2
- Anthropic SDK 0.24.0 for AI integration
- music-metadata 7.13.4 for audio file parsing
- node-cache 5.1.2 for in-memory caching
- fs-extra 11.1.1 for filesystem operations
Infrastructure:
- Docker containerization with multi-stage builds
- Nginx as reverse proxy for frontend
- Volume mounting for music library and persistent data
Client Request → Express Server → Music Metadata Parser → Library Scan
↓
Claude API → Playlist Generation → Cache Manager
↓
JSON Response → React State → UI Update
The server scans a mounted /music directory recursively, parsing audio files with the following extensions: .mp3, .flac, .wav, .m4a, .alac. For each file, the system extracts:
- Title, artist, album metadata
- Genre tags (array)
- Year of release
- Duration in seconds
- Audio format specifications
- Embedded or external album artwork
Album art is resolved through a priority system:
- Embedded picture data in audio file metadata
cover.jpgin the same directoryfolder.jpgin the same directory
The library is served via REST API at /api/library and cached client-side for performance.
The playlist generation system uses a two-tier approach:
Primary Method: Claude AI Analysis
The system sends the complete music library metadata to Claude with a structured prompt that instructs the model to:
- Analyze musical coherence rather than artist or album grouping
- Create thematic playlists based on:
- Genre compatibility
- Temporal cohesion (decade-based grouping)
- Energy and mood characteristics
- Ensure variety by limiting consecutive tracks from the same album to 2-3 maximum
- Generate 10-30 tracks per playlist
- Output structured JSON with playlist metadata
The prompt engineering specifically prohibits album-centric grouping to avoid creating playlists that simply mirror album tracklists. Instead, the AI is instructed to evaluate individual tracks for their fit within a broader musical context.
Secondary Method: Fallback Algorithm
When the Claude API is unavailable or fails, a deterministic fallback algorithm activates:
- Group tracks by genre tags
- Group tracks by decade (floor of year/10)
- Apply a diversification function that:
- Shuffles tracks randomly
- Filters consecutive tracks from the same album
- Maintains a maximum of 2 consecutive tracks per album
- Limits playlists to 30 tracks
Generated playlists are persisted to disk at /app/data/playlists.json with the following cache invalidation logic:
- Cache TTL: 7 days (configurable via environment variable)
- Invalidation trigger: Library hash change
- Hash calculation: MD5 of concatenated file paths, sizes, and modification timestamps
This ensures playlists regenerate only when the library content changes, minimizing API costs while maintaining accuracy.
The AI Next Song feature implements an intelligent track sequencing system with predictive preloading:
Preload Timing: The system initiates AI selection 20 seconds before the current track ends. This allows:
- API request completion during playback
- Seamless transition between tracks
- Zero-gap playback experience
- User notification of upcoming selection
Selection Criteria:
- Current track characteristics (genre, artist, year, album)
- Recent playback history (last 5 tracks to avoid repetition)
- Available library subset (smart sampling)
Library Sampling Optimization:
To reduce API token consumption for large libraries, the system implements weighted sampling:
- 50% of sample: tracks matching current genre
- 30% of sample: tracks from the same decade
- 20% of sample: random selection
- Maximum sample size: 150 tracks
This maintains recommendation quality while reducing API payload size by up to 90% for large libraries.
Response Handling:
The system implements a 3-second timeout with automatic fallback. The fallback algorithm:
- Filters recently played tracks
- Prioritizes same-genre tracks
- Secondarily prioritizes same-decade tracks
- Selects randomly from the filtered pool
AI Next Song selections are cached in-memory using node-cache with:
- Cache key: MD5 hash of (current_song_path + recent_history)
- TTL: 24 hours
- Expected hit rate: 60-75%
This provides instant responses for repeated contexts while allowing fresh recommendations for new scenarios.
When AI Next selects a track, the system displays a temporary notification toast containing:
- Selected track title and artist
- Indicator of AI selection vs fallback algorithm
- 3-second display duration with slide-in animation
- Color-coded by selection type (green for AI, yellow for fallback)
The notification appears in the top-right corner without obstructing playback controls or library view.
The application implements a queue-based playlist playback system:
State Management:
currentPlaylistQueue: {
playlistId: string,
songs: Array<Song>,
currentIndex: number
}
playlistMode: booleanPlayback Priority:
The playNext() function implements the following decision tree:
- If
playlistModeis active:- Increment queue index
- Play next track in queue
- If queue exhausted, exit playlist mode
- Else if
aiNextEnabled:- Call AI selection API
- Play recommended track
- Else:
- Sequential library playback
This ensures playlist continuity takes precedence over other playback modes.
The interface recreates the iTunes 11-12 era aesthetic through several design principles:
The application uses a draggable window paradigm despite being web-based:
- Custom title bar with macOS-style traffic light buttons (red, yellow, green)
- Window shadow and border radius for depth perception
- Absolute positioning with transform for smooth dragging
- Mouse event handling for drag state management
Implementation uses React state for position tracking and event listeners for mousemove/mouseup events.
Brushed Metal Gradient:
The top control area simulates brushed aluminum through CSS gradients:
background: linear-gradient(to bottom, #3a3a3a 0%, #2a2a2a 50%, #1a1a1a 100%)This creates the perception of light reflection across a non-uniform metallic surface.
LCD Display:
The center information panel mimics a backlit LCD screen:
- Dark background (#0f0f0f) for contrast
- Inset shadow for depth illusion
- Monochromatic color scheme (white/gray text)
- Fixed-width font for time display
Physical Buttons:
Control buttons simulate 3D depth through:
- Gradient fills (lighter top, darker bottom)
- Border definition
- Box shadow for elevation
- Active state transform (translateY) for press feedback
Three-Column Layout:
┌─────────────────────────────────────────┐
│ Title Bar (Draggable) │
├─────────────────────────────────────────┤
│ Controls + LCD Display │
├──────────┬────────────────┬─────────────┤
│ │ │ │
│ Sidebar │ Content Area │ │
│ │ │ │
└──────────┴────────────────┴─────────────┘
│ Status Bar │
└─────────────────────────────────────────┘
Sidebar:
- Fixed width (224px)
- Dark background (#252525)
- Hierarchical navigation with collapsible sections
- Selection highlighting with iTunes blue (#3d8dd5)
Content Area:
- List view: Table-based layout with alternating row colors
- Grid view: CSS Grid with aspect-ratio preserved album art
- Album headers in list view for grouping
Status Bar:
- Fixed height (24px)
- Dark background matching window chrome
- Contextual information display (track count, playlist status, AI indicators)
The interface uses system fonts for native appearance:
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serifText hierarchy:
- Headers: 12px, uppercase, letter-spacing
- Body: 12-14px, regular weight
- Status: 10-11px, tabular numerals for time display
Spacing follows an 8px grid system with exceptions for pixel-perfect alignment with historical iTunes layouts.
The application uses a restricted palette for visual cohesion:
Backgrounds:
- Window: #2a2a2a
- Content: #1e1e1e
- Sidebar: #252525
- LCD: #0f0f0f
Accents:
- Selection: #3d8dd5 (iTunes blue)
- AI Indicators: #60a5fa (blue-400)
- Playlist Mode: #4ade80 (green-400)
Text:
- Primary: #e5e5e5 (gray-200)
- Secondary: #9ca3af (gray-400)
- Tertiary: #6b7280 (gray-500)
All interactive elements implement hover and active states:
- Hover: Brightness adjustment or background color change
- Active: Vertical translation (1px) for button press simulation
- Focus: Border color change for keyboard navigation
- Selection: Background fill with iTunes blue
GET /api/library
Returns complete music library with metadata.
Response structure:
[
{
"path": "/Artist/Album/01. Track.flac",
"name": "01. Track.flac",
"title": "Track Title",
"artist": "Artist Name",
"album": "Album Name",
"genre": ["Genre1", "Genre2"],
"year": 2024,
"duration": 245.6,
"format": { ... },
"hasArt": true
}
]GET /api/art/{path}
Streams album artwork for specified track path. Returns JPEG/PNG binary data or 404 if unavailable.
GET /api/stream/{path}
Streams audio file with proper MIME type headers. Supports range requests for seeking.
GET /api/playlists
Returns all generated playlists or triggers generation if cache is invalid.
Response structure:
{
"playlists": [
{
"id": "uuid",
"name": "Playlist Name",
"description": "Description text",
"category": "genre|year|mood",
"metadata": {},
"songs": ["/path/to/track.flac"],
"createdAt": "2024-12-05T...",
"generatedBy": "ai|fallback",
"version": 1
}
],
"lastGenerated": "2024-12-05T...",
"status": "ready|generating|error"
}POST /api/playlists/regenerate
Forces playlist regeneration, clearing cache.
Request body:
{
"force": true
}Response:
{
"status": "started",
"estimatedTime": 15
}GET /api/playlists/status
Returns current generation progress.
Response:
{
"isGenerating": true,
"progress": 45,
"currentStep": "Generating playlists with AI"
}POST /api/ai-next-song
Requests AI-powered next track selection.
Request body:
{
"currentSong": {
"path": "/path/to/current.flac",
"title": "Current Track",
"artist": "Artist",
"album": "Album",
"genre": ["Genre"],
"year": 2024,
"duration": 245
},
"recentHistory": ["/path1", "/path2"],
"library": [ /* full library metadata */ ]
}Response:
{
"nextSong": "/path/to/next.flac",
"reason": "Genre compatibility and energy match",
"fallback": false,
"cacheHit": true,
"duration": 45
}ANTHROPIC_API_KEY (required)
- Anthropic API key for Claude access
- Format:
sk-ant-... - Obtain from: https://console.anthropic.com/
PLAYLIST_CACHE_TTL (optional)
- Cache time-to-live in seconds
- Default: 604800 (7 days)
MAX_PLAYLIST_SIZE (optional)
- Maximum tracks per generated playlist
- Default: 30
Music Library:
volumes:
- ./music:/music:roMount music files at /music. Read-only recommended for safety.
Persistent Data:
volumes:
- ./data:/app/dataStores generated playlists and cache data at /app/data.
- Initial load time: ~200ms (cached assets)
- Library fetch: ~100-500ms (depends on library size)
- Playlist generation trigger: ~50ms (async operation)
- UI responsiveness: 60fps target for dragging and scrolling
- Memory footprint: ~50-100MB (depends on library size)
- Library scan: ~100ms per 100 tracks
- Playlist generation (AI): ~10-30s for 100 tracks
- Playlist generation (fallback): ~100-500ms
- AI Next Song: 1-3s (with 3s timeout)
- Cache hit response: <10ms
- Library API payload: ~1-5MB (depends on metadata richness)
- Playlist API payload: ~50-200KB
- Audio streaming: Minimal buffering with range request support
- Album art: ~50-500KB per image
Playlist Generation:
- Input tokens: ~2000-8000 (depends on library size)
- Output tokens: ~1000-2000
- Cost per generation: $0.05-0.10
- Frequency: Once per library change (cached 7 days)
AI Next Song:
- Input tokens: ~1000-1500 (with sampling)
- Output tokens: ~50-100
- Cost per selection: ~$0.004
- Cache hit rate: 60-75%
- Effective cost per session (20 tracks): $0.03-0.08
Monthly Estimate:
- Playlist regeneration: $0.10 (assuming monthly library updates)
- Daily listening (30 min): $0.03
- Total: $1-5 per month for typical usage
Claude API responses occasionally include markdown code blocks wrapping JSON output. The system implements a cleaning function that:
- Trims whitespace from response text
- Detects markdown code block delimiters (triple backticks)
- Extracts content between opening and closing delimiters
- Parses cleaned JSON with error handling
- Validates expected structure before processing
This approach handles both raw JSON and markdown-wrapped JSON responses without requiring API behavior changes.
The cache invalidation system uses MD5 hashing of library state:
hash = MD5(file1.path + file1.size + file1.mtime + '|' + file2.path + ...)This provides fast comparison while detecting:
- Added files (new paths)
- Removed files (missing paths)
- Modified files (changed size or mtime)
The hash is stored with cached playlists and compared on subsequent requests.
The fallback algorithm implements a greedy approach to album diversity:
input: tracks[], maxSize
output: diversified[]
shuffle(tracks)
lastAlbum = null
consecutiveCount = 0
for track in tracks:
if length(diversified) >= maxSize:
break
if track.album == lastAlbum:
consecutiveCount++
if consecutiveCount >= 2:
continue
else:
consecutiveCount = 1
lastAlbum = track.album
diversified.append(track)
return diversified
This ensures no more than 2 consecutive tracks from the same album while maintaining randomization.
The application uses React hooks for state management without external libraries:
Core State:
library: Complete music library arraycurrentSong: Currently playing track objectisPlaying: Boolean playback statecurrentTime/duration: Playback position tracking
AI Features State:
aiPlaylists: Generated playlists arrayselectedPlaylist: Active playlist IDaiNextEnabled: AI recommendation togglerecentHistory: Last 10 played tracksnextSongPreloaded: Preloaded next track objecthasRequestedNext: Flag to prevent duplicate API calls
Playlist Mode State:
currentPlaylistQueue: Active queue with indexplaylistMode: Boolean queue active state
Notification State:
notification: Object containing message, type, and visibility flag
State updates trigger React re-renders which propagate to affected components through props drilling (no context or Redux due to shallow component tree).
Tested and functional on:
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Requires:
- JavaScript ES6+ support
- CSS Grid and Flexbox
- Audio element with streaming support
- Fetch API for network requests
The Anthropic API key is stored server-side as an environment variable and never exposed to the client. All AI operations proxy through the Express backend.
The server has read-only access to the music directory. No write operations are performed on music files. Cache and playlist data is isolated to /app/data.
User-provided paths in API requests are validated against the mounted music directory to prevent directory traversal attacks. All file operations use path normalization and containment checks.
The server implements permissive CORS for development. Production deployments should restrict origins to the frontend domain.
# Server (port 3000)
cd server
npm install
npm run dev
# Client (port 5173)
cd client
npm install
npm run devThe client dev server proxies API requests to the backend automatically.
docker-compose up --buildAccess at http://localhost:8080
# Client build
cd client
npm run build
# Server runs in production mode
cd server
npm startThe built client is served by Nginx in the Docker container.
cd server
node test-api.jsThis script validates Anthropic API key configuration and tests basic playlist generation.
- Library loading and display
- Album art rendering (embedded and external)
- Audio playback and seeking
- Playlist generation (AI and fallback)
- AI Next Song selection
- Playlist mode continuity
- Window dragging functionality
- View mode switching (list/grid)
- Genre filtering
- Playback controls (play, pause, next, previous)
- Single-user application (no authentication or multi-user support)
- No playlist editing capabilities (generated playlists are read-only)
- No mobile-responsive design (desktop-only interface)
- No streaming service integration (local files only)
- Limited audio format support (no DSD or proprietary formats)
- Large libraries (>1000 tracks) may experience slow initial load
- Very long playlists (>50 tracks) may cause Claude API timeouts
- Album art extraction may fail for some file formats
- Window dragging can be jerky on low-end systems
v1.0.4 (Current)
- AI Next preloading 20 seconds before track end
- Toast notification system for AI selections
- Seamless track transitions with zero-gap playback
- Preload state management and cleanup
v1.0.3
- Playlist mode with queue management
- English playlist names
- Fix for initial track playback in playlists
v1.0.2
- JSON parsing fix for markdown-wrapped responses
- Enhanced error handling
v1.0.1
- Album diversification in playlist generation
- Improved variety algorithm
v1.0.0
- Initial release
- AI playlist generation
- AI Next Song feature
- iTunes-inspired interface
- React: https://react.dev/
- Express: https://expressjs.com/
- Anthropic API: https://docs.anthropic.com/
- music-metadata: https://github.com/Borewit/music-metadata
- iTunes 11-12 interface design
- macOS Human Interface Guidelines
- Skeuomorphic design principles in pre-iOS 7 Apple software
This project is provided as-is for educational and personal use.