How SpotifySaver's YouTube Music Search Algorithm Matches Spotify Metadata

SpotifySaver matches Spotify tracks to YouTube Music using a multi-stage algorithm that normalizes metadata, executes hierarchical fallback searches, and applies a weighted scoring system to select the best candidate.

SpotifySaver, an open-source tool maintained in the gabrielbaute/spotify-saver repository, bridges the gap between Spotify's streaming catalog and YouTube Music's library through sophisticated metadata matching. The algorithm orchestrates three core Python modules—YoutubeMusicSearcher, ScoreMatchCalculator, and the Track model—to translate Spotify URI metadata into precise YouTube Music URLs with high accuracy.

Metadata Normalization Pipeline

Before executing any search, the algorithm sanitizes both query and candidate strings through a custom _normalize helper function implemented in spotifysaver/services/youtube_api.py and spotifysaver/services/score_match_calculator.py. This function lowercases text, strips common noise words like official, video, lyrics, and audio, removes punctuation including parentheses and brackets, and collapses whitespace.

The normalization code at lines 56-74 in youtube_api.py ensures that "Blinding Lights (Official Video)" and "blinding lights" are treated as identical strings during comparison. This preprocessing is critical for the fuzzy matching stages that follow, as it eliminates formatting discrepancies that would otherwise break exact string comparisons.

Hierarchical Search Strategy with Fallbacks

The YoutubeMusicSearcher class in spotifysaver/services/youtube_api.py implements a resilient three-tier fallback system within its _search_with_fallback method (lines 75-99). Each strategy progressively relaxes constraints until a viable candidate emerges:

1. Exact Match Search (_search_exact_match)

  • Constructs a query from normalized artist, track, and album fields
  • Searches YouTube Music's song catalog with ignore_spelling=True
  • Limits results to 5 candidates for efficiency

2. Album Context Search (_search_album_context)

  • Activates when direct song searches fail
  • Retrieves the full track list from the Spotify album on YouTube Music
  • Scores each track in the album context to find the correct position

3. Fuzzy Match Search (_search_fuzzy_match)

  • Broadens the search to 10 results with spelling corrections disabled
  • Tolerates greater deviation in track naming conventions
  • Serves as the final safety net before failure

Each strategy returns candidate results to _process_results, which hands them to the scoring module for evaluation. The first strategy to yield a candidate meeting the score threshold wins, and the system immediately returns the corresponding YouTube Music URL.

Weighted Scoring Algorithm

The ScoreMatchCalculator class defined in spotifysaver/services/score_match_calculator.py evaluates candidates through _calculate_match_score (lines 45-64), aggregating four weighted components into a final confidence score between 0.0 and 1.0:

Duration Matching (_score_duration) — Weight: 0.3

  • Calculates absolute difference between Spotify and YouTube Music track lengths
  • Awards perfect score if difference ≤ 2 seconds
  • Applies linear degradation for larger discrepancies

Artist Overlap (_score_artist_overlap) — Weight: 0.3 + 0.1 bonus

  • Computes Jaccard-like set intersection of normalized artist names
  • Adds 0.1 bonus when the primary album artist matches exactly

Title Similarity (_score_title_similarity) — Weight: 0.3

  • Uses SequenceMatcher for normalized Levenshtein-style ratio calculation
  • Applies penalty if token overlap falls below 30%
  • Implements safety cap: if title similarity < 0.1, total score cannot exceed 0.5 regardless of other factors

Album Bonus (_score_album_bonus) — Weight: +0.1

  • Adds flat boost when YouTube result's album field contains the Spotify album name

The candidate passes if the total_score meets the threshold: 0.7 for strict mode or 0.6 for standard mode.

Error Handling and Caching

The search_track method implements defensive programming with self.max_retries = 3 attempts for transient failures. Specific exceptions—AlbumNotFoundError and InvalidResultError—trigger retry logic, while unexpected errors are logged before returning None (lines 38-57 in youtube_api.py).

Performance optimization comes through an @lru_cache(maxsize=100) decorator applied to search_track (lines 26-28). This LRU cache stores results for identical Track objects, eliminating redundant API calls when processing playlist duplicates or handling repeated downloads.

Implementation Example

The following demonstrates how to invoke the search algorithm programmatically using the actual classes from the repository:

from spotifysaver.services.youtube_api import YoutubeMusicSearcher
from spotifysaver.models.track import Track

# Initialize with Spotify metadata

track = Track(
    number=1,
    total_tracks=10,
    name="Blinding Lights",
    duration=200,
    uri="spotify:track:7ouMYWpwJ422jRcDASZB7P",
    artists=["The Weeknd"],
    album_artist=["The Weeknd"],
    release_date="2020-03-20",
    disc_number=1,
    source_type="album",
    album_name="After Hours",
)

searcher = YoutubeMusicSearcher()
yt_url = searcher.search_track(track)

print(f"YouTube Music URL: {yt_url}")

To inspect why a particular candidate was selected or rejected during debugging, access the scorer's explanation method:

raw_results = searcher.search_raw(track)
for candidate in raw_results:
    explanation = searcher.scorer.explain_score(candidate, track, strict=False)
    print(explanation)

This outputs the component scores (duration, artist overlap, title similarity, and album bonus) alongside the final threshold decision.

Summary

  • Three-module architecture: YoutubeMusicSearcher orchestrates the flow, ScoreMatchCalculator evaluates candidates, and the Track model provides immutable Spotify metadata
  • Noise-resistant matching: The _normalize function strips non-semantic words and punctuation to enable fuzzy comparisons
  • Tiered fallback logic: Exact match → Album context → Fuzzy search ensures high precision without sacrificing recall
  • Weighted scoring: Duration, artist overlap, and title similarity each contribute 30% to the final score, with a 10% album name bonus
  • Threshold enforcement: Strict mode requires 0.7 confidence; standard mode accepts 0.6, with title similarity caps preventing false positives
  • Performance features: LRU caching (size 100) and 3-attempt retry logic with specific exception handling optimize API usage

Frequently Asked Questions

How does SpotifySaver handle spelling variations in artist or track names?

The algorithm tolerates spelling variations through multiple mechanisms. The _normalize function in spotifysaver/services/youtube_api.py preprocesses strings to remove case sensitivity and punctuation. During scoring, _score_title_similarity uses SequenceMatcher to calculate Levenshtein-style ratios, allowing minor character differences. If exact searches fail, the fuzzy match strategy disables spelling corrections entirely (ignore_spelling=False in YouTube Music's API), letting the weighted scoring algorithm select the closest candidate regardless of minor spelling mismatches.

What happens when no YouTube Music match meets the scoring threshold?

When all three fallback strategies fail to produce a candidate scoring above the threshold (0.6 for standard mode, 0.7 for strict), YoutubeMusicSearcher.search_track returns None after logging a warning. The calling code in spotifysaver/spotsaver/spotsaver handles this null return by skipping the track or reporting it as unavailable, depending on the CLI command context. The retry logic (3 attempts) only triggers for specific exceptions like AlbumNotFoundError, not for threshold failures, ensuring the system fails fast when metadata simply does not match.

Can I adjust the strictness of the metadata matching algorithm?

Yes. The search_track method accepts a strict boolean parameter that adjusts the acceptance threshold from 0.6 (standard) to 0.7 (strict). When strict=True, the ScoreMatchCalculator requires higher confidence in duration, artist overlap, and title similarity before returning a match. Additionally, you can instantiate YoutubeMusicSearcher with custom scoring weights by modifying the ScoreMatchCalculator constants, though this requires subclassing since the weights are defined as class attributes in spotifysaver/services/score_match_calculator.py.

How does the caching mechanism affect repeated searches?

The @lru_cache(maxsize=100) decorator on search_track caches results based on the Track object's hash equality. Identical Spotify tracks—common when processing playlists containing duplicates or when re-running the tool—return instantly without hitting the YouTube Music API. The cache stores up to 100 unique Track objects in memory; least recently used entries are evicted when this limit is exceeded. Note that the cache is process-bound and does not persist between CLI invocations, only within a single Python session.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →