aede_core/model/
composers.rs

1//! Exact local composer credits grouped by edition, without artist-name inference.
2use super::{Catalog, EntityKind, Id};
3use std::collections::{BTreeMap, BTreeSet};
4impl Catalog {
5    /// Index credited composers once per projection/evaluation. Recording and work credits
6    /// reach every local edition containing that recording; duplicate credits stay one identity.
7    /// This reads the local graph only, never unaccepted external claims or audio files.
8    pub fn composers_by_release(&self) -> BTreeMap<Id, BTreeSet<Id>> {
9        // Index only actual composer credits, rather than allocating an inverse map for
10        // every track/recording/work placement in a potentially large library.
11        let mut credits: BTreeMap<(EntityKind, Id), BTreeSet<Id>> = BTreeMap::new();
12        for credit in self
13            .credits
14            .iter()
15            .filter(|credit| credit.role == "composer")
16        {
17            if self.artist(credit.artist_id).is_some() {
18                credits
19                    .entry((credit.entity_kind, credit.entity_id))
20                    .or_default()
21                    .insert(credit.artist_id);
22            }
23        }
24        let mut result = BTreeMap::new();
25        if credits.is_empty() {
26            return result;
27        }
28        for release in &self.releases {
29            let mut composers = credits
30                .get(&(EntityKind::Release, release.id))
31                .cloned()
32                .unwrap_or_default();
33            for id in &release.track_ids {
34                let Some(track) = self.track(*id) else {
35                    continue;
36                };
37                for target in [
38                    (EntityKind::Track, track.id),
39                    (EntityKind::Recording, track.recording_id),
40                ] {
41                    if let Some(artists) = credits.get(&target) {
42                        composers.extend(artists);
43                    }
44                }
45                if let Some(recording) = self.recording(track.recording_id) {
46                    for work in &recording.work_ids {
47                        if let Some(artists) = credits.get(&(EntityKind::Work, *work)) {
48                            composers.extend(artists);
49                        }
50                    }
51                }
52            }
53            if !composers.is_empty() {
54                result.insert(release.id, composers);
55            }
56        }
57        result
58    }
59}