From 3ec7cc1f22ddaac334d8183a7ae4eeff18d0f90b Mon Sep 17 00:00:00 2001 From: Nick Szittai Date: Sun, 4 Oct 2026 09:39:51 -0400 Subject: [PATCH 1/2] fix: exclude role-labelled contributors from author metadata --- internal/audnexus/metadata.go | 568 +++++++++++++------------- internal/audnexus/metadata_test.go | 44 ++ internal/contributors/authors.go | 26 ++ internal/contributors/authors_test.go | 24 ++ internal/library/sync.go | 19 +- internal/library/sync_author_test.go | 25 ++ 6 files changed, 418 insertions(+), 288 deletions(-) create mode 100644 internal/audnexus/metadata_test.go create mode 100644 internal/contributors/authors.go create mode 100644 internal/contributors/authors_test.go create mode 100644 internal/library/sync_author_test.go diff --git a/internal/audnexus/metadata.go b/internal/audnexus/metadata.go index f7aa99a..9d6ce0d 100644 --- a/internal/audnexus/metadata.go +++ b/internal/audnexus/metadata.go @@ -1,280 +1,288 @@ -package audnexus - -import ( - "context" - "strings" - - "github.com/mstrhakr/audplexus/internal/audio" - "github.com/mstrhakr/audplexus/internal/database" -) - -// EnrichMetadata fetches Audnexus data and merges with the existing book record. -// Audnexus values are preferred when available; Audible data is used as fallback. -func (c *Client) EnrichMetadata(ctx context.Context, book *database.Book) (*EnrichedBook, error) { - enriched := &EnrichedBook{Book: book} - - anBook, err := c.GetBook(ctx, book.ASIN) - if err != nil { - anLog.Warn().Err(err).Str("asin", book.ASIN).Msg("audnexus book metadata unavailable, using audible data only") - } else { - enriched.AudnexusBook = anBook - } - - if book.AuthorASIN != "" { - anAuthor, err := c.GetAuthor(ctx, book.AuthorASIN) - if err != nil { - anLog.Debug().Err(err).Str("author_asin", book.AuthorASIN).Msg("audnexus author metadata unavailable") - } else { - enriched.AudnexusAuthor = anAuthor - } - } - - anChapters, err := c.GetChapters(ctx, book.ASIN) - if err != nil { - anLog.Debug().Err(err).Str("asin", book.ASIN).Msg("audnexus chapter data unavailable") - } else { - enriched.AudnexusChapters = anChapters - } - - return enriched, nil -} - -// EnrichedBook holds merged metadata from Audible (DB) and Audnexus. -type EnrichedBook struct { - Book *database.Book - AudnexusBook *BookResponse - AudnexusAuthor *AuthorResponse - AudnexusChapters *ChapterResponse -} - -// Title returns the best available title. -func (e *EnrichedBook) Title() string { - if e.AudnexusBook != nil && e.AudnexusBook.Title != "" { - return e.AudnexusBook.Title - } - return e.Book.Title -} - -// Subtitle returns the subtitle if available. -func (e *EnrichedBook) Subtitle() string { - if e.AudnexusBook != nil { - return e.AudnexusBook.Subtitle - } - return "" -} - -// Region returns the region code (e.g., "us", "uk"). -func (e *EnrichedBook) Region() string { - if e.AudnexusBook != nil && e.AudnexusBook.Region != "" { - return e.AudnexusBook.Region - } - return "" -} - -// Author returns the best available author name. -func (e *EnrichedBook) Author() string { - if e.AudnexusBook != nil && len(e.AudnexusBook.Authors) > 0 { - names := make([]string, len(e.AudnexusBook.Authors)) - for i, a := range e.AudnexusBook.Authors { - names[i] = a.Name - } - return strings.Join(names, ", ") - } - return e.Book.Author -} - -// Narrator returns the best available narrator name. -func (e *EnrichedBook) Narrator() string { - if e.AudnexusBook != nil && len(e.AudnexusBook.Narrators) > 0 { - names := make([]string, len(e.AudnexusBook.Narrators)) - for i, n := range e.AudnexusBook.Narrators { - names[i] = n.Name - } - return strings.Join(names, ", ") - } - return e.Book.Narrator -} - -// Description returns the best available description. -func (e *EnrichedBook) Description() string { - if e.AudnexusBook != nil && e.AudnexusBook.Summary != "" { - return e.AudnexusBook.Summary - } - if e.AudnexusBook != nil && e.AudnexusBook.Description != "" { - return e.AudnexusBook.Description - } - return e.Book.Description -} - -// Genre returns genres from Audnexus, or empty string. -func (e *EnrichedBook) Genre() string { - if e.AudnexusBook == nil { - return "" - } - var genres []string - for _, g := range e.AudnexusBook.Genres { - if g.Type == "genre" { - genres = append(genres, g.Name) - } - } - return strings.Join(genres, ", ") -} - -// CoverURL returns the best available cover image URL. -func (e *EnrichedBook) CoverURL() string { - if e.AudnexusBook != nil && e.AudnexusBook.Image != "" { - return e.AudnexusBook.Image - } - return e.Book.CoverURL -} - -// Series returns the series name. -func (e *EnrichedBook) Series() string { - if e.AudnexusBook != nil && len(e.AudnexusBook.Series) > 0 { - return e.AudnexusBook.Series[0].Name - } - return e.Book.Series -} - -// SeriesPosition returns the position in series. -func (e *EnrichedBook) SeriesPosition() string { - if e.AudnexusBook != nil && len(e.AudnexusBook.Series) > 0 { - return e.AudnexusBook.Series[0].Position - } - return e.Book.SeriesPosition -} - -// ChapterMarks converts Audnexus chapters to audio.ChapterMark slices. -// Returns nil if no chapter data is available. -func (e *EnrichedBook) ChapterMarks() []audio.ChapterMark { - if e.AudnexusChapters == nil || len(e.AudnexusChapters.Chapters) == 0 { - return nil - } - - marks := make([]audio.ChapterMark, len(e.AudnexusChapters.Chapters)) - for i, ch := range e.AudnexusChapters.Chapters { - endMs := ch.StartOffsetMs + ch.LengthMs - marks[i] = audio.ChapterMark{ - Title: ch.Title, - StartMs: ch.StartOffsetMs, - EndMs: endMs, - } - } - return marks -} - -// Writer returns the full cast and writer information if available. -func (e *EnrichedBook) Writer() string { - // Build a combined cast field from narrators and authors if we have Audnexus data - var parts []string - - if e.AudnexusBook != nil { - // Add narrators/readers - if len(e.AudnexusBook.Narrators) > 0 { - names := make([]string, len(e.AudnexusBook.Narrators)) - for i, n := range e.AudnexusBook.Narrators { - names[i] = n.Name - } - parts = append(parts, "Read by "+strings.Join(names, ", ")) - } - - // Add authors (writers) - if len(e.AudnexusBook.Authors) > 0 { - names := make([]string, len(e.AudnexusBook.Authors)) - for i, a := range e.AudnexusBook.Authors { - names[i] = a.Name - } - parts = append(parts, "Written by "+strings.Join(names, ", ")) - } - } - - return strings.Join(parts, " | ") -} - -// Publisher returns the publisher name. -func (e *EnrichedBook) Publisher() string { - if e.AudnexusBook != nil && e.AudnexusBook.Publisher != "" { - return e.AudnexusBook.Publisher - } - return e.Book.Publisher -} - -// Copyright returns copyright information. -func (e *EnrichedBook) Copyright() string { - if e.AudnexusBook != nil && e.AudnexusBook.ReleaseDate != "" { - // Extract year from release date - if len(e.AudnexusBook.ReleaseDate) >= 4 { - year := e.AudnexusBook.ReleaseDate[:4] - // Format as "© YYYY Publisher" - pub := e.Publisher() - if pub != "" { - return "© " + year + " " + pub - } - return "© " + year - } - } - if !e.Book.ReleaseDate.IsZero() { - year := e.Book.ReleaseDate.Format("2006") - pub := e.Publisher() - if pub != "" { - return "© " + year + " " + pub - } - return "© " + year - } - return "" -} - -// Language returns the language code (e.g., "en", "fr"). -func (e *EnrichedBook) Language() string { - if e.AudnexusBook != nil && e.AudnexusBook.Language != "" { - return e.AudnexusBook.Language - } - return e.Book.Language -} - -// ToAudioMetadata builds an audio.Metadata struct from the merged data. -func (e *EnrichedBook) ToAudioMetadata() audio.Metadata { - // Album must be the book title so Plex treats each audiobook as its own - // album. When album was set to the series name, Plex grouped every book - // in a series as tracks under one album. - album := e.Title() - - year := "" - if e.AudnexusBook != nil && e.AudnexusBook.ReleaseDate != "" { - if len(e.AudnexusBook.ReleaseDate) >= 4 { - year = e.AudnexusBook.ReleaseDate[:4] - } - } else if !e.Book.ReleaseDate.IsZero() { - year = e.Book.ReleaseDate.Format("2006") - } - - asin := "" - if e.Book != nil { - asin = e.Book.ASIN - } - - return audio.Metadata{ - Title: e.Title(), - Author: e.Author(), - Narrator: e.Narrator(), - Writer: e.Writer(), - Publisher: e.Publisher(), - Copyright: e.Copyright(), - Language: e.Language(), - Album: album, - AlbumArtist: e.Author(), - Genre: e.Genre(), - Year: year, - Comment: e.Description(), - Track: e.SeriesPosition(), - // Always populated. Whether they're emitted into the m4b is - // gated by audio.Metadata.Profile in buildMetadataArgs. - Series: e.Series(), - SeriesPart: e.SeriesPosition(), - ASIN: asin, - // Caller sets Profile after ToAudioMetadata returns. Defaults to - // TagProfileBasic (zero value), which preserves today's behavior - // for any caller that doesn't opt into a profile. - } -} - +package audnexus + +import ( + "context" + "strings" + + "github.com/mstrhakr/audplexus/internal/audio" + "github.com/mstrhakr/audplexus/internal/contributors" + "github.com/mstrhakr/audplexus/internal/database" +) + +// EnrichMetadata fetches Audnexus data and merges with the existing book record. +// Audnexus values are preferred when available; Audible data is used as fallback. +func (c *Client) EnrichMetadata(ctx context.Context, book *database.Book) (*EnrichedBook, error) { + enriched := &EnrichedBook{Book: book} + + anBook, err := c.GetBook(ctx, book.ASIN) + if err != nil { + anLog.Warn().Err(err).Str("asin", book.ASIN).Msg("audnexus book metadata unavailable, using audible data only") + } else { + enriched.AudnexusBook = anBook + } + + if book.AuthorASIN != "" { + anAuthor, err := c.GetAuthor(ctx, book.AuthorASIN) + if err != nil { + anLog.Debug().Err(err).Str("author_asin", book.AuthorASIN).Msg("audnexus author metadata unavailable") + } else { + enriched.AudnexusAuthor = anAuthor + } + } + + anChapters, err := c.GetChapters(ctx, book.ASIN) + if err != nil { + anLog.Debug().Err(err).Str("asin", book.ASIN).Msg("audnexus chapter data unavailable") + } else { + enriched.AudnexusChapters = anChapters + } + + return enriched, nil +} + +// EnrichedBook holds merged metadata from Audible (DB) and Audnexus. +type EnrichedBook struct { + Book *database.Book + AudnexusBook *BookResponse + AudnexusAuthor *AuthorResponse + AudnexusChapters *ChapterResponse +} + +// Title returns the best available title. +func (e *EnrichedBook) Title() string { + if e.AudnexusBook != nil && e.AudnexusBook.Title != "" { + return e.AudnexusBook.Title + } + return e.Book.Title +} + +// Subtitle returns the subtitle if available. +func (e *EnrichedBook) Subtitle() string { + if e.AudnexusBook != nil { + return e.AudnexusBook.Subtitle + } + return "" +} + +// Region returns the region code (e.g., "us", "uk"). +func (e *EnrichedBook) Region() string { + if e.AudnexusBook != nil && e.AudnexusBook.Region != "" { + return e.AudnexusBook.Region + } + return "" +} + +// Author returns the best available author name. +func (e *EnrichedBook) Author() string { + if e.AudnexusBook != nil && len(e.AudnexusBook.Authors) > 0 { + names := make([]string, 0, len(e.AudnexusBook.Authors)) + for _, a := range e.AudnexusBook.Authors { + if !contributors.IsNonAuthorRole(a.Name) { + names = append(names, a.Name) + } + } + if len(names) > 0 { + return strings.Join(names, ", ") + } + } + return e.Book.Author +} + +// Narrator returns the best available narrator name. +func (e *EnrichedBook) Narrator() string { + if e.AudnexusBook != nil && len(e.AudnexusBook.Narrators) > 0 { + names := make([]string, len(e.AudnexusBook.Narrators)) + for i, n := range e.AudnexusBook.Narrators { + names[i] = n.Name + } + return strings.Join(names, ", ") + } + return e.Book.Narrator +} + +// Description returns the best available description. +func (e *EnrichedBook) Description() string { + if e.AudnexusBook != nil && e.AudnexusBook.Summary != "" { + return e.AudnexusBook.Summary + } + if e.AudnexusBook != nil && e.AudnexusBook.Description != "" { + return e.AudnexusBook.Description + } + return e.Book.Description +} + +// Genre returns genres from Audnexus, or empty string. +func (e *EnrichedBook) Genre() string { + if e.AudnexusBook == nil { + return "" + } + var genres []string + for _, g := range e.AudnexusBook.Genres { + if g.Type == "genre" { + genres = append(genres, g.Name) + } + } + return strings.Join(genres, ", ") +} + +// CoverURL returns the best available cover image URL. +func (e *EnrichedBook) CoverURL() string { + if e.AudnexusBook != nil && e.AudnexusBook.Image != "" { + return e.AudnexusBook.Image + } + return e.Book.CoverURL +} + +// Series returns the series name. +func (e *EnrichedBook) Series() string { + if e.AudnexusBook != nil && len(e.AudnexusBook.Series) > 0 { + return e.AudnexusBook.Series[0].Name + } + return e.Book.Series +} + +// SeriesPosition returns the position in series. +func (e *EnrichedBook) SeriesPosition() string { + if e.AudnexusBook != nil && len(e.AudnexusBook.Series) > 0 { + return e.AudnexusBook.Series[0].Position + } + return e.Book.SeriesPosition +} + +// ChapterMarks converts Audnexus chapters to audio.ChapterMark slices. +// Returns nil if no chapter data is available. +func (e *EnrichedBook) ChapterMarks() []audio.ChapterMark { + if e.AudnexusChapters == nil || len(e.AudnexusChapters.Chapters) == 0 { + return nil + } + + marks := make([]audio.ChapterMark, len(e.AudnexusChapters.Chapters)) + for i, ch := range e.AudnexusChapters.Chapters { + endMs := ch.StartOffsetMs + ch.LengthMs + marks[i] = audio.ChapterMark{ + Title: ch.Title, + StartMs: ch.StartOffsetMs, + EndMs: endMs, + } + } + return marks +} + +// Writer returns the full cast and writer information if available. +func (e *EnrichedBook) Writer() string { + // Build a combined cast field from narrators and authors if we have Audnexus data + var parts []string + + if e.AudnexusBook != nil { + // Add narrators/readers + if len(e.AudnexusBook.Narrators) > 0 { + names := make([]string, len(e.AudnexusBook.Narrators)) + for i, n := range e.AudnexusBook.Narrators { + names[i] = n.Name + } + parts = append(parts, "Read by "+strings.Join(names, ", ")) + } + + // Add authors (writers) + if len(e.AudnexusBook.Authors) > 0 { + names := make([]string, 0, len(e.AudnexusBook.Authors)) + for _, a := range e.AudnexusBook.Authors { + if !contributors.IsNonAuthorRole(a.Name) { + names = append(names, a.Name) + } + } + if len(names) > 0 { + parts = append(parts, "Written by "+strings.Join(names, ", ")) + } + } + } + + return strings.Join(parts, " | ") +} + +// Publisher returns the publisher name. +func (e *EnrichedBook) Publisher() string { + if e.AudnexusBook != nil && e.AudnexusBook.Publisher != "" { + return e.AudnexusBook.Publisher + } + return e.Book.Publisher +} + +// Copyright returns copyright information. +func (e *EnrichedBook) Copyright() string { + if e.AudnexusBook != nil && e.AudnexusBook.ReleaseDate != "" { + // Extract year from release date + if len(e.AudnexusBook.ReleaseDate) >= 4 { + year := e.AudnexusBook.ReleaseDate[:4] + // Format as "© YYYY Publisher" + pub := e.Publisher() + if pub != "" { + return "© " + year + " " + pub + } + return "© " + year + } + } + if !e.Book.ReleaseDate.IsZero() { + year := e.Book.ReleaseDate.Format("2006") + pub := e.Publisher() + if pub != "" { + return "© " + year + " " + pub + } + return "© " + year + } + return "" +} + +// Language returns the language code (e.g., "en", "fr"). +func (e *EnrichedBook) Language() string { + if e.AudnexusBook != nil && e.AudnexusBook.Language != "" { + return e.AudnexusBook.Language + } + return e.Book.Language +} + +// ToAudioMetadata builds an audio.Metadata struct from the merged data. +func (e *EnrichedBook) ToAudioMetadata() audio.Metadata { + // Album must be the book title so Plex treats each audiobook as its own + // album. When album was set to the series name, Plex grouped every book + // in a series as tracks under one album. + album := e.Title() + + year := "" + if e.AudnexusBook != nil && e.AudnexusBook.ReleaseDate != "" { + if len(e.AudnexusBook.ReleaseDate) >= 4 { + year = e.AudnexusBook.ReleaseDate[:4] + } + } else if !e.Book.ReleaseDate.IsZero() { + year = e.Book.ReleaseDate.Format("2006") + } + + asin := "" + if e.Book != nil { + asin = e.Book.ASIN + } + + return audio.Metadata{ + Title: e.Title(), + Author: e.Author(), + Narrator: e.Narrator(), + Writer: e.Writer(), + Publisher: e.Publisher(), + Copyright: e.Copyright(), + Language: e.Language(), + Album: album, + AlbumArtist: e.Author(), + Genre: e.Genre(), + Year: year, + Comment: e.Description(), + Track: e.SeriesPosition(), + // Always populated. Whether they're emitted into the m4b is + // gated by audio.Metadata.Profile in buildMetadataArgs. + Series: e.Series(), + SeriesPart: e.SeriesPosition(), + ASIN: asin, + // Caller sets Profile after ToAudioMetadata returns. Defaults to + // TagProfileBasic (zero value), which preserves today's behavior + // for any caller that doesn't opt into a profile. + } +} diff --git a/internal/audnexus/metadata_test.go b/internal/audnexus/metadata_test.go new file mode 100644 index 0000000..5964ddc --- /dev/null +++ b/internal/audnexus/metadata_test.go @@ -0,0 +1,44 @@ +package audnexus + +import ( + "testing" + + "github.com/mstrhakr/audplexus/internal/database" +) + +func TestEnrichedBookAuthorFiltersRoleContributors(t *testing.T) { + enriched := &EnrichedBook{ + Book: &database.Book{Author: "Audible fallback"}, + AudnexusBook: &BookResponse{ + Authors: []Person{ + {Name: "J.R.R. Tolkien"}, + {Name: "Christopher Tolkien"}, + {Name: "Karl A. Klewer - translator"}, + }, + }, + } + + const want = "J.R.R. Tolkien, Christopher Tolkien" + if got := enriched.Author(); got != want { + t.Errorf("Author() = %q, want %q", got, want) + } + if got := enriched.Writer(); got != "Written by "+want { + t.Errorf("Writer() = %q, want %q", got, "Written by "+want) + } + if got := enriched.ToAudioMetadata().Author; got != want { + t.Errorf("ToAudioMetadata().Author = %q, want %q", got, want) + } +} + +func TestEnrichedBookAuthorFallsBackWhenOnlyRoleContributors(t *testing.T) { + enriched := &EnrichedBook{ + Book: &database.Book{Author: "J.R.R. Tolkien"}, + AudnexusBook: &BookResponse{ + Authors: []Person{{Name: "Karl A. Klewer - translator"}}, + }, + } + + if got, want := enriched.Author(), "J.R.R. Tolkien"; got != want { + t.Errorf("Author() = %q, want fallback %q", got, want) + } +} diff --git a/internal/contributors/authors.go b/internal/contributors/authors.go new file mode 100644 index 0000000..9221b34 --- /dev/null +++ b/internal/contributors/authors.go @@ -0,0 +1,26 @@ +// Package contributors contains shared helpers for contributor metadata. +package contributors + +import ( + "strings" +) + +// IsNonAuthorRole reports whether a contributor name ends with a known role +// annotation rather than naming an author. Audible and Audnexus encode some +// contributor roles in names, for example "Karl A. Klewer - translator". +func IsNonAuthorRole(name string) bool { + separator := strings.LastIndex(name, " - ") + if separator < 0 { + return false + } + + role := strings.ToLower(strings.TrimSpace(name[separator+3:])) + switch role { + case "translator", "editor", "illustrator", "introduction", "foreword", + "afterword", "preface", "abridger", "adapter", "adaptor", "compiler", + "contributor", "annotator": + return true + default: + return false + } +} diff --git a/internal/contributors/authors_test.go b/internal/contributors/authors_test.go new file mode 100644 index 0000000..2ca5b26 --- /dev/null +++ b/internal/contributors/authors_test.go @@ -0,0 +1,24 @@ +package contributors + +import "testing" + +func TestIsNonAuthorRole(t *testing.T) { + tests := []struct { + name string + want bool + }{ + {name: "Karl A. Klewer - translator", want: true}, + {name: "A. Editor - EDITOR", want: true}, + {name: "J.R.R. Tolkien", want: false}, + {name: "Tolkien - The Hobbit", want: false}, + {name: "Jean-Paul Sartre", want: false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := IsNonAuthorRole(tc.name); got != tc.want { + t.Errorf("IsNonAuthorRole(%q) = %t, want %t", tc.name, got, tc.want) + } + }) + } +} diff --git a/internal/library/sync.go b/internal/library/sync.go index 63309bc..03952ae 100644 --- a/internal/library/sync.go +++ b/internal/library/sync.go @@ -12,6 +12,7 @@ import ( "github.com/mstrhakr/audplexus/internal/audio" "github.com/mstrhakr/audplexus/internal/audnexus" + "github.com/mstrhakr/audplexus/internal/contributors" "github.com/mstrhakr/audplexus/internal/database" "github.com/mstrhakr/audplexus/internal/errs" "github.com/mstrhakr/audplexus/internal/logging" @@ -1693,20 +1694,22 @@ func parseAudibleDate(raw string) time.Time { } func convertBook(b audible.Book) database.Book { - authors := make([]string, len(b.Authors)) - for i, a := range b.Authors { - authors[i] = cleanText(a.Name) + authors := make([]string, 0, len(b.Authors)) + var authorASIN string + for _, a := range b.Authors { + if contributors.IsNonAuthorRole(a.Name) { + continue + } + authors = append(authors, cleanText(a.Name)) + if authorASIN == "" { + authorASIN = a.ASIN + } } narrators := make([]string, len(b.Narrators)) for i, n := range b.Narrators { narrators[i] = cleanText(n.Name) } - var authorASIN string - if len(b.Authors) > 0 { - authorASIN = b.Authors[0].ASIN - } - var series, seriesPos string if len(b.Series) > 0 { series = cleanText(b.Series[0].Title) diff --git a/internal/library/sync_author_test.go b/internal/library/sync_author_test.go new file mode 100644 index 0000000..447234e --- /dev/null +++ b/internal/library/sync_author_test.go @@ -0,0 +1,25 @@ +package library + +import ( + "testing" + + "github.com/mstrhakr/go-audible" +) + +func TestConvertBookFiltersRoleContributorsAndKeepsCoauthors(t *testing.T) { + book := convertBook(audible.Book{ + ASIN: "B012345678", + Authors: []audible.Contributor{ + {ASIN: "TRANSLATOR", Name: "Karl A. Klewer - translator"}, + {ASIN: "TOLKIEN", Name: "J.R.R. Tolkien"}, + {ASIN: "CHRISTOPHER", Name: "Christopher Tolkien"}, + }, + }) + + if got, want := book.Author, "J.R.R. Tolkien, Christopher Tolkien"; got != want { + t.Errorf("Author = %q, want %q", got, want) + } + if got, want := book.AuthorASIN, "TOLKIEN"; got != want { + t.Errorf("AuthorASIN = %q, want first actual author ASIN %q", got, want) + } +} From 4c08f69b1edcb34a19c610586797859dabd38e7c Mon Sep 17 00:00:00 2001 From: Nick Szittai Date: Sun, 4 Oct 2026 10:22:47 -0400 Subject: [PATCH 2/2] fix: normalize contributor names before role filtering --- internal/library/sync.go | 5 +++-- internal/library/sync_author_test.go | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/library/sync.go b/internal/library/sync.go index 03952ae..8467148 100644 --- a/internal/library/sync.go +++ b/internal/library/sync.go @@ -1697,10 +1697,11 @@ func convertBook(b audible.Book) database.Book { authors := make([]string, 0, len(b.Authors)) var authorASIN string for _, a := range b.Authors { - if contributors.IsNonAuthorRole(a.Name) { + name := cleanText(a.Name) + if contributors.IsNonAuthorRole(name) { continue } - authors = append(authors, cleanText(a.Name)) + authors = append(authors, name) if authorASIN == "" { authorASIN = a.ASIN } diff --git a/internal/library/sync_author_test.go b/internal/library/sync_author_test.go index 447234e..cae54cd 100644 --- a/internal/library/sync_author_test.go +++ b/internal/library/sync_author_test.go @@ -11,6 +11,7 @@ func TestConvertBookFiltersRoleContributorsAndKeepsCoauthors(t *testing.T) { ASIN: "B012345678", Authors: []audible.Contributor{ {ASIN: "TRANSLATOR", Name: "Karl A. Klewer - translator"}, + {ASIN: "ENCODED_TRANSLATOR", Name: "Another Name - translator "}, {ASIN: "TOLKIEN", Name: "J.R.R. Tolkien"}, {ASIN: "CHRISTOPHER", Name: "Christopher Tolkien"}, },