# ID3 Tags and Artwork Management This document describes the ID3 tagging and artwork management features in SoundWave. ## Features ### ID3 Tag Support - **Broad codec support** with read/write tags for multiple audio formats - **Automatic tag extraction** from downloaded audio files - **Manual tag editing** through API - **Bulk tag updates** from metadata **Supported Audio Formats:** Lossy formats: - MP3 (ID3v2) - MP4/M4A/M4B (iTunes tags) - OGG Vorbis - Opus - Musepack (MPC) Lossless formats: - FLAC - WavPack (.wv) - Monkey's Audio (.ape) - AIFF/AIF - WAV High-resolution DSD formats: - DSF (DSD Stream File) - DFF (DSDIFF - Direct Stream Digital Interchange File Format) Supported tags: - Title - Artist - Album - Album Artist - Year - Genre - Track Number - Disc Number - Duration (read-only) - Bitrate (read-only) - Sample Rate (DSD formats) - Channels (DSD formats) - Bits per Sample (DSD formats) ### Artwork Management - **Multiple artwork types**: - Audio thumbnail (YouTube) - Audio cover art - Album cover - Artist image - Artist banner - Artist logo - **Multiple artwork sources**: - YouTube thumbnails (automatic) - Last.fm API - Fanart.tv API - Manual uploads - **Automatic artwork fetching** from Last.fm and Fanart.tv - **Artwork embedding** in audio files - **Priority-based artwork selection** - **Local artwork caching** ### Music Metadata - **Extended metadata** for audio tracks: - Album information - Track and disc numbers - Genre and tags - Last.fm statistics (play count, listeners) - MusicBrainz IDs - Fanart.tv IDs - **Artist information** for channels: - Biography - Last.fm statistics - Tags and genres - Similar artists - MusicBrainz ID - Fanart.tv ID ## Setup ### 1. Install Dependencies The required packages are already in `requirements.txt`: - `mutagen>=1.47.0` - ID3 tag reading/writing - `pylast>=5.2.0` - Last.fm API client Install with: ```bash pip install -r requirements.txt ``` ### 2. Configure API Keys #### Last.fm API 1. Register at https://www.last.fm/api/account/create 2. Add to `.env`: ```bash LASTFM_API_KEY=your_api_key_here LASTFM_API_SECRET=your_api_secret_here ``` #### Fanart.tv API 1. Register at https://fanart.tv/get-an-api-key/ 2. Add to `.env`: ```bash FANART_API_KEY=your_api_key_here ``` ### 3. Run Migrations ```bash python manage.py makemigrations audio python manage.py migrate ``` ### 4. Configure Celery Tasks The following periodic tasks are configured in `config/celery.py`: - **Auto-fetch artwork**: Every 2 hours (50 tracks per batch) - **Auto-fetch artist info**: Daily at 2 AM (20 channels per batch) ## Usage ### API Endpoints #### Artwork Management ``` GET /api/audio/api/artwork/ # List all artwork GET /api/audio/api/artwork/{id}/ # Get artwork details POST /api/audio/api/artwork/ # Create artwork PUT /api/audio/api/artwork/{id}/ # Update artwork DELETE /api/audio/api/artwork/{id}/ # Delete artwork POST /api/audio/api/artwork/{id}/download/ # Download artwork from URL POST /api/audio/api/artwork/{id}/set_primary/ # Set as primary artwork ``` Query parameters: - `audio_id` - Filter by audio ID - `channel_id` - Filter by channel ID - `type` - Filter by artwork type - `source` - Filter by source #### Music Metadata ``` GET /api/audio/api/metadata/ # List metadata GET /api/audio/api/metadata/{id}/ # Get metadata POST /api/audio/api/metadata/ # Create metadata PUT /api/audio/api/metadata/{id}/ # Update metadata DELETE /api/audio/api/metadata/{id}/ # Delete metadata POST /api/audio/api/metadata/{id}/fetch_from_lastfm/ # Fetch from Last.fm POST /api/audio/api/metadata/{id}/update_id3_tags/ # Update file ID3 tags ``` #### Artist Information ``` GET /api/audio/api/artist-info/ # List artist info GET /api/audio/api/artist-info/{id}/ # Get artist info POST /api/audio/api/artist-info/ # Create artist info PUT /api/audio/api/artist-info/{id}/ # Update artist info DELETE /api/audio/api/artist-info/{id}/ # Delete artist info POST /api/audio/api/artist-info/{id}/fetch_from_lastfm/ # Fetch from Last.fm ``` #### Audio Artwork Operations ``` GET /api/audio/api/audio-artwork/{audio_id}/ # Get all artwork for audio POST /api/audio/api/audio-artwork/{audio_id}/fetch_artwork/ # Fetch artwork POST /api/audio/api/audio-artwork/{audio_id}/fetch_metadata/ # Fetch metadata POST /api/audio/api/audio-artwork/{audio_id}/embed_artwork/ # Embed artwork in file ``` Request body for embed_artwork (optional): ```json { "artwork_id": 123 // Specific artwork to embed, or omit for best artwork } ``` #### Channel Artwork Operations ``` GET /api/audio/api/channel-artwork/{channel_id}/ # Get all artwork for channel POST /api/audio/api/channel-artwork/{channel_id}/fetch_artwork/ # Fetch artwork POST /api/audio/api/channel-artwork/{channel_id}/fetch_info/ # Fetch artist info ``` ### Celery Tasks #### Manual Task Execution Fetch metadata for specific audio: ```python from audio.tasks_artwork import fetch_metadata_for_audio fetch_metadata_for_audio.delay(audio_id) ``` Fetch artwork for specific audio: ```python from audio.tasks_artwork import fetch_artwork_for_audio fetch_artwork_for_audio.delay(audio_id) ``` Fetch artist info: ```python from audio.tasks_artwork import fetch_artist_info fetch_artist_info.delay(channel_id) ``` Fetch artist artwork: ```python from audio.tasks_artwork import fetch_artist_artwork fetch_artist_artwork.delay(channel_id) ``` Embed artwork in audio file: ```python from audio.tasks_artwork import embed_artwork_in_audio embed_artwork_in_audio.delay(audio_id, artwork_id=None) # None = use best artwork ``` Update ID3 tags from metadata: ```python from audio.tasks_artwork import update_id3_tags_from_metadata update_id3_tags_from_metadata.delay(audio_id) ``` #### Batch Operations Auto-fetch artwork for 50 audio without artwork: ```python from audio.tasks_artwork import auto_fetch_artwork_batch auto_fetch_artwork_batch.delay(limit=50) ``` Auto-fetch artist info for 20 channels: ```python from audio.tasks_artwork import auto_fetch_artist_info_batch auto_fetch_artist_info_batch.delay(limit=20) ``` ### ID3 Service Direct tag manipulation for all supported formats: ```python from audio.id3_service import ID3TagService service = ID3TagService() # Read tags (supports MP3, M4A, FLAC, OGG, Opus, WavPack, APE, DSF, DFF, AIFF, WAV, etc.) tags = service.read_tags('/path/to/audio.dsf') print(tags) # { # 'title': 'Song Title', # 'artist': 'Artist Name', # 'album': 'Album Name', # 'year': '2024', # 'genre': 'Rock', # 'track_number': 5, # 'duration': 240.5, # 'bitrate': 256000, # 'has_cover': True, # 'format': 'DSF', # 'sample_rate': 2822400, # DSD64 (2.8224 MHz) # 'channels': 2, # 'bits_per_sample': 1 # } # Write tags (works with all supported formats) new_tags = { 'title': 'New Title', 'artist': 'New Artist', 'album': 'New Album', 'year': '2024', 'genre': 'Jazz', 'track_number': 3, 'disc_number': 1, } service.write_tags('/path/to/audio.dsf', new_tags) # Works with DSF, FLAC, MP3, etc. # Embed cover art (supports all formats including DSD) with open('/path/to/cover.jpg', 'rb') as f: image_data = f.read() service.embed_cover_art('/path/to/audio.dsf', image_data, 'image/jpeg') # Extract cover art (works with all formats) cover_data = service.extract_cover_art('/path/to/audio.dsf') if cover_data: with open('/path/to/extracted_cover.jpg', 'wb') as f: f.write(cover_data) # Check supported formats print(service.SUPPORTED_FORMATS) # {'.mp3': 'MP3', '.m4a': 'MP4', '.flac': 'FLAC', '.dsf': 'DSF', '.dff': 'DSDIFF', ...} ``` ### Last.fm Client ```python from audio.lastfm_client import LastFMClient client = LastFMClient() # Search for track track_info = client.search_track('Artist Name', 'Song Title') print(track_info) # { # 'title': 'Song Title', # 'artist': 'Artist Name', # 'album': 'Album Name', # 'url': 'https://www.last.fm/music/...', # 'duration': 240, # 'listeners': 50000, # 'playcount': 1000000, # 'tags': ['rock', 'alternative'], # 'mbid': '...', # 'images': [{'size': 'large', 'url': '...'}] # } # Get artist info artist_info = client.get_artist_info('Artist Name') print(artist_info) # { # 'name': 'Artist Name', # 'url': 'https://www.last.fm/music/...', # 'listeners': 1000000, # 'playcount': 50000000, # 'bio': '...', # 'bio_summary': '...', # 'tags': ['rock', 'alternative'], # 'mbid': '...', # 'similar_artists': [...] # } # Get album info album_info = client.get_album_info('Artist Name', 'Album Name') # Download image client.download_image('https://...', '/path/to/save.jpg') ``` ### Fanart.tv Client ```python from audio.fanart_client import FanartClient client = FanartClient() # Get artist images (requires MusicBrainz ID) images = client.get_artist_images('mbid-here') print(images) # { # 'backgrounds': [{'id': '...', 'url': '...', 'likes': '100'}], # 'thumbnails': [...], # 'logos': [...], # 'logos_hd': [...], # 'banners': [...], # 'album_covers': [...] # } # Get best thumbnail thumbnail_url = client.get_best_artist_image('mbid-here', 'thumbnail') # Get album images (requires MusicBrainz release ID) album_images = client.get_album_images('release-mbid-here') # Search for MusicBrainz ID by name mbid = client.search_by_artist_name('Artist Name') ``` ## Django Admin The artwork models are registered in Django admin with useful actions: ### Artwork Admin - Filter by type, source, primary flag - Search by audio/channel name and URL - Actions: - Download selected artwork - Set as primary ### Music Metadata Admin - Filter by genre, year - Search by audio, album, artist - Actions: - Fetch from Last.fm - Update ID3 tags ### Artist Info Admin - Search by channel name, bio, tags - Actions: - Fetch from Last.fm ## Architecture ### Models #### Artwork ```python - audio: ForeignKey to Audio (optional) - channel: ForeignKey to Channel (optional) - artwork_type: audio_thumbnail, audio_cover, album_cover, artist_image, artist_banner, artist_logo - source: youtube, lastfm, fanart, manual - url: Remote URL - local_path: Local file path - width, height: Dimensions - priority: Priority for selection (higher = better) - is_primary: Primary artwork flag ``` #### MusicMetadata ```python - audio: OneToOne with Audio - album_name, album_artist, release_year - track_number, disc_number - genre, tags - lastfm_url, lastfm_mbid, play_count, listeners - fanart_artist_id, fanart_album_id ``` #### ArtistInfo ```python - channel: OneToOne with Channel - bio, bio_summary - lastfm_url, lastfm_mbid, lastfm_listeners, lastfm_playcount - tags, similar_artists - fanart_id ``` ### Services #### ID3TagService - Broad codec support: MP3, MP4/M4A, FLAC, OGG Vorbis, Opus, WavPack, APE, Musepack, DSF, DFF, AIFF, WAV - Cover art embedding/extraction for all formats - DSD format support (DSF, DSDIFF) with sample rate detection - Uses mutagen library with format-specific handlers #### LastFMClient - Wrapper for pylast library - Track, album, and artist search - Image URL extraction - MusicBrainz ID retrieval #### FanartClient - REST API client for Fanart.tv - High-quality artwork retrieval - Requires MusicBrainz IDs ### Celery Tasks All tasks are designed to be: - **Asynchronous** - Don't block request handling - **Retryable** - Auto-retry on failure (max 3 times) - **Idempotent** - Safe to run multiple times - **Scheduled** - Run automatically via Celery Beat ## Best Practices 1. **Always use Celery tasks** for external API calls and file operations 2. **Check for existing artwork** before creating duplicates 3. **Set appropriate priorities** - Fanart (30) > Last.fm (20) > YouTube (10) 4. **Cache artwork locally** to reduce API calls 5. **Use MusicBrainz IDs** when available for better matching 6. **Handle missing API keys gracefully** - fallback to YouTube thumbnails ## Troubleshooting ### No artwork fetched - Check API keys are configured correctly - Verify artist/track names match Last.fm database - Check Celery logs for errors ### Artwork not embedded in file - Ensure audio file format is supported (all major formats including DSD) - Check file permissions - Verify artwork was downloaded locally first - Note: Some formats (OGG, Opus) use base64-encoded pictures in metadata ### Last.fm API errors - Rate limit: 5 requests per second - Check API key validity - Some tracks may not be in database ### Fanart.tv API errors - Requires valid MusicBrainz ID - Free tier has rate limits - Not all artists have artwork ## Future Enhancements - [ ] MusicBrainz API integration for better matching - [ ] Spotify API for additional metadata - [ ] iTunes API for artwork - [ ] Manual artwork upload via frontend - [ ] Artwork quality scoring - [ ] Bulk metadata import/export - [ ] Artwork generation for missing covers