unarr/internal/library/sync.go
Deivid Soto f0ac905fdb feat(library): detect corrupt/incomplete files during scan
ffprobe already runs on every scanned file; now we capture its stderr and
assess integrity from it. assessIntegrity flags a file "damaged" on the
markers that mean the container/bitstream is unusable: invalid_data,
ebml_corrupt, moov_missing, bitstream_corrupt, plus no_duration (a video
stream with non-positive duration = a truncated/incomplete download).

The verdict rides on MediaInfo.Integrity (IntegrityInfo{Damaged,Reason}),
maps onto LibrarySyncItem.{Integrity,IntegrityReason}, and syncs to the web
so a damaged file can be surfaced at rest instead of only blowing up at
playback.

Bumps the scan cache version (1 → 2) so existing entries re-probe once, and
the scanner re-probes any cached entry that has no integrity verdict yet.
2026-06-02 19:42:00 +02:00

48 lines
1.5 KiB
Go

package library
import "github.com/torrentclaw/unarr/internal/agent"
// BuildSyncItems converts cached library items to sync request items.
// Shared between unarr scan (cmd/scan.go) and auto-scan (cmd/daemon.go).
func BuildSyncItems(cache *LibraryCache) []agent.LibrarySyncItem {
items := make([]agent.LibrarySyncItem, 0, len(cache.Items))
for _, item := range cache.Items {
if item.ScanError != "" {
continue
}
si := agent.LibrarySyncItem{
FilePath: item.FilePath,
FileName: item.FileName,
FileSize: item.FileSize,
Title: item.Title,
Year: item.Year,
ContentType: DeriveContentType(item),
Season: item.Season,
Episode: item.Episode,
}
if item.MediaInfo != nil {
if item.MediaInfo.Video != nil {
si.Resolution = ResolveResolution(item.MediaInfo.Video.Width, item.MediaInfo.Video.Height)
si.VideoCodec = item.MediaInfo.Video.Codec
si.HDR = item.MediaInfo.Video.HDR
si.BitDepth = item.MediaInfo.Video.BitDepth
}
codec, channels := PrimaryAudioTrack(item.MediaInfo.Audio)
si.AudioCodec = codec
si.AudioChannels = channels
si.AudioLanguages = AudioLanguages(item.MediaInfo.Audio)
si.SubtitleLanguages = SubtitleLanguages(item.MediaInfo.Subtitles)
si.AudioTracks = item.MediaInfo.Audio
si.SubtitleTracks = item.MediaInfo.Subtitles
si.VideoInfo = item.MediaInfo.Video
if integ := item.MediaInfo.Integrity; integ != nil && integ.Damaged {
si.Integrity = "damaged"
si.IntegrityReason = integ.Reason
}
}
items = append(items, si)
}
return items
}