fix(scanner): route the audio-tag genre through the smart-genre pipeline
applyAudioTagFallbacks captured the ffprobe genre atom verbatim into
media_items.genre — no splitting, no shelf resolution, no cap. That raw
capture is what fed 100+-char Audible category blobs into the column
(the 2026-10 Crusader import/update failures).
The ABS sidecar path already has the correct doctrine (ab2a5568): a known
lexicon shelf wins the genre column, everything else is tags-only. Give
the tag fallback the same treatment — splitGenreCandidates +
pickSmartGenre for the column, split candidates appended to tags
(NormalizeTags dedups). Demoted genres like "Audiobook" stop masquerading
as a shelf, and an unknown blob can no longer manufacture an overflowing
value at all.
Fixtures pin the new behavior: the real 111-char Crusader blob stays out
of the column entirely and lands as six separate tags; a shelf-carrying
blob still wins the column; the previously-pinned verbatim "Audiobook"
expectation is updated to the demoted (tags-only) contract.
This commit is contained in:
1 parent
d42c78f58c
commit
885d614092
2 files changed
+68
-4
No files matched your search
@@ -30,6 +30,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bookhoard/internal/utils"
|
||||
)
|
||||
|
||||
// AudioChapter is the uniform chapter entry for every audiobook file model.
|
||||
@@ -394,7 +396,8 @@ func marshalAudioMetadata(payload *AudioMetadataPayload) []byte {
|
||||
// ----:com.apple.iTunes:SUBTITLE → joined into the title
|
||||
// ("Title: Subtitle"), mirroring the ABS sidecar path
|
||||
// ----:com.apple.iTunes:ASIN / ABRIDGED → asin / abridged flag
|
||||
// date → publish date; genre → genre
|
||||
// date → publish date; genre → smart-genre (shelf-or-empty column,
|
||||
// split candidates to tags — see pickSmartGenre)
|
||||
//
|
||||
// Abridged is the one field where an explicit sidecar false must not be
|
||||
// overridden by a tag — the payload pointer marks sidecar presence.
|
||||
@@ -472,7 +475,21 @@ func applyAudioTagFallbacks(metadata *MediaMetadata, report *ffprobeReport) {
|
||||
}
|
||||
}
|
||||
if metadata.Genre == "" {
|
||||
metadata.Genre = report.tag("genre")
|
||||
// Same doctrine as the ABS sidecar path (ab2a5568): the raw file
|
||||
// atom is a comma-joined category blob ("Politicians, Presidents &
|
||||
// Heads of State, United States, ..." — Audible ladders, often past
|
||||
// 100 chars). Shelf-or-empty wins the genre column; every split
|
||||
// candidate lands in tags. No shelf match → the column stays empty
|
||||
// and the blob is tags-only, never a VARCHAR-overflowing INSERT.
|
||||
if genreTag := report.tag("genre"); genreTag != "" {
|
||||
genre, all := pickSmartGenre([]string{genreTag})
|
||||
if genre != "" {
|
||||
metadata.Genre = genre
|
||||
}
|
||||
if len(all) > 0 {
|
||||
metadata.Tags = utils.NormalizeTags(append(metadata.Tags, all...))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user