fix(scanner): re-extraction is write-after-success — no NULL audio_metadata
The 2026-10-21 wipe: a re-extraction pass NULLed audio_metadata on the m4b audiobooks — every affected book dropped its chapters, track manifest and duration and displayed as 1 second long (the reader also lost its manifest, since single-file books synthesize it from the payload). Guards on both writers of that column: - updateMediaItemWithMetadata (generic full-row pass): when the new extraction carries no audio payload, keep existing.AudioMetadata. Single-file m4b books route here, and a failed/absent extraction leaves AudioMeta nil. - updateAudioBookItem (focused payload write): an empty payload (no tracks, no chapters, no duration) over a populated row is a failed extraction — refuse the write with a loud warning and let the next scan retry. audioPayloadIsEmpty() pins the empty-payload predicate (unit-tested): the column is only ever replaced by fresh data from a real audio extraction, never by absence of one.
This commit is contained in:
1 parent
970beee4b8
commit
80bfc1a77b
3 files changed
+64
-1
No files matched your search
@@ -4049,6 +4049,18 @@ func (s *MediaScanner) updateMediaItemWithMetadata(ctx context.Context, existing
|
||||
if metadata.AlternateInfo != "" {
|
||||
alternateInfoBytes = []byte(metadata.AlternateInfo)
|
||||
}
|
||||
// A re-extraction that produced no audio payload must never wipe
|
||||
// scan-derived audio_metadata (tracks/chapters/duration): the
|
||||
// ev-gated generic pass runs for multi-file books too, where
|
||||
// extractMetadata leaves AudioMeta nil — writing NULL here deleted
|
||||
// every folder book's payload and broke playback (2026-10-21
|
||||
// incident: all multi-file audiobooks showed as 1 second long). The
|
||||
// audio chain (UpdateMediaItemAudioBookPayload) owns this column;
|
||||
// without fresh data from THAT chain, keep what the row has.
|
||||
audioMetadata := marshalAudioMetadata(metadata.AudioMeta)
|
||||
if audioMetadata == nil {
|
||||
audioMetadata = existing.AudioMetadata
|
||||
}
|
||||
params := database.UpdateMediaItemParams{
|
||||
ID: existing.ID,
|
||||
Title: metadata.Title,
|
||||
@@ -4089,7 +4101,7 @@ func (s *MediaScanner) updateMediaItemWithMetadata(ctx context.Context, existing
|
||||
AlternateInfo: alternateInfoBytes,
|
||||
ScanInformation: pgtype.Text{String: metadata.ScanInformation, Valid: metadata.ScanInformation != ""},
|
||||
Summary: pgtype.Text{String: metadata.Summary, Valid: metadata.Summary != ""},
|
||||
AudioMetadata: marshalAudioMetadata(metadata.AudioMeta),
|
||||
AudioMetadata: audioMetadata,
|
||||
}
|
||||
|
||||
// Keep user-customized fields, and keep the override set itself intact.
|
||||
|
||||
Reference in new issue
Block a user