node build fixed
This commit is contained in:
226
seanime-2.9.10/internal/library/scanner/media_tree_analysis.go
Normal file
226
seanime-2.9.10/internal/library/scanner/media_tree_analysis.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/api/metadata"
|
||||
"seanime/internal/util/limiter"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/sourcegraph/conc/pool"
|
||||
)
|
||||
|
||||
type (
|
||||
MediaTreeAnalysisOptions struct {
|
||||
tree *anilist.CompleteAnimeRelationTree
|
||||
metadataProvider metadata.Provider
|
||||
rateLimiter *limiter.Limiter
|
||||
}
|
||||
|
||||
MediaTreeAnalysis struct {
|
||||
branches []*MediaTreeAnalysisBranch
|
||||
}
|
||||
|
||||
MediaTreeAnalysisBranch struct {
|
||||
media *anilist.CompleteAnime
|
||||
animeMetadata *metadata.AnimeMetadata
|
||||
// The second absolute episode number of the first episode
|
||||
// Sometimes, the metadata provider may have a 'true' absolute episode number and a 'part' absolute episode number
|
||||
// 'part' absolute episode numbers might be used for "Part 2s" of a season
|
||||
minPartAbsoluteEpisodeNumber int
|
||||
maxPartAbsoluteEpisodeNumber int
|
||||
minAbsoluteEpisode int
|
||||
maxAbsoluteEpisode int
|
||||
totalEpisodeCount int
|
||||
noAbsoluteEpisodesFound bool
|
||||
}
|
||||
)
|
||||
|
||||
// NewMediaTreeAnalysis will analyze the media tree and create and store a MediaTreeAnalysisBranch for each media in the tree.
|
||||
// Each MediaTreeAnalysisBranch will contain the min and max absolute episode number for the media.
|
||||
// The min and max absolute episode numbers are used to get the relative episode number from an absolute episode number.
|
||||
func NewMediaTreeAnalysis(opts *MediaTreeAnalysisOptions) (*MediaTreeAnalysis, error) {
|
||||
|
||||
relations := make([]*anilist.CompleteAnime, 0)
|
||||
opts.tree.Range(func(key int, value *anilist.CompleteAnime) bool {
|
||||
relations = append(relations, value)
|
||||
return true
|
||||
})
|
||||
|
||||
// Get Animap data for all related media in the tree
|
||||
// With each Animap media, get the min and max absolute episode number
|
||||
// Create new MediaTreeAnalysisBranch for each Animap media
|
||||
p := pool.NewWithResults[*MediaTreeAnalysisBranch]().WithErrors()
|
||||
for _, rel := range relations {
|
||||
p.Go(func() (*MediaTreeAnalysisBranch, error) {
|
||||
opts.rateLimiter.Wait()
|
||||
|
||||
animeMetadata, err := opts.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, rel.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the first episode
|
||||
firstEp, ok := animeMetadata.Episodes["1"]
|
||||
if !ok {
|
||||
return nil, errors.New("no first episode")
|
||||
}
|
||||
|
||||
// discrepancy: "seasonNumber":1,"episodeNumber":12,"absoluteEpisodeNumber":13,
|
||||
// this happens when the media has a separate entry but is technically the same season
|
||||
// when we detect this, we should use the "episodeNumber" as the absoluteEpisodeNumber
|
||||
// this is a hacky fix, but it works for the cases I've seen so far
|
||||
usePartEpisodeNumber := firstEp.EpisodeNumber > 1 && firstEp.AbsoluteEpisodeNumber-firstEp.EpisodeNumber > 1
|
||||
partAbsoluteEpisodeNumber := 0
|
||||
maxPartAbsoluteEpisodeNumber := 0
|
||||
if usePartEpisodeNumber {
|
||||
partAbsoluteEpisodeNumber = firstEp.EpisodeNumber
|
||||
maxPartAbsoluteEpisodeNumber = partAbsoluteEpisodeNumber + animeMetadata.GetMainEpisodeCount() - 1
|
||||
}
|
||||
|
||||
// If the first episode exists and has a valid absolute episode number, create a new MediaTreeAnalysisBranch
|
||||
if animeMetadata.Episodes != nil {
|
||||
return &MediaTreeAnalysisBranch{
|
||||
media: rel,
|
||||
animeMetadata: animeMetadata,
|
||||
minPartAbsoluteEpisodeNumber: partAbsoluteEpisodeNumber,
|
||||
maxPartAbsoluteEpisodeNumber: maxPartAbsoluteEpisodeNumber,
|
||||
minAbsoluteEpisode: firstEp.AbsoluteEpisodeNumber,
|
||||
// The max absolute episode number is the first episode's absolute episode number plus the total episode count minus 1
|
||||
// We subtract 1 because the first episode's absolute episode number is already included in the total episode count
|
||||
// e.g, if the first episode's absolute episode number is 13 and the total episode count is 12, the max absolute episode number is 24
|
||||
maxAbsoluteEpisode: firstEp.AbsoluteEpisodeNumber + (animeMetadata.GetMainEpisodeCount() - 1),
|
||||
totalEpisodeCount: animeMetadata.GetMainEpisodeCount(),
|
||||
noAbsoluteEpisodesFound: firstEp.AbsoluteEpisodeNumber == 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("could not analyze media tree branch")
|
||||
|
||||
})
|
||||
}
|
||||
branches, _ := p.Wait()
|
||||
|
||||
if branches == nil || len(branches) == 0 {
|
||||
return nil, errors.New("no branches found")
|
||||
}
|
||||
|
||||
return &MediaTreeAnalysis{branches: branches}, nil
|
||||
|
||||
}
|
||||
|
||||
// getRelativeEpisodeNumber uses the MediaTreeAnalysis to get the relative episode number for an absolute episode number
|
||||
func (o *MediaTreeAnalysis) getRelativeEpisodeNumber(abs int) (relativeEp int, mediaId int, ok bool) {
|
||||
|
||||
isPartAbsolute := false
|
||||
|
||||
// Find the MediaTreeAnalysisBranch that contains the absolute episode number
|
||||
branch, ok := lo.Find(o.branches, func(n *MediaTreeAnalysisBranch) bool {
|
||||
// First check if the partAbsoluteEpisodeNumber is set
|
||||
if n.minPartAbsoluteEpisodeNumber > 0 && n.maxPartAbsoluteEpisodeNumber > 0 {
|
||||
// If it is, check if the absolute episode number given is the same as the partAbsoluteEpisodeNumber
|
||||
// If it is, return true
|
||||
if n.minPartAbsoluteEpisodeNumber <= abs && n.maxPartAbsoluteEpisodeNumber >= abs {
|
||||
isPartAbsolute = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Else, check if the absolute episode number given is within the min and max absolute episode numbers of the branch
|
||||
if n.minAbsoluteEpisode <= abs && n.maxAbsoluteEpisode >= abs {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if !ok {
|
||||
// Sort branches manually
|
||||
type branchByFirstEpDate struct {
|
||||
branch *MediaTreeAnalysisBranch
|
||||
firstEpDate time.Time
|
||||
minAbsoluteEpisode int
|
||||
maxAbsoluteEpisode int
|
||||
}
|
||||
branches := make([]*branchByFirstEpDate, 0)
|
||||
for _, b := range o.branches {
|
||||
// Get the first episode date
|
||||
firstEp, ok := b.animeMetadata.Episodes["1"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// parse date
|
||||
t, err := time.Parse(time.DateOnly, firstEp.AirDate)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
branches = append(branches, &branchByFirstEpDate{
|
||||
branch: b,
|
||||
firstEpDate: t,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort branches by first episode date
|
||||
// If the first episode date is not available, the branch will be placed at the end
|
||||
sort.Slice(branches, func(i, j int) bool {
|
||||
return branches[i].firstEpDate.Before(branches[j].firstEpDate)
|
||||
})
|
||||
|
||||
// Hydrate branches with min and max absolute episode numbers
|
||||
visited := make(map[int]*branchByFirstEpDate)
|
||||
for idx, b := range branches {
|
||||
visited[idx] = b
|
||||
if v, ok := visited[idx-1]; ok {
|
||||
b.minAbsoluteEpisode = v.maxAbsoluteEpisode + 1
|
||||
b.maxAbsoluteEpisode = b.minAbsoluteEpisode + b.branch.totalEpisodeCount - 1
|
||||
continue
|
||||
}
|
||||
b.minAbsoluteEpisode = 1
|
||||
b.maxAbsoluteEpisode = b.minAbsoluteEpisode + b.branch.totalEpisodeCount - 1
|
||||
}
|
||||
|
||||
for _, b := range branches {
|
||||
if b.minAbsoluteEpisode <= abs && b.maxAbsoluteEpisode >= abs {
|
||||
b.branch.minAbsoluteEpisode = b.minAbsoluteEpisode
|
||||
b.branch.maxAbsoluteEpisode = b.maxAbsoluteEpisode
|
||||
branch = b.branch
|
||||
relativeEp = abs - (branch.minAbsoluteEpisode - 1)
|
||||
mediaId = branch.media.ID
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
if isPartAbsolute {
|
||||
// Let's say the media has 12 episodes and the file is "episode 13"
|
||||
// If the [partAbsoluteEpisodeNumber] is 13, then the [relativeEp] will be 1, we can safely ignore the [absoluteEpisodeNumber]
|
||||
// e.g. 13 - (13-1) = 1
|
||||
relativeEp = abs - (branch.minPartAbsoluteEpisodeNumber - 1)
|
||||
} else {
|
||||
// Let's say the media has 12 episodes and the file is "episode 38"
|
||||
// The [minAbsoluteEpisode] will be 38 and the [relativeEp] will be 1
|
||||
// e.g. 38 - (38-1) = 1
|
||||
relativeEp = abs - (branch.minAbsoluteEpisode - 1)
|
||||
}
|
||||
|
||||
mediaId = branch.media.ID
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *MediaTreeAnalysis) printBranches() (str string) {
|
||||
str = "["
|
||||
for _, branch := range o.branches {
|
||||
str += fmt.Sprintf("media: '%s', minAbsoluteEpisode: %d, maxAbsoluteEpisode: %d, totalEpisodeCount: %d; ", branch.media.GetTitleSafe(), branch.minAbsoluteEpisode, branch.maxAbsoluteEpisode, branch.totalEpisodeCount)
|
||||
}
|
||||
if len(o.branches) > 0 {
|
||||
str = str[:len(str)-2]
|
||||
}
|
||||
str += "]"
|
||||
return str
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user