From a672b5a7cadbd1013c02adf0b74b5b8f3ebd633c Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 12 Jul 2023 15:24:07 +0200 Subject: [PATCH] media-file: Import/export sort fields --- crates/core/src/track/title/mod.rs | 21 +++ crates/media-file/src/fmt/mod.rs | 226 +++++++++++++++++++++++----- crates/media-file/src/io/export.rs | 65 +++++--- crates/media-file/src/util/mod.rs | 82 +++++----- crates/media-file/src/util/tests.rs | 69 +++++---- 5 files changed, 336 insertions(+), 127 deletions(-) diff --git a/crates/core/src/track/title/mod.rs b/crates/core/src/track/title/mod.rs index e700d475..1ba0a49c 100644 --- a/crates/core/src/track/title/mod.rs +++ b/crates/core/src/track/title/mod.rs @@ -90,6 +90,7 @@ pub enum TitlesInvalidity { Title(TitleInvalidity), MainTitleMissing, MainTitleAmbiguous, + TitleSortingAmbiguous, } pub const ANY_KIND_FILTER: Option = None; @@ -115,6 +116,12 @@ impl Titles { _ => context.invalidate(TitlesInvalidity::MainTitleAmbiguous), } } + if context.is_valid() { + context = match Self::sorting_titles(titles.to_owned()).count() { + 0 | 1 => context, // ok + _ => context.invalidate(TitlesInvalidity::TitleSortingAmbiguous), + } + } context.into() } @@ -145,6 +152,20 @@ impl Titles { Self::main_titles(titles).next() } + pub fn sorting_titles<'a, 'b, I>(titles: I) -> impl Iterator + where + I: IntoIterator, + { + Self::filter_kind(titles, Kind::Sorting) + } + + pub fn title_sorting<'a, I>(titles: I) -> Option<&'a Title> + where + I: IntoIterator, + { + Self::sorting_titles(titles).next() + } + pub fn set_main_title(titles: &mut Vec, name: impl Into<String>) -> bool { debug_assert!(titles.is_canonical()); let name = name.into(); diff --git a/crates/media-file/src/fmt/mod.rs b/crates/media-file/src/fmt/mod.rs index b50cc9b2..0fd924fe 100644 --- a/crates/media-file/src/fmt/mod.rs +++ b/crates/media-file/src/fmt/mod.rs @@ -17,7 +17,7 @@ use aoide_core::{ prelude::*, tag::{FacetId, FacetKey, FacetedTags, Label, PlainTag, Tags, TagsMap}, track::{ - actor::Role as ActorRole, + actor::{Kind as ActorKind, Role as ActorRole}, album::Kind as AlbumKind, index::Index, metric::MetricsFlags, @@ -50,7 +50,7 @@ use crate::{ }, digest::MediaDigest, format_valid_replay_gain, format_validated_tempo_bpm, ingest_title_from, - key_signature_as_str, push_next_actor_role_name_from, + key_signature_as_str, push_next_actor, tag::TagMappingConfig, TempoBpmFormat, }, @@ -460,6 +460,12 @@ pub(crate) fn import_file_tag_into_track( { track_titles.push(title); } + if let Some(title) = tag + .take_strings(&ItemKey::TrackTitleSortOrder) + .find_map(|name| ingest_title_from(name, TitleKind::Sorting)) + { + track_titles.push(title); + } if let Some(title) = tag .take_strings(&ItemKey::TrackSubtitle) .find_map(|name| ingest_title_from(name, TitleKind::Sub)) @@ -495,40 +501,116 @@ pub(crate) fn import_file_tag_into_track( // Track actors let mut track_actors = Vec::with_capacity(8); for name in tag.take_strings(&ItemKey::TrackArtist) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Artist, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Artist, + ); + } + for name in tag.take_strings(&ItemKey::TrackArtistSortOrder) { + push_next_actor( + &mut track_actors, + name, + ActorKind::Sorting, + ActorRole::Artist, + ); } for name in tag.take_strings(&ItemKey::Arranger) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Arranger, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Arranger, + ); } for name in tag.take_strings(&ItemKey::Composer) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Composer, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Composer, + ); + } + for name in tag.take_strings(&ItemKey::ComposerSortOrder) { + push_next_actor( + &mut track_actors, + name, + ActorKind::Sorting, + ActorRole::Composer, + ); } for name in tag.take_strings(&ItemKey::Conductor) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Conductor, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Conductor, + ); } for name in tag.take_strings(&ItemKey::Director) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Director, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Director, + ); } for name in tag.take_strings(&ItemKey::Engineer) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Engineer, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Engineer, + ); } for name in tag.take_strings(&ItemKey::Lyricist) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Lyricist, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Lyricist, + ); } for name in tag.take_strings(&ItemKey::MixDj) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::MixDj, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::MixDj, + ); } for name in tag.take_strings(&ItemKey::MixEngineer) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::MixEngineer, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::MixEngineer, + ); } for name in tag.take_strings(&ItemKey::Performer) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Performer, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Performer, + ); } for name in tag.take_strings(&ItemKey::Producer) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Producer, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Producer, + ); } for name in tag.take_strings(&ItemKey::Writer) { - push_next_actor_role_name_from(&mut track_actors, ActorRole::Writer, name); + push_next_actor( + &mut track_actors, + name, + Default::default(), + ActorRole::Writer, + ); } let new_track_actors = importer.finish_import_of_actors(TrackScope::Track, track_actors); let old_track_actors = &mut track.actors; @@ -547,6 +629,12 @@ pub(crate) fn import_file_tag_into_track( { album_titles.push(title); } + if let Some(title) = tag + .take_strings(&ItemKey::AlbumTitleSortOrder) + .find_map(|name| ingest_title_from(name, TitleKind::Sorting)) + { + album_titles.push(title); + } let new_album_titles = importer.finish_import_of_titles(TrackScope::Album, album_titles); let old_album_titles = &mut album.titles; if !old_album_titles.is_empty() && *old_album_titles != new_album_titles { @@ -557,7 +645,20 @@ pub(crate) fn import_file_tag_into_track( // Album actors let mut album_actors = Vec::with_capacity(4); for name in tag.take_strings(&ItemKey::AlbumArtist) { - push_next_actor_role_name_from(&mut album_actors, ActorRole::Artist, name); + push_next_actor( + &mut album_actors, + name, + Default::default(), + ActorRole::Artist, + ); + } + for name in tag.take_strings(&ItemKey::AlbumArtistSortOrder) { + push_next_actor( + &mut album_actors, + name, + ActorKind::Sorting, + ActorRole::Artist, + ); } let new_album_actors = importer.finish_import_of_actors(TrackScope::Album, album_actors); let old_album_actors = &mut album.actors; @@ -898,10 +999,14 @@ pub(crate) fn import_serato_tags(track: &mut Track, serato_tags: &triseratops::t fn export_filtered_actor_names( tag: &mut Tag, item_key: ItemKey, - actor_names: FilteredActorNames<'_>, + actor_names: Option<FilteredActorNames<'_>>, ) { + let Some(actor_names) = actor_names else { + tag.remove_key(&item_key); + return; + }; match actor_names { - FilteredActorNames::Summary(name) => { + FilteredActorNames::Summary(name) | FilteredActorNames::Sorting(name) => { tag.insert_text(item_key, name.to_owned()); } FilteredActorNames::Individual(names) => { @@ -1023,11 +1128,19 @@ pub(crate) fn export_track_to_tag( } // Track titles - if let Some(title) = Titles::main_title(track.titles.iter()) { - tag.set_title(title.name.clone()); + if let Some(track_title) = Titles::main_title(track.titles.iter()) { + tag.set_title(track_title.name.clone()); } else { tag.remove_title(); } + if let Some(track_title_sorting) = Titles::title_sorting(track.titles.iter()) { + tag.insert_text( + ItemKey::TrackTitleSortOrder, + track_title_sorting.name.clone(), + ); + } else { + tag.remove_key(&ItemKey::TrackTitleSortOrder); + } tag.remove_key(&ItemKey::TrackSubtitle); for track_subtitle in Titles::filter_kind(track.titles.iter(), TitleKind::Sub).peekable() { let item_val = ItemValue::Text(track_subtitle.name.clone()); @@ -1052,79 +1165,122 @@ pub(crate) fn export_track_to_tag( export_filtered_actor_names( tag, ItemKey::TrackArtist, - FilteredActorNames::new(track.actors.iter(), ActorRole::Artist), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Artist, Default::default()), + ); + export_filtered_actor_names( + tag, + ItemKey::TrackArtistSortOrder, + FilteredActorNames::filter(track.actors.iter(), ActorRole::Artist, ActorKind::Sorting), ); export_filtered_actor_names( tag, ItemKey::Arranger, - FilteredActorNames::new(track.actors.iter(), ActorRole::Arranger), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Arranger, Default::default()), ); export_filtered_actor_names( tag, ItemKey::Composer, - FilteredActorNames::new(track.actors.iter(), ActorRole::Composer), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Composer, Default::default()), + ); + export_filtered_actor_names( + tag, + ItemKey::ComposerSortOrder, + FilteredActorNames::filter(track.actors.iter(), ActorRole::Composer, ActorKind::Sorting), ); export_filtered_actor_names( tag, ItemKey::Conductor, - FilteredActorNames::new(track.actors.iter(), ActorRole::Conductor), + FilteredActorNames::filter( + track.actors.iter(), + ActorRole::Conductor, + Default::default(), + ), ); export_filtered_actor_names( tag, ItemKey::Director, - FilteredActorNames::new(track.actors.iter(), ActorRole::Director), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Director, Default::default()), ); export_filtered_actor_names( tag, ItemKey::Engineer, - FilteredActorNames::new(track.actors.iter(), ActorRole::Engineer), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Engineer, Default::default()), ); export_filtered_actor_names( tag, ItemKey::MixDj, - FilteredActorNames::new(track.actors.iter(), ActorRole::MixDj), + FilteredActorNames::filter(track.actors.iter(), ActorRole::MixDj, Default::default()), ); export_filtered_actor_names( tag, ItemKey::MixEngineer, - FilteredActorNames::new(track.actors.iter(), ActorRole::MixEngineer), + FilteredActorNames::filter( + track.actors.iter(), + ActorRole::MixEngineer, + Default::default(), + ), ); export_filtered_actor_names( tag, ItemKey::Lyricist, - FilteredActorNames::new(track.actors.iter(), ActorRole::Lyricist), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Lyricist, Default::default()), ); export_filtered_actor_names( tag, ItemKey::Performer, - FilteredActorNames::new(track.actors.iter(), ActorRole::Performer), + FilteredActorNames::filter( + track.actors.iter(), + ActorRole::Performer, + Default::default(), + ), ); export_filtered_actor_names( tag, ItemKey::Producer, - FilteredActorNames::new(track.actors.iter(), ActorRole::Producer), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Producer, Default::default()), ); export_filtered_actor_names( tag, ItemKey::Remixer, - FilteredActorNames::new(track.actors.iter(), ActorRole::Remixer), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Remixer, Default::default()), ); export_filtered_actor_names( tag, ItemKey::Writer, - FilteredActorNames::new(track.actors.iter(), ActorRole::Writer), + FilteredActorNames::filter(track.actors.iter(), ActorRole::Writer, Default::default()), ); // Album - if let Some(title) = Titles::main_title(track.album.titles.iter()) { - tag.set_album(title.name.clone()); + if let Some(album_title) = Titles::main_title(track.album.titles.iter()) { + tag.set_album(album_title.name.clone()); } else { tag.remove_album(); } + if let Some(album_title_sorting) = Titles::title_sorting(track.album.titles.iter()) { + tag.insert_text( + ItemKey::AlbumTitleSortOrder, + album_title_sorting.name.clone(), + ); + } else { + tag.remove_key(&ItemKey::AlbumTitleSortOrder); + } export_filtered_actor_names( tag, ItemKey::AlbumArtist, - FilteredActorNames::new(track.album.actors.iter(), ActorRole::Artist), + FilteredActorNames::filter( + track.album.actors.iter(), + ActorRole::Artist, + Default::default(), + ), + ); + export_filtered_actor_names( + tag, + ItemKey::AlbumArtistSortOrder, + FilteredActorNames::filter( + track.album.actors.iter(), + ActorRole::Artist, + ActorKind::Sorting, + ), ); if let Some(kind) = track.album.kind { match kind { diff --git a/crates/media-file/src/io/export.rs b/crates/media-file/src/io/export.rs index 2b67dc8a..44fb7e48 100644 --- a/crates/media-file/src/io/export.rs +++ b/crates/media-file/src/io/export.rs @@ -163,31 +163,56 @@ pub fn export_track_to_file( pub enum FilteredActorNames<'a> { Summary(&'a str), Individual(Vec<&'a str>), // TODO: Replace with impl Iterator<Item = &'a str>! How? + Sorting(&'a str), } impl<'a> FilteredActorNames<'a> { #[must_use] - pub fn new(actors: impl IntoIterator<Item = &'a Actor> + Clone, role: ActorRole) -> Self { - // At most a single summary actor - debug_assert!( - Actors::filter_kind_role(actors.clone(), ActorKind::Summary, role).count() <= 1 - ); - // Either a summary actor or individual actors but not both at the same time - debug_assert!( - Actors::filter_kind_role(actors.clone(), ActorKind::Summary, role) - .next() - .is_none() - || Actors::filter_kind_role(actors.clone(), ActorKind::Individual, role) + pub fn filter( + actors: impl IntoIterator<Item = &'a Actor> + Clone, + role: ActorRole, + kind: ActorKind, + ) -> Option<Self> { + match kind { + ActorKind::Summary | ActorKind::Individual => { + // At most a single summary actor + debug_assert!( + Actors::filter_kind_role(actors.clone(), ActorKind::Summary, role).count() <= 1 + ); + // Either a summary actor or individual actors but not both at the same time + debug_assert!( + Actors::filter_kind_role(actors.clone(), ActorKind::Summary, role) + .next() + .is_none() + || Actors::filter_kind_role(actors.clone(), ActorKind::Individual, role) + .next() + .is_none() + ); + if let Some(summary_actor) = + Actors::filter_kind_role(actors.clone(), ActorKind::Summary, role).next() + { + Some(Self::Summary(summary_actor.name.as_str())) + } else { + let individual_actors = + Actors::filter_kind_role(actors, ActorKind::Individual, role) + .map(|actor| actor.name.as_str()) + .collect::<Vec<_>>(); + if individual_actors.is_empty() { + None + } else { + Some(Self::Individual(individual_actors)) + } + } + } + ActorKind::Sorting => { + // At most a single sorting actor + debug_assert!( + Actors::filter_kind_role(actors.clone(), ActorKind::Sorting, role).count() <= 1 + ); + Actors::filter_kind_role(actors, ActorKind::Sorting, role) .next() - .is_none() - ); - if let Some(summary_actor) = - Actors::filter_kind_role(actors.clone(), ActorKind::Summary, role).next() - { - Self::Summary(summary_actor.name.as_str()) - } else { - let individual_actors = Actors::filter_kind_role(actors, ActorKind::Individual, role); - Self::Individual(individual_actors.map(|actor| actor.name.as_str()).collect()) + .map(|actor| Self::Sorting(actor.name.as_str())) + } } } } diff --git a/crates/media-file/src/util/mod.rs b/crates/media-file/src/util/mod.rs index 97f3e4e2..a9a59bd6 100644 --- a/crates/media-file/src/util/mod.rs +++ b/crates/media-file/src/util/mod.rs @@ -12,7 +12,8 @@ use aoide_core::{ prelude::*, track::{ actor::{ - is_valid_summary_individual_actor_name, Actor, Kind as ActorKind, Role as ActorRole, + is_valid_summary_individual_actor_name, Actor, Actors, Kind as ActorKind, + Role as ActorRole, }, title::{Kind as TitleKind, Title}, }, @@ -100,7 +101,7 @@ pub fn guess_mime_from_file_path(path: impl AsRef<Path>) -> Result<Mime> { /// Otherwise a new chunk of actors is started, starting with the kind /// Summary. fn adjust_summary_actor_kind(actors: &mut [Actor], role: ActorRole, next_name: &str) -> ActorKind { - // Precodinition: Coherent chunk of actors with the given role at the back of the slice + // Precondition: Coherent chunk of actors with the given role at the back of the slice debug_assert_eq!( actors.iter().filter(|actor| actor.role == role).count(), actors @@ -171,28 +172,44 @@ fn adjust_summary_actor_kind(actors: &mut [Actor], role: ActorRole, next_name: & proposed_kind } -pub fn push_next_actor_role_name(actors: &mut Vec<Actor>, role: ActorRole, name: String) -> bool { - if let Some(mut actor) = ingest_actor_from_owned(name, Default::default(), role) { - actor.kind = adjust_summary_actor_kind(actors.as_mut_slice(), role, &actor.name); - actors.push(actor); - true - } else { - false - } -} - -pub fn push_next_actor_role_name_from<'a>( +pub fn push_next_actor<'a>( actors: &mut Vec<Actor>, - role: ActorRole, name: impl Into<Cow<'a, str>>, + kind: ActorKind, + role: ActorRole, ) -> bool { - if let Some(mut actor) = ingest_actor_from(name, Default::default(), role) { - actor.kind = adjust_summary_actor_kind(actors.as_mut_slice(), role, &actor.name); - actors.push(actor); - true - } else { - false - } + let Some(name) = trimmed_non_empty_from(name) else { + return false; + }; + let kind = match kind { + ActorKind::Summary => adjust_summary_actor_kind(actors.as_mut_slice(), role, &name), + ActorKind::Individual => ActorKind::Individual, + ActorKind::Sorting => { + if let Some(actor) = Actors::filter_kind_role(actors.as_slice(), kind, role).next() { + // Only a single sorting actor is supported + if name == actor.name { + // Silently ignore redundant/duplicate sorting actors + return true; + } + // Warn about ambiguous sorting actors + log::warn!( + "Ignoring {role:?} actor \"{name}\" because \"{actor_name}\" is already \ + used for sorting", + actor_name = actor.name + ); + return false; + } + ActorKind::Sorting + } + }; + let actor = Actor { + name: name.into(), + kind, + role, + role_notes: None, + }; + actors.push(actor); + true } pub fn format_parseable_value<T>(value: &mut T) -> String @@ -482,29 +499,6 @@ pub fn ingest_title_from_owned(name: String, kind: TitleKind) -> Option<Title> { }) } -pub fn ingest_actor_from<'a>( - name: impl Into<Cow<'a, str>>, - kind: ActorKind, - role: ActorRole, -) -> Option<Actor> { - trimmed_non_empty_from(name).map(|name| Actor { - name: name.into(), - kind, - role, - role_notes: None, - }) -} - -#[must_use] -pub fn ingest_actor_from_owned(name: String, kind: ActorKind, role: ActorRole) -> Option<Actor> { - trimmed_non_empty_from_owned(name).map(|name| Actor { - name: name.into(), - kind, - role, - role_notes: None, - }) -} - /////////////////////////////////////////////////////////////////////// // Tests /////////////////////////////////////////////////////////////////////// diff --git a/crates/media-file/src/util/tests.rs b/crates/media-file/src/util/tests.rs index 599fae0a..5f66bc81 100644 --- a/crates/media-file/src/util/tests.rs +++ b/crates/media-file/src/util/tests.rs @@ -171,108 +171,121 @@ fn format_fractional_bpm() { fn push_next_actor_role_names() { let mut actors = vec![]; - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Artist1 ft. Artist2"), + Default::default(), ActorRole::Artist, - "Artist1 ft. Artist2".to_owned() )); assert_eq!( Some("Artist1 ft. Artist2"), Actors::summary_actor(actors.iter(), ActorRole::Artist).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Artist1"), + Default::default(), ActorRole::Artist, - "Artist1".to_owned() )); assert_eq!( Some("Artist1 ft. Artist2"), Actors::summary_actor(actors.iter(), ActorRole::Artist).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Artist2"), + Default::default(), ActorRole::Artist, - "Artist2".to_owned() )); assert_eq!( Some("Artist1 ft. Artist2"), Actors::summary_actor(actors.iter(), ActorRole::Artist).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Composer1"), + Default::default(), ActorRole::Composer, - "Composer1".to_owned() )); assert_eq!( Some("Composer1"), Actors::summary_actor(actors.iter(), ActorRole::Composer).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Composer1, Composer2"), + Default::default(), ActorRole::Composer, - "Composer1, Composer2".to_owned() )); assert_eq!( Some("Composer1, Composer2"), Actors::summary_actor(actors.iter(), ActorRole::Composer).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Composer2"), + Default::default(), ActorRole::Composer, - "Composer2".to_owned() )); assert_eq!( Some("Composer1, Composer2"), Actors::summary_actor(actors.iter(), ActorRole::Composer).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Remixer2"), + Default::default(), ActorRole::Remixer, - "Remixer2".to_owned() )); assert_eq!( Some("Remixer2"), Actors::summary_actor(actors.iter(), ActorRole::Remixer).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Remixer1"), + Default::default(), ActorRole::Remixer, - "Remixer1".to_owned() )); assert!(Actors::summary_actor(actors.iter(), ActorRole::Remixer).is_none()); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Remixer1 & Remixer2"), + Default::default(), ActorRole::Remixer, - "Remixer1 & Remixer2".to_owned() )); assert_eq!( Some("Remixer1 & Remixer2"), Actors::summary_actor(actors.iter(), ActorRole::Remixer).map(|actor| actor.name.as_str()) ); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Lyricist1"), + Default::default(), ActorRole::Lyricist, - "Lyricist1".to_owned() )); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, + Cow::Borrowed("Lyricist2"), + Default::default(), ActorRole::Lyricist, - "Lyricist2".to_owned() )); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, - ActorRole::Lyricist, // Duplicate name - "Lyricist1".to_owned() + Cow::Borrowed("Lyricist1"), + Default::default(), + ActorRole::Lyricist, )); - assert!(push_next_actor_role_name( + assert!(push_next_actor( &mut actors, - ActorRole::Lyricist, // Duplicate name (again) - "Lyricist2".to_owned() + Cow::Borrowed("Lyricist2"), + Default::default(), + ActorRole::Lyricist, )); let actors = actors.canonicalize_into(); assert_eq!(