Files
seanime-docker/seanime-2.9.10/internal/library/scanner/media_container.go
2025-09-20 14:08:38 +01:00

147 lines
4.6 KiB
Go

package scanner
import (
"github.com/rs/zerolog"
"github.com/samber/lo"
lop "github.com/samber/lo/parallel"
"seanime/internal/api/anilist"
"seanime/internal/library/anime"
"seanime/internal/util/comparison"
"strings"
)
type (
MediaContainerOptions struct {
AllMedia []*anilist.CompleteAnime
ScanLogger *ScanLogger
}
MediaContainer struct {
NormalizedMedia []*anime.NormalizedMedia
ScanLogger *ScanLogger
engTitles []*string
romTitles []*string
synonyms []*string
allMedia []*anilist.CompleteAnime
}
)
// NewMediaContainer will create a list of all English titles, Romaji titles, and synonyms from all anilist.BaseAnime (used by Matcher).
//
// The list will include all anilist.BaseAnime and their relations (prequels, sequels, spin-offs, etc...) as NormalizedMedia.
//
// It also provides helper functions to get a NormalizedMedia from a title or synonym (used by FileHydrator).
func NewMediaContainer(opts *MediaContainerOptions) *MediaContainer {
mc := new(MediaContainer)
mc.ScanLogger = opts.ScanLogger
mc.NormalizedMedia = make([]*anime.NormalizedMedia, 0)
normalizedMediaMap := make(map[int]*anime.NormalizedMedia)
for _, m := range opts.AllMedia {
normalizedMediaMap[m.ID] = anime.NewNormalizedMedia(m.ToBaseAnime())
if m.Relations != nil && m.Relations.Edges != nil && len(m.Relations.Edges) > 0 {
for _, edgeM := range m.Relations.Edges {
if edgeM.Node == nil || edgeM.Node.Format == nil || edgeM.RelationType == nil {
continue
}
if *edgeM.Node.Format != anilist.MediaFormatMovie &&
*edgeM.Node.Format != anilist.MediaFormatOva &&
*edgeM.Node.Format != anilist.MediaFormatSpecial &&
*edgeM.Node.Format != anilist.MediaFormatTv {
continue
}
if *edgeM.RelationType != anilist.MediaRelationPrequel &&
*edgeM.RelationType != anilist.MediaRelationSequel &&
*edgeM.RelationType != anilist.MediaRelationSpinOff &&
*edgeM.RelationType != anilist.MediaRelationAlternative &&
*edgeM.RelationType != anilist.MediaRelationParent {
continue
}
// DEVNOTE: Edges fetched from the AniList AnimeCollection query do not contain NextAiringEpisode
// Make sure we don't overwrite the original media in the map that contains NextAiringEpisode
if _, found := normalizedMediaMap[edgeM.Node.ID]; !found {
normalizedMediaMap[edgeM.Node.ID] = anime.NewNormalizedMedia(edgeM.Node)
}
}
}
}
for _, m := range normalizedMediaMap {
mc.NormalizedMedia = append(mc.NormalizedMedia, m)
}
engTitles := lop.Map(mc.NormalizedMedia, func(m *anime.NormalizedMedia, index int) *string {
if m.Title.English != nil {
return m.Title.English
}
return new(string)
})
romTitles := lop.Map(mc.NormalizedMedia, func(m *anime.NormalizedMedia, index int) *string {
if m.Title.Romaji != nil {
return m.Title.Romaji
}
return new(string)
})
_synonymsArr := lop.Map(mc.NormalizedMedia, func(m *anime.NormalizedMedia, index int) []*string {
if m.Synonyms != nil {
return m.Synonyms
}
return make([]*string, 0)
})
synonyms := lo.Flatten(_synonymsArr)
engTitles = lo.Filter(engTitles, func(s *string, i int) bool { return s != nil && len(*s) > 0 })
romTitles = lo.Filter(romTitles, func(s *string, i int) bool { return s != nil && len(*s) > 0 })
synonyms = lo.Filter(synonyms, func(s *string, i int) bool { return comparison.ValueContainsSeason(*s) })
mc.engTitles = engTitles
mc.romTitles = romTitles
mc.synonyms = synonyms
mc.allMedia = opts.AllMedia
if mc.ScanLogger != nil {
mc.ScanLogger.LogMediaContainer(zerolog.InfoLevel).
Any("inputCount", len(opts.AllMedia)).
Any("mediaCount", len(mc.NormalizedMedia)).
Any("titles", len(mc.engTitles)+len(mc.romTitles)+len(mc.synonyms)).
Msg("Created media container")
}
return mc
}
func (mc *MediaContainer) GetMediaFromTitleOrSynonym(title *string) (*anime.NormalizedMedia, bool) {
if title == nil {
return nil, false
}
t := strings.ToLower(*title)
res, found := lo.Find(mc.NormalizedMedia, func(m *anime.NormalizedMedia) bool {
if m.HasEnglishTitle() && t == strings.ToLower(*m.Title.English) {
return true
}
if m.HasRomajiTitle() && t == strings.ToLower(*m.Title.Romaji) {
return true
}
if m.HasSynonyms() {
for _, syn := range m.Synonyms {
if t == strings.ToLower(*syn) {
return true
}
}
}
return false
})
return res, found
}
func (mc *MediaContainer) GetMediaFromId(id int) (*anime.NormalizedMedia, bool) {
res, found := lo.Find(mc.NormalizedMedia, func(m *anime.NormalizedMedia) bool {
if m.ID == id {
return true
}
return false
})
return res, found
}