From 74bdb1c84b633b62325fda4a0a59d74b0596dea3 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 9 Apr 2026 04:41:40 +0000 Subject: [PATCH] Fix Unknown Artist fallback during Spotify rate-limiting and update CI to use GHCR - Add fallback chain in spotify_artist_context construction to use track.artists when explicit_artist_context has placeholder names like 'Unknown Artist' - Add fallback in _extract_spotify_metadata to use original_search['artists'] when album_artist resolution fails due to Spotify rate-limiting - This fixes downloads being saved to 'Unknown Artist' folders when Spotify API is rate-limited but actual artist data is available from download sources - Update GitHub Actions workflow to push Docker images only to GitHub Container Registry (ghcr.io) instead of Docker Hub --- .github/workflows/docker-publish.yml | 25 +++++++++------------- web_server.py | 32 +++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 0ebaae8d..781520c3 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -7,6 +7,10 @@ on: description: 'Version tag (e.g. 1.6, 1.7)' required: true default: '2.2' + push: + branches: + - main + - master jobs: build-and-push: @@ -22,11 +26,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 @@ -39,15 +44,5 @@ jobs: build-args: | COMMIT_SHA=${{ github.sha }} tags: | - boulderbadgedad/soulsync:latest - boulderbadgedad/soulsync:${{ inputs.version_tag }} - - - name: Announce release to Discord - if: success() - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_ANNOUNCEMENTS_WEBHOOK }} - run: | - if [ -z "$DISCORD_WEBHOOK" ]; then echo "No webhook configured, skipping"; exit 0; fi - curl -s -H "Content-Type: application/json" \ - -d "{\"embeds\": [{\"title\": \"SoulSync v${{ inputs.version_tag }} Released\", \"description\": \"A new version of SoulSync is available! Pull the latest Docker image to update.\n\n\`\`\`\ndocker pull boulderbadgedad/soulsync:${{ inputs.version_tag }}\n\`\`\`\", \"color\": 5025616, \"footer\": {\"text\": \"SoulSync Auto-Release\"}, \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}]}" \ - "$DISCORD_WEBHOOK" + ghcr.io/${{ github.repository_owner }}/soulsync:latest + ghcr.io/${{ github.repository_owner }}/soulsync:${{ inputs.version_tag || 'dev' }} diff --git a/web_server.py b/web_server.py index 4dfa8b01..4d4c60a2 100644 --- a/web_server.py +++ b/web_server.py @@ -17733,6 +17733,21 @@ def _extract_spotify_metadata(context: dict, artist: dict, album_info: dict) -> _raw_album_artist = _first_aa _album_artists_for_collab = _sa_aa + # SPOTIFY RATE LIMIT FIX: When Spotify is rate-limited, explicit_artist_context and spotify_album + # may have placeholder/incomplete artist data. Use actual track artist data from original_search + # which comes from the download source (HiFi, Tidal, etc.) and contains real artist names. + if not _raw_album_artist or _raw_album_artist in ['Unknown Artist', 'Unknown', '']: + if 'artists' in original_search and isinstance(original_search['artists'], list) and len(original_search['artists']) > 0: + first_artist = original_search['artists'][0] + if isinstance(first_artist, dict) and first_artist.get('name'): + _raw_album_artist = first_artist['name'] + if not _album_artists_for_collab: + _album_artists_for_collab = [first_artist] + elif isinstance(first_artist, str): + _raw_album_artist = first_artist + if not _album_artists_for_collab: + _album_artists_for_collab = [{'name': first_artist}] + collab_mode = config_manager.get('file_organization.collab_artist_mode', 'first') if collab_mode == 'first' and _raw_album_artist: original_search = context.get("original_search_result", {}) @@ -27778,9 +27793,24 @@ def _attempt_download_with_candidates(task_id, candidates, track, batch_id=None) if isinstance(explicit_artist, str): explicit_artist = {'name': explicit_artist} + # Determine artist name with proper fallback chain: + # 1. explicit_artist.name (if not a placeholder like 'Unknown Artist') + # 2. track.artists[0] (actual track artist data from download source) + # 3. 'Unknown Artist' as last resort + explicit_artist_name = explicit_artist.get('name', '') + is_placeholder_name = explicit_artist_name in ['Unknown Artist', 'Unknown', '', 'Various Artists'] + + if is_placeholder_name and track.artists: + # Use actual track artist data instead of placeholder from rate-limited context + spotify_artist_name = track.artists[0] + elif explicit_artist_name: + spotify_artist_name = explicit_artist_name + else: + spotify_artist_name = track.artists[0] if track.artists else 'Unknown Artist' + spotify_artist_context = { 'id': explicit_artist.get('id', 'explicit_artist'), - 'name': explicit_artist.get('name', track.artists[0] if track.artists else 'Unknown'), + 'name': spotify_artist_name, 'genres': explicit_artist.get('genres', []) } # Handle both image_url formats (direct string or images array)