Skip to content

Track matching architecture

Aurral matches tracks with native JavaScript rules. The matcher needs no Python runtime, external service, or audio fingerprints. A source adapter supplies only the evidence that its provider really exposes. A missing artist, duration, or recording ID never counts as a match.

  1. trackIdentity.ts builds the requested recording.
  2. candidateNormalizer.ts and the source adapters turn search results into comparable candidates.
  3. nativeMatcher.ts applies one versioned policy. It checks contradictions and ranks candidates.
  4. releaseJudge.ts decides with the same policy whether a folder, album, or download is exactly the requested release, and pairs each release track with one file.
  5. decisionEngine.ts adds provider evidence and returns accept, verify, review, or reject for each result.

After a download, postDownloadValidator.ts runs the same matcher on the original file tags. Aurral writes its own tags only after this check passes.

  • A strong title score cannot override a known conflict in the recording ID, version, artist, or duration.
  • A recording ID tag counts only when it holds a MusicBrainz ID. Some rip tools write Spotify IDs there. An ID that MusicBrainz merged into the requested recording, which files tagged before the merge still carry, counts as the same recording.
  • Missing evidence does not count as agreement.
  • A file whose size cannot hold music of its length is no copy of any track, however well its name and length match. Some peers share generated digital silence at each track’s exact MusicBrainz length. The encoded bitrate, size times 8 divided by length, must reach 48 kbit/s for a lossless format and 12 kbit/s for a lossy one. Soulseek search checks the listed size and length, and the post-download check measures the real file from every source. The floors were measured on real Soulseek listings, where every real lossless file sat above 77 kbit/s and every real lossy file at 16 kbit/s or more. A track that MusicBrainz titles [silence] is exempt, because its real file is digital silence and is tiny in every lossless format.
  • Editions of one recording differ by a few seconds of mastering, fade, or silence, so a file whose original title and artist tags match exactly is imported within 10 seconds of the requested length, as in Lidarr. A partly matching title needs to be within 2 seconds.
  • A file goes to review only when it is very probably the requested recording: its title matches, or nearly matches, and nothing contradicts it, but its length or artist credit is off. A length more than 30 seconds off, as in beets, is another recording. A file whose title does not match is another song, even from the requested artist with a similar length, and is rejected.
  • A Soulseek, Usenet, or yt-dlp file worth a review waits while the job tries its remaining candidates and the other download sources. It goes to review only when none of them verifies, and keeps its own source details, so denying it blocks that file. A deemix file goes to review at once.
  • An automatic quality upgrade never goes to review, as in Lidarr. The Library already has a copy, so a file that does not verify is dropped and the current file stays.
  • A note on where a track sits on a release, such as “(Bonus Track)” or “(Hidden Track)”, does not change the recording.
  • A credit such as “Kendrick Lamar, Drake” or “Blue Swede & Björn Skifs” names each of its artists, and a leading “The” does not change an artist.
  • A clean, edited, or censored copy contradicts a request that does not ask for one. An “explicit” label does not.
  • A file without usable identity tags is not imported automatically.
  • A track number that points at another track of the requested album is a conflict only when the file comes from that album. A single, a compilation, or another album has its own numbering, and a later disc numbers its tracks from 1 again.
  • A job’s artist ID looked up by name must be an artist with that name or alias, where “&” reads as “and”. A credit with a guest, such as “Koyo feat. DRAIN”, falls back to its lead artist. Search often ranks an artist that only shares a word with the name first.
  • A job’s release group must list its track on one of its editions and credit its artist by ID, name, alias, or lead artist. A release group the job names that does not list the track is looked up again by album title, so another of an artist’s self-titled albums, or another artist’s album with the same title, is never tagged. A release group BrainzMash has no tracklist for counts when it has the track’s title, as for many singles. When no album qualifies, the job keeps its album name without a release group.
  • Aurral takes a recording ID only from MusicBrainz release data, never from Last.fm. The expected length comes from the matched MusicBrainz release track; a Last.fm length, often a radio edit, only fills a missing one.
  • A “Various Artists” credit is not artist evidence. A compilation’s jobs keep “Various Artists” as their artist and name each track’s own artist, with that artist’s other names from MusicBrainz, as aliases for matching and searching. Each file is tagged with its own artist and “Various Artists” as the album artist, so media servers group the album, and the Library keeps each track’s own artist.

A title in a file name keeps its dash-separated version suffix. A remaster can match the original recording, but a radio edit must match the requested version. When the part before the dash names more than the artist, as in Artist [Album] 08 - Title, the part after it counts if it is closer to the requested title.

Matching removes Latin accents and treats equivalent Unicode spellings as equal. If either core title contains non-Latin letters, two different normalized titles are a contradiction, even when the artist, duration, or recording ID agrees. This rule also applies to file names. A transliteration or spelling difference in such a title needs the user to choose. A release listing is the exception, as Exact release, not best fit describes.

yt-dlp finds one track at a time. It searches YouTube Music first, and searches YouTube videos only when no YouTube Music song is the requested recording.

The YouTube Music song search for Artist Title lists only song IDs and titles. Aurral reads the songs whose titles could be the requested track for YouTube Music’s own artists, album, and length, three at a time and at most six. It skips an entry without a title and a song the user denied. A song that cannot be read, such as an age-restricted one, is left out. Every read a batch starts is waited for, so one search always judges the same songs. After each batch, the search stops when a song is accepted or verified, so it reads the next three only when none read so far could be the track. The whole YouTube Music search has a 6-second deadline, because now and then one read stalls for 15 seconds or more. At the deadline the reads still running are killed and the songs already read are judged, so a stalled read holds its batch until then. In a live sample of 16 tracks searched nine times each, the whole yt-dlp search took a median of 4.0 seconds and 9.3 seconds at the 90th percentile. The slowest searches are the ones that fall back to videos, either because no song is the track or because the song list or every read in a batch stalled past the deadline.

A song is judged on those names, the way a store result is:

  • An artist other than the requested one, such as a cover or a tribute act, is a contradiction.
  • Version wording in the title, such as “Radio Edit”, “(Live)”, “Acoustic”, “Karaoke”, “Stereo Mix”, or “Alternate Take”, contradicts a request that does not name it, through the same semantic policy as every source. A requested track or album name that names the version allows it, so a request on “Pet Sounds (Stereo Mix)” allows that album’s “(Stereo Mix)” songs. An artist name allows only karaoke, sped-up, slowed, nightcore, 8D, or tribute versions, which such an act makes of every song, so Acoustic Alchemy does not allow “(Acoustic)”. “(2004 Remaster)”, “Stereo Remaster”, and “(feat. …)” do not contradict. Neither does “(Mono)”, because albums of the 1960s label their own tracks that way. It contradicts only a request that names stereo, and “Stereo” contradicts only one that names mono. An album name such as “Live at Wembley” or “Acoustic Sessions” contradicts too, but a live album whose name does not say “live” passes.
  • A song with the requested artist, the exact title, and a length within 2 seconds is the recording. On the requested album it is accepted outright, and every copy there, such as the two “Dreams” on Rumours, is accepted. On another album, such as a compilation, it is verify: it comes after the requested album’s copies and is checked after download with the 2-second window, because it may be another mix or a live take with the same title.
  • Any other song must be on the requested album when the request names one, or it is rejected.

Release checks, such as track count or tracklist, do not apply to yt-dlp.

When no song is accepted or verified, or the YouTube Music search fails, Aurral searches videos for Artist Title, then Artist Title official audio. It searches Title Artist only when those find no videos. A YouTube Music song ID is also a video ID, so a video replaces the song with its ID. A song rejected for its album can still be chosen as the artist’s own upload, judged on its channel. At most ten yt-dlp processes search for one track: the song search, six song reads, and three video searches. The YouTube Music part ends within 6 seconds, and each video search has a 60-second timeout.

Aurral reads a video title the same way before and after the download. It removes upload wording such as “Official Music Video”, “(Audio)”, “Audio Oficial”, or “| Album”. The part of the title that names the requested artist is the artist, whether it comes first, last, or before 「Title」, and the part closest to the requested title is the title. A channel’s name counts as the artist only when it is the artist’s own channel, such as “Fleetwood Mac - Topic” or “DaftPunkVEVO”.

An upload on the artist’s channel that is not a music video is the release audio: the “- Topic” uploads, “(Audio)” uploads, lyric videos, and visualizers. Aurral downloads those first and gives them the 10-second length window. A music video, which is often cut differently, or an upload on another channel needs to be within 2 seconds, because re-uploads are often re-timed.

yt-dlp names files by video ID, so the file name is not title evidence. A YouTube Music song is downloaded with its own track, artist, and album tags. Aurral checks them as it checks any file’s tags. The 10-second window for an exact title and artist applies only to a song on the requested album. A video download gets YouTube’s track and artist when YouTube has them. Otherwise yt-dlp splits a title of the form Artist - Title at the first dash, or names the uploader as the artist. A song’s title is never split, because “First Love - From THE FIRST TAKE” is one title. A YouTube title must match exactly: uploader words such as “Concept” or “Reworked” send the file to review.

When a user chooses a result in a manual search, the choice decides the identity. The validator still requires readable audio with a known quality tier. It accepts tiers that the quality profile turns off. It does not apply the title, artist, album, duration, or version rules. Aurral downloads only the chosen result and does not fall back to another result or source.

MATCH_POLICY.version in nativeMatcher.ts is stored with each matcher decision and returned by the health response. Change the policy in one place. Then run the fixture evaluation and the cross-source regression tests.

Every track job whose release group is known pins one exact MusicBrainz release before any album source searches. This covers Library, static playlist, flow, and upgrade jobs, single tracks included. An upgrade’s attempt holds only upgrades: every pending one of the release, whatever request or playlist its track came from. The upgrade check starts an album’s upgrades together, so their release downloads once and is judged whole. The check adds no upgrade for a playlist whose owner is not active, and such an upgrade never leads or joins an attempt. An upgrade attempt that starts while an earlier one still downloads a release of the same album waits at its search, without taking a search slot, until that attempt ends or falls back to a track search, and for at most 30 minutes. It then downloads its release whole. NZBGet puts two downloads of one NZB in one folder, and a Usenet title does not name the pressing, so two releases of one album can pick the same NZB. Manual replacements skip it. A track request, which is any job except one a whole-album request created (request_kind), first runs a plain track search on the track-only sources that rank above every album source, and pins its release only after they import nothing. The job records each of those sources when its search ends (quick_searched_sources), and its later track search skips them even if the priority order changes. A job that ended and is asked for again starts with none recorded. A file one of them holds for review is kept on the job (held_for_review) until the job ends. Each later attempt of the job carries it, including after a restart or a release attempt that gives the track back. It goes to review only when the last search verifies nothing. A track whose held file goes to review may try its release again if the file is denied.

releaseResolver.ts chooses the release, and releaseManifest.ts reads it as a full ordered tracklist with lengths. A release keeps the lengths it lists, in the manifest, on the album page, and in a job’s requested length. A length missing from the release takes the median of its other pressings with the same running order that list the same recording at that place. Without one, the length stays unknown. A Library album stores the edition it asked for as metadata.requestedRelease, apart from trackList.release, which names the release its files came from. Its jobs ask for that release. Other jobs try the Library album’s release when there is one, then the standard edition, and keep the first one that lists their track. Failing both, they take the commonest tracklist among the releases that hold the track, by recording or by its place and title. The job stores the release ID in release_mbid, so a restart asks for the same edition. A pinned release that MusicBrainz no longer lists, as after a merge, is chosen again. The job saves the new pin, and a Library album that asked for the old release asks for the new one.

Aurral refuses a tracklist that it cannot prove complete. That is one whose track count differs from the count it declares, one with a track that has no position, or one with a disc whose places skip a number. A pinned release that is refused or does not list the track leaves the job without a release. The job then goes to track search, and its reason says why. A metadata outage is different: a track that searches by release waits while its release cannot be read, with the reason “Waiting for metadata to read the album release”. So does a track known only by its album’s name, or by a release and an album name as ListenBrainz gives it, while metadata cannot find its album. It keeps the release it names while it waits. It tries again every 60 seconds, up to 30 times, and goes to track search only after the last try. A track request that can start its quick search now does not wait, because the quick search needs no release. A track whose album is not known yet still waits. When its quick search ends while the release cannot be read, the step waits the same way, keeping any file held for review, and does not count as a search while it waits. Once the release reads, every waiting track of that album starts one attempt together.

Album-capable sources search with the album alone, in the configured priority. With the default priorities, that is Soulseek, then deemix, then Usenet. yt-dlp is a track-only source. When a source runs out of releases, the next album-capable source searches for the tracks still unfilled. Only then do the remaining jobs fall back to per-track search through every source. An attempt takes the job and the pending jobs of its request that the release lists, unless they are pinned to another release that the release group still lists. A group of one is allowed. Every job in the attempt saves its release ID. Lidarr albums go through Lidarr. Cancellation removes only work that belongs to the request, and queued pipeline state resumes after a worker restart. A job left mid-search with no queued step goes back to the worker, which chooses its release again.

An attempt can end before its source judges the release: its leading job stops or is cancelled, or its release cannot be read when it searches. The tracks it still holds then go back to the worker untried, so they can start another attempt, and the source removes the search, transfers, or download it started.

When an album step fails on every retry, the source removes the work it started, and a release it already downloaded is blocked for the attempt’s tracks. The next album-capable source then searches for the tracks. After the last one, every track the attempt held, the leading one included, goes back untried behind fresh work, because such failures are most often a passing outage. The third attempt that ends this way sends each track to track search instead. The count is stored with each job and starts again when the job finishes or fails. A track search that follows an album attempt tries the sources whose album step failed this way last. A track step that fails on every retry while searching or downloading removes its work and moves to the next source, and the job fails only when no source is left. A track that fails on every retry while importing a download that passed its checks keeps that download, and the job fails with the import error, because another source’s copy would fail to import the same way. A Usenet download whose client cannot be read keeps waiting, every 30 seconds for up to 30 minutes, because the download goes on in the client.

The earlier rule matched files against whichever release in the group fit best and accepted 80 percent coverage. That let a deluxe folder fill a standard request and let a shorter edition complete an album. judgeRelease() instead decides whether a listing is exactly the pinned release. A listing that is not gives no tracks, and the tracks it lacks stay wanted. The cost is that a standard request passes over a deluxe folder that holds every wanted track. Track search then finds those tracks one at a time.

One judge reads a Soulseek folder and a Deezer tracklist before download, and the files of a Usenet download after it. Each source only parses its results into a listing. The judge applies these rules:

  • Each release track pairs with one file. The pair needs identity: a recording ID, a similar title with a known length, an exact title at its disc and track position, or an untitled file at its position with a known length and the release’s artist named. A file title counts as untitled when it is a placeholder such as “Track 01”, or when it is Latin for a non-Latin track title, or the reverse. A file numbered 101 or 201 is at track 1 of disc 1 or disc 2. A recording contradiction, a known wrong position, or a known length more than 2 seconds off blocks the pair. A mix label such as “(Mono)”, “(Stereo Mix)”, or “(Alternate Take)” in a file’s title does not, because at its place a file is that release’s own mix. The Library pairs its files with an album’s places by the same rule. A length counts as known when it is the track’s length on the pinned release, or on another official pressing with the same running order that lists the same recording at that place. So a rip of the CD fits a pin on its vinyl pressing, and a rip of a remix or re-recording in the same release group does not. When no length is known, an exact title at its place is still identity, so a re-recording’s rip with the same titles fits too. The 2-second limit was measured on reviewed real Soulseek listings. Every listing reviewed as a match paired within 1 second, and Soulseek reports whole seconds, so the limit leaves one second for rounding. The listings reviewed as uncertain for timing were more than 3 seconds off, so the limit still rejects them.
  • An unpaired file that is another copy of a paired song is a duplicate. So is a copy at the paired track’s place whose length is up to 10 seconds off, such as a VBR MP3 beside its FLAC, though Aurral never downloads it. A file that names a missing track at its place, with no length or a length up to 10 seconds off, is an unverified copy of that track. The listing then lacks the track, but holds no extra song. Any other unpaired file is an extra and rejects the listing. The judge never compares the file count with the track count. The Soulseek selector skips a folder with fewer files than tracks before it judges any folder.
  • A product marker such as “box set”, “bootleg”, or “discography” in the album label rejects the listing unless the release names it. An artist label that names no one on the release, or an album label or album tag that names another album, also rejects it. A version such as live, acoustic, or remix in a label, folder, or file is checked against the requested album, artists, and track, and against the release group’s MusicBrainz type. A Live, Remix, or Demo release group asks for that version on every track. A label or folder may also name a version that the pinned release’s own tracks are, where those tracks make up at least half of a disc that holds at least a quarter of the release’s tracks. So a deluxe edition with a live disc accepts “(Bonus Live Disc)”, and a single of a song’s remix, demo, and instrumental accepts “(Remixes)”. A standard edition with one live bonus track still rejects “(Live at Wembley)”, whether the track ends the first disc or sits on a bonus disc of its own. A file’s title may not name another track’s version. Every version word in a label counts, however the label notes it. A hyphen joins the words of one version, so “(Sped-Up)” and “Artist-Album-Sped-Up-WEB-2019-GRP” name a sped-up release. A phrase never runs across a bracket, a slash, or a spaced dash, so “[Covers] [Album Art]” and “[FLAC/Covers/Album Art]” are not covers albums. “Covers” followed by “album art”, “album artwork”, “album scans”, or “album cover” names artwork, so “[FLAC-Covers-Album Art]” and “[FLAC, Covers, Album Art]” pass too. “(Reissue + Live In London Bonus Disc)” and “[Remix Bonus Tracks Version]” name live and remix releases, and a scene name such as “Artist-Album_Live-WEB-2013-GRP” names a live one. Artwork, an edition, bonus tracks, or a format names no version, so “[FLAC+Covers]” and “(Deluxe Edition)” pass. The Soulseek selector, the judge, and Usenet ranking read labels the same way. The pinned release’s own versions count only in the judge, in Usenet ranking for an album download, and in the check of files imported as a whole album. Track search and its post-download check ignore them, because a deluxe edition’s live bonus tracks would otherwise admit copies from other live albums. A version word inside a requested name is part of the name, as in the album “Live Through This”. A one-word name spelled like a version word, such as the band Live, is the name only where it stands alone between dashes in the label, as in “Live - Throwing Copper”. So “Live at the Paradiso” is a live album even when the band Live is requested.
  • A Soulseek folder in which copies of two or more release tracks are below the bitrate floor is rejected as implausible-file, because a peer who shares generated silence mixes it with real files. One such file is more likely a damaged copy or a silent track on the release. That folder can still be the release, but it ranks after an otherwise equal folder without one, and the file fails its post-download check.
  • The listing holds every audio file, including files that the quality profile would refuse. Quality decides only which copy to download. A filter before the judge would hide a low-bitrate bonus track and make a deluxe folder look exact.

Soulseek and deemix show the tracklist, so Aurral judges them before download. From an exact Soulseek folder, it downloads only the wanted files. From an exact Deezer album, it queues the URL of each wanted track, and the album URL only when every track of the release is wanted. A later album in the same attempt queues only the tracks that earlier albums did not import. On Soulseek, each wanted track needs a copy that the quality profile accepts. On deemix, the account must be able to stream each wanted track.

A Soulseek folder named “CD2”, “Disc Two”, “LP 2”, or “Disc 1 - Title” is a disc of the release folder above it. A folder named for the requested album, such as Ratatat’s “LP4”, “LP 4”, or “LP4 (2010) [XL Recordings]”, is the album’s own folder and never a disc. This applies to downloaded album files too.

Several exact Soulseek folders are usually the same rip shared by several people, so Aurral ranks them by quality tier, fit, free upload slot, queue length, and speed instead of abstaining. A Soulseek listing without lengths can pair exact titles at their positions. The post-download check still compares the real durations.

A Usenet release title cannot show its tracklist, so Aurral judges the downloaded files. A download that is not exact, or that holds an unreadable audio file, imports nothing and blocks the NZB for every wanted job.

Aurral assigns the files to the wanted jobs, places each at its track and disc number on the pinned release, and never uses one file twice. Each assigned file must pass its own post-download check before it moves into the Library. That check accepts the same pressing lengths and version names that the judge accepted.

Per-track search keeps the pinned release, including its MusicBrainz type and the lengths of its other pressings, so a live album’s tracks still accept their live copies. In Soulseek results of the same quality tier and matcher decision, a file from a folder that holds the track at its place ranks first. That folder holds nothing extra, but it may lack other tracks or hold only unverified copies of them. In Usenet results, a release of the requested album ranks before a single within one quality tier.

The download pipeline runs one step at a time, and each step holds its owner’s download step lock. The worker chooses a job’s release before it queues the job. A step reads that release’s tracklist from the metadata cache, which asks the provider only when it is cold. The step then makes one provider call. It starts a search, polls a running search once, runs one Prowlarr query, or moves a download forward.

A finished album download needs its release to place the files. When the step cannot read the release, it pins the attempt again to the release that its leading track resolves to, as after a MusicBrainz merge. If the metadata still cannot be read, the step waits 60 seconds and tries again, up to 30 times in a row. Only then does the download finish without a release, and its tracks go to track search. A metadata outage therefore never discards a finished download at once. Once the release reads again, the step goes on at once, and a later outage gets 30 more tries. An album download queued by a version of Aurral that did not pin releases resolves its release the same way, and one still searching stops its search and gives its tracks back to start again. Its files are judged against the release before import, because its listing was never judged. For Soulseek, which downloads only wanted files, that passes only when every track of the release was wanted. Soulseek and Usenet results stay in a 15-minute cache keyed by query, so a restart repeats only the queries whose results it lost. The download worker hands a job to the pipeline only while fewer jobs than its concurrency are searching.

  1. Declare the evidence that the source provides in candidateNormalizer.ts.
  2. Map provider results through buildSourceCandidates(). Keep provider-specific availability checks in the adapter.
  3. Run the shared post-download validator before you write tags or import files.
  4. For release downloads, build a listing of every audio file that the provider shows and judge it with judgeRelease(). Mark a candidate releaseVerified only when the listing was judged before download. Pass the finished file paths and the payload fields to clear for the next candidate to finishAlbumGrab(), and test partial and failed downloads.
  5. Keep each pipeline step to one provider call, and block a file or release that fails its checks with recordDeniedSource().

.tests/track-matching/fixtures/ holds development and acceptance cases built from CC0 MusicBrainz data. The two sets share no identities. To get correct, wrong, abstained, contradicted, and yield counts for each flow, run:

Terminal window
node .tests/track-matching/native-evaluation.mts

The held-out acceptance run is a release gate. Tune rules against the development set only. The cross-source and download tests cover provider mapping, real audio tags, album imports, partial failures, cancellation, and fallback.

These fixtures hold no real provider listings. Check a change that depends on how peers name, split, or time their files against real search results, and count a selection as correct only when reviewed evidence supports it.

Metadata cannot tell apart two recordings with identical visible evidence. Aurral does not use audio fingerprints. If that case matters, review the downloaded file before import, or choose the file with a manual search.