pub struct Catalog {Show 18 fields
pub roots: Vec<String>,
pub excluded: Vec<String>,
pub scanned_at: u64,
pub files: Vec<AudioFile>,
pub file_mtime_subseconds: BTreeMap<String, u32>,
pub file_added_at: BTreeMap<String, u64>,
pub analyses: Vec<FileAnalysis>,
pub artists: Vec<Artist>,
pub releases: Vec<Release>,
pub release_groups: Vec<ReleaseGroup>,
pub tracks: Vec<Track>,
pub recordings: Vec<Recording>,
pub works: Vec<Work>,
pub labels: Vec<Label>,
pub genres: Vec<Genre>,
pub credits: Vec<Credit>,
pub relations: Vec<Relation>,
pub genre_links: Vec<GenreLink>,
}Expand description
The complete catalog.
Fields§
§roots: Vec<String>Scanned root folders.
excluded: Vec<String>Folders never read, however deep under a root they sit.
Kept beside the roots rather than passed to each scan, because a plain
aede scan re-reads every root: an exclusion that had to be retyped
would be forgotten precisely when it mattered. Canonical, like the
roots, so the comparison against a walked path is the same string
comparison everywhere else in the program makes.
scanned_at: u64Date of the last scan (Unix epoch, seconds).
files: Vec<AudioFile>The files that were read, in the order the scan settled on.
file_mtime_subseconds: BTreeMap<String, u32>Nanosecond fractions of file modification dates, keyed by native path.
Stored as an optional mtime_subseconds field on each file row. Legacy
rows without that field need one fresh read before they can be reused;
guessing zero would miss real same-second changes. Keeping the fraction
apart from epoch seconds avoids losing precision in JSON numbers.
file_added_at: BTreeMap<String, u64>First successful catalog observation by path, in Unix seconds. Absent legacy dates remain unknown; file modification dates are not substitutes.
analyses: Vec<FileAnalysis>Deep analyses imported from another tool, one row per path and source.
Optional by nature: a catalog without a single one works exactly the same. They are kept apart from the files rather than merged into them, so that a measurement always answers “who says so”.
Keyed by path, which means some of them may describe files the catalog does not hold — a library analysed before it was ever scanned. Those wait here until a scan brings the file in.
artists: Vec<Artist>Artists, one entry per normalized key.
releases: Vec<Release>Releases, one entry per title, owner and folder.
release_groups: Vec<ReleaseGroup>Release groups shared by explicitly identified local editions.
tracks: Vec<Track>Tracks, one entry per file that carried readable tags.
recordings: Vec<Recording>Recorded performances, distinct from their local track placements.
works: Vec<Work>Musical compositions explicitly linked to local recordings.
labels: Vec<Label>Labels, one entry per normalized key.
genres: Vec<Genre>Genres, one entry per normalized key.
credits: Vec<Credit>Every “who did what, on what” observation, without repeats.
relations: Vec<Relation>Links between entities; symmetric ones are stored in both directions.
genre_links: Vec<GenreLink>Attachments of genres to tracks and to releases.
Implementations§
Source§impl Catalog
impl Catalog
Sourcepub fn composers_by_release(&self) -> BTreeMap<Id, BTreeSet<Id>>
pub fn composers_by_release(&self) -> BTreeMap<Id, BTreeSet<Id>>
Index credited composers once per projection/evaluation. Recording and work credits reach every local edition containing that recording; duplicate credits stay one identity. This reads the local graph only, never unaccepted external claims or audio files.
Source§impl Catalog
impl Catalog
Sourcepub fn release_added_at(&self, id: Id) -> Option<u64>
pub fn release_added_at(&self, id: Id) -> Option<u64>
Earliest known file entry date; any unknown placement keeps an existing edition undated.
Sourcepub fn playback_release_order(&self, ids: Vec<Id>) -> Vec<Id> ⓘ
pub fn playback_release_order(&self, ids: Vec<Id>) -> Vec<Id> ⓘ
Order selected releases for playback: oldest first, unknown dates last. Equal years retain the CLI folder/title tie breakers; track order is untouched.
Sourcepub fn artist(&self, id: Id) -> Option<&Artist>
pub fn artist(&self, id: Id) -> Option<&Artist>
The artist an id designates, or None when the id is out of range.
Sourcepub fn release(&self, id: Id) -> Option<&Release>
pub fn release(&self, id: Id) -> Option<&Release>
The release an id designates, or None when the id is out of range.
Sourcepub fn release_group(&self, id: Id) -> Option<&ReleaseGroup>
pub fn release_group(&self, id: Id) -> Option<&ReleaseGroup>
The canonical group an id designates, if this catalog identified it.
Sourcepub fn track(&self, id: Id) -> Option<&Track>
pub fn track(&self, id: Id) -> Option<&Track>
The track an id designates, or None when the id is out of range.
Sourcepub fn recording(&self, id: Id) -> Option<&Recording>
pub fn recording(&self, id: Id) -> Option<&Recording>
The recorded performance an id designates, if it is in this catalog.
Sourcepub fn tracks_of_recording(&self, recording_id: Id) -> Vec<Id> ⓘ
pub fn tracks_of_recording(&self, recording_id: Id) -> Vec<Id> ⓘ
Every local placement of one recording, in catalog order.
Sourcepub fn work(&self, id: Id) -> Option<&Work>
pub fn work(&self, id: Id) -> Option<&Work>
The composition an id designates, if this catalog explicitly knows it.
Sourcepub fn recordings_of_work(&self, work_id: Id) -> Vec<Id> ⓘ
pub fn recordings_of_work(&self, work_id: Id) -> Vec<Id> ⓘ
Every recording explicitly known to realize this composition.
Sourcepub fn find_recordings(&self, query: &str) -> Vec<&Recording>
pub fn find_recordings(&self, query: &str) -> Vec<&Recording>
Recordings whose title or MusicBrainz identifier matches the query.
Sourcepub fn find_works(&self, query: &str) -> Vec<&Work>
pub fn find_works(&self, query: &str) -> Vec<&Work>
Works whose title or MusicBrainz identifier matches the query.
Sourcepub fn find_release_groups(&self, query: &str) -> Vec<&ReleaseGroup>
pub fn find_release_groups(&self, query: &str) -> Vec<&ReleaseGroup>
Release groups whose title or MusicBrainz identifier matches.
Sourcepub fn file(&self, id: Id) -> Option<&AudioFile>
pub fn file(&self, id: Id) -> Option<&AudioFile>
The file an id designates, or None when the id is out of range.
Sourcepub fn relations_from(&self, kind: EntityKind, id: Id) -> Vec<&Relation>
pub fn relations_from(&self, kind: EntityKind, id: Id) -> Vec<&Relation>
Every outgoing graph relationship from one entity.
Targets reached through one typed relationship.
Sourcepub fn analyses_of(
&self,
file: &AudioFile,
) -> impl Iterator<Item = &FileAnalysis>
pub fn analyses_of( &self, file: &AudioFile, ) -> impl Iterator<Item = &FileAnalysis>
Every imported analysis that describes this file, whatever the source.
Sourcepub fn pending_analyses_list(&self) -> Vec<&FileAnalysis>
pub fn pending_analyses_list(&self) -> Vec<&FileAnalysis>
Imported analyses describing files the catalog does not hold, in full.
Same population Catalog::pending_analyses counts. Kept apart
because most callers only want the number — doctor says “N waiting”
on every run — and building the list, with its own reference into
every path, is a heavier answer nobody should pay for just to learn
how many there are. aede import --pending is the caller that does
want the list, to say which ones.
Sourcepub fn pending_analyses(&self) -> usize
pub fn pending_analyses(&self) -> usize
Imported analyses describing files the catalog does not hold.
Not an error and not garbage: the usual reason is that the folder they speak of has not been scanned yet. Counting them is how the interface can say “twelve analyses are waiting for a scan” instead of losing them without a word.
Sourcepub fn label(&self, id: Id) -> Option<&Label>
pub fn label(&self, id: Id) -> Option<&Label>
The label an id designates, or None when the id is out of range.
Sourcepub fn genre(&self, id: Id) -> Option<&Genre>
pub fn genre(&self, id: Id) -> Option<&Genre>
The genre an id designates, or None when the id is out of range.
Sourcepub fn total_duration_ms(&self) -> u64
pub fn total_duration_ms(&self) -> u64
Total duration of the library, saturating at u64::MAX for oversized measures.
Sourcepub fn total_size(&self) -> u64
pub fn total_size(&self) -> u64
Total size on disk, saturating at u64::MAX for oversized measures.
Sourcepub fn releases_as_album_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn releases_as_album_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
The artist’s own discography: releases they are the album artist of.
This is what a listener means by “their albums”. A guest appearance on
somebody else’s record belongs in Catalog::guest_appearances.
Sourcepub fn guest_appearances(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn guest_appearances(&self, artist_id: Id) -> Vec<Id> ⓘ
Releases the artist is audible on without being the album artist: featured vocals, a guest solo, one track on a compilation.
Sourcepub fn releases_with_writing_credit(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn releases_with_writing_credit(&self, artist_id: Id) -> Vec<Id> ⓘ
Releases where the artist holds a credit that is not a performance.
All of them, including the ones they also play on. Ozzy Osbourne composes most of what he sings: a figure that subtracted those would report one album where the credits count sixty-nine, and the number would be right about a set nobody asked for.
Sourcepub fn writing_tracks_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn writing_tracks_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
Tracks where the artist holds a credit that is not a performance.
Sourcepub fn releases_written_without_performing(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn releases_written_without_performing(&self, artist_id: Id) -> Vec<Id> ⓘ
Releases the artist wrote or produced and is not audible on.
Not a measure of anything — a display set. It exists so the artist page
can show the records that would otherwise never appear on it, without
repeating the discography above. Never label it “writing”: that is
Catalog::releases_with_writing_credit.
Sourcepub fn performed_tracks_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn performed_tracks_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
Tracks the artist is audible on, without duplicates.
Sourcepub fn written_tracks_without_performing(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn written_tracks_without_performing(&self, artist_id: Id) -> Vec<Id> ⓘ
Tracks the artist wrote or produced and is not audible on.
The display counterpart of Catalog::performed_tracks_of_artist:
together they cover every track credit without counting one twice, a
track where someone both plays and composes going to the first. For
“how much did this person write”, use
Catalog::writing_tracks_of_artist.
Sourcepub fn releases_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn releases_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
Every release the artist appears on, whatever their role.
Sourcepub fn tracks_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
pub fn tracks_of_artist(&self, artist_id: Id) -> Vec<Id> ⓘ
Tracks credited to an artist, without duplicates.
A single artist may hold several roles on one track (performer and composer): without deduplication, every count would be inflated.
Sourcepub fn credits_on(&self, kind: EntityKind, id: Id) -> Vec<(&Artist, &str)>
pub fn credits_on(&self, kind: EntityKind, id: Id) -> Vec<(&Artist, &str)>
Artists credited on an entity, along with their role.
Sourcepub fn neighbours_of_artist(&self, artist_id: Id) -> Vec<(&Artist, u32, &str)>
pub fn neighbours_of_artist(&self, artist_id: Id) -> Vec<(&Artist, u32, &str)>
An artist’s neighbours in the graph, from strongest link to weakest.
Sourcepub fn tracks_in_common(&self, a: Id, b: Id) -> Vec<Id> ⓘ
pub fn tracks_in_common(&self, a: Id, b: Id) -> Vec<Id> ⓘ
Tracks on which two artists are both credited in a performing role.
This is what the weight of a collaborated relation counts, recomputed
on demand rather than stored: the credit table already holds the
answer, and a second copy is a second thing that can fall out of step.
Releases tied to this one by a relation of the given kind.
Used with crate::model::DUPLICATE and crate::model::OTHER_EDITION
to answer “is this album here twice, and on purpose?”.
Sourcepub fn search(&self, query: &str, limit: usize) -> Vec<SearchHit>
pub fn search(&self, query: &str, limit: usize) -> Vec<SearchHit>
Case- and accent-insensitive search, across every entity.
Sourcepub fn find_artist(&self, name: &str) -> Option<&Artist>
pub fn find_artist(&self, name: &str) -> Option<&Artist>
Finds an artist by name, up to normalization.
Sourcepub fn find_artists(&self, name: &str) -> (Vec<&Artist>, TitleMatch)
pub fn find_artists(&self, name: &str) -> (Vec<&Artist>, TitleMatch)
Every artist whose name matches, exactly or failing that partially.
The rule Catalog::find_releases and Catalog::find_tracks
already followed, and that this one did not. find_artist matched
exactly and nothing else, so every caller that wanted osbourne to
reach Ozzy bolted a fuzzy search underneath and took its first hit
— an arbitrary answer among several, given without a word. On a shelf
holding both Ozzy Osbourne and O. Osbourne that is not a nicety:
aede artist osbourne and aede artist ozzy answered about two
different people, one of them with a biography and thirteen albums and
the other with seven.
So: exact first, and only widen when nothing was exact — the same shape,
returning the same TitleMatch, so a caller can say which of the two
happened and refuse to arbitrate between several.
Sourcepub fn find_releases(&self, title: &str) -> (Vec<&Release>, TitleMatch)
pub fn find_releases(&self, title: &str) -> (Vec<&Release>, TitleMatch)
One release by MusicBrainz identity, or every title match.
Same rule as Catalog::find_tracks, and for the same reason: a
command must not pick one answer out of several without saying so.
An identifier wins before names. Unlike tracks, two matching albums are
two different editions — a shared prefix is not an ambiguity — which
is why an exact title usually ends the name search without choosing one.
Sourcepub fn find_tracks(&self, title: &str) -> (Vec<&Track>, TitleMatch)
pub fn find_tracks(&self, title: &str) -> (Vec<&Track>, TitleMatch)
Every track carrying this title, up to normalization.
A title is not an identifier: the same one legitimately comes back on the album, on a single and on a live record, and those are different recordings. All of them are returned, in catalog order.
Exact matches win. Only when there is none does the search widen to the
titles containing the text, so that a half-remembered title still leads
somewhere; TitleMatch says which of the two happened.
Sourcepub fn find_genres(&self, name: &str) -> (Vec<&Genre>, TitleMatch)
pub fn find_genres(&self, name: &str) -> (Vec<&Genre>, TitleMatch)
Every genre whose name matches, exactly or failing that partially.
Same rule as Catalog::find_releases: “metal” must reach the genre
spelled exactly that way without hiding “Black Metal” and “Doom Metal”
when there is no exact one.
Sourcepub fn find_labels(&self, name: &str) -> (Vec<&Label>, TitleMatch)
pub fn find_labels(&self, name: &str) -> (Vec<&Label>, TitleMatch)
Every label whose name matches, exactly or failing that partially.
Sourcepub fn tracks_of_genre(&self, genre_id: Id) -> Vec<Id> ⓘ
pub fn tracks_of_genre(&self, genre_id: Id) -> Vec<Id> ⓘ
Tracks carrying a genre, directly or through their release.
A genre attached to an album is a genre of every track on it: tags put it in either place, and a listener does not think of the difference.
Sourcepub fn releases_of_genre(&self, genre_id: Id) -> Vec<Id> ⓘ
pub fn releases_of_genre(&self, genre_id: Id) -> Vec<Id> ⓘ
Releases holding at least one track of a genre, or carrying it whole.
Sourcepub fn tracks_of_label(&self, label_id: Id) -> Vec<Id> ⓘ
pub fn tracks_of_label(&self, label_id: Id) -> Vec<Id> ⓘ
Tracks published under a label, through the releases carrying it.
Sourcepub fn artists_in_role(&self, role: &str) -> Vec<(Id, usize)>
pub fn artists_in_role(&self, role: &str) -> Vec<(Id, usize)>
Everyone credited in a role, with how many credits each holds.
This is the inverse of the artist page: that one answers “what did this person do”, this one answers “who does this in my library”. The whole point of storing roles rather than a bare artist column is that the question can be asked in both directions.
Sorted by number of credits, then by sort name, so the answer is stable and the most present come first.
Sourcepub fn tracks_of_artist_in_role(&self, artist_id: Id, role: &str) -> Vec<Id> ⓘ
pub fn tracks_of_artist_in_role(&self, artist_id: Id, role: &str) -> Vec<Id> ⓘ
Tracks on which an artist holds one particular role.
The other direction of Catalog::artists_in_role, one person at a
time: not “who produces here” but “what did this person produce”. The
artist page separates performing from writing; this goes one step
finer, down to the single role.
album is credited on the release, not on its tracks, so asking
for it yields every track of the releases they sign — otherwise the
answer would be empty for the one role every album artist holds.
Sourcepub fn roles_of_artist(&self, artist_id: Id) -> Vec<(&str, usize)>
pub fn roles_of_artist(&self, artist_id: Id) -> Vec<(&str, usize)>
The roles one artist holds, with how many credits each.
What makes an empty answer readable: told that nobody is credited a given way, the user has to be able to see what this person is credited as.
Sourcepub fn roles_in_use(&self) -> Vec<&str>
pub fn roles_in_use(&self) -> Vec<&str>
Every role actually used in this catalog, in the order they are shown.
Read from the credits rather than from a fixed list: a role that came from a tag nobody anticipated is still a role, and it must be offerable.
Sourcepub fn tracks_with_comment(&self, text: &str) -> Vec<Id> ⓘ
pub fn tracks_with_comment(&self, text: &str) -> Vec<Id> ⓘ
Tracks whose file carries a comment containing this text.
The comment belongs to the file, not to the track: it is free text a user wrote, and it survives in the raw tags exactly as it was typed. Matching is done on the normalized form, so accents and case are no obstacle — a comment is prose, and prose is typed carelessly.
Sourcepub fn lyrics_of_track(&self, track_id: Id) -> Option<Lyrics>
pub fn lyrics_of_track(&self, track_id: Id) -> Option<Lyrics>
The words of a track: the tag that carries them, or the .lrc beside
the file.
The one place that decides which of the two wins. The tag is
preferred, and costs nothing since raw tags are in the catalog; the
sidecar is opened only when the tag holds none, and only for the tracks
that have one. Three callers ask this question — the track page, the
lyrics: field of the grammar, search --lyrics — and three answers
that could disagree about precedence is two too many.
Sourcepub fn tracks_with_lyric(&self, text: &str) -> Vec<(Id, String)>
pub fn tracks_with_lyric(&self, text: &str) -> Vec<(Id, String)>
The tracks whose words carry this text, with the line that carries it.
The line, not the song: a table cell holding four hundred lines is a table nobody can read, and what somebody searching for “all aboard” wants to see is the line they half-remembered, in the shape they half-remembered it.
Sourcepub fn comment_of_track(&self, track_id: Id) -> Option<&str>
pub fn comment_of_track(&self, track_id: Id) -> Option<&str>
The comment carried by the file behind a track, when there is one.