mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 04:26:38 +08:00
Keep TV episodes as mixed-series representatives
This commit is contained in:
@@ -301,16 +301,8 @@ func groupMediaSeriesCards(items []model.Media) []SeriesCard {
|
|||||||
if betterSeriesLinkMedia(item, card.LinkMedia) {
|
if betterSeriesLinkMedia(item, card.LinkMedia) {
|
||||||
card.LinkMedia = item
|
card.LinkMedia = item
|
||||||
}
|
}
|
||||||
currentArtwork := seriesArtworkScore(item)
|
if betterSeriesRepresentative(item, card.Rep) {
|
||||||
representativeArtwork := seriesArtworkScore(card.Rep)
|
|
||||||
if currentArtwork > representativeArtwork {
|
|
||||||
card.Rep = item
|
card.Rep = item
|
||||||
} else if currentArtwork == representativeArtwork {
|
|
||||||
cur := item.SeasonNum*10000 + item.EpisodeNum
|
|
||||||
rep := card.Rep.SeasonNum*10000 + card.Rep.EpisodeNum
|
|
||||||
if cur > 0 && (rep == 0 || cur < rep) {
|
|
||||||
card.Rep = item
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -335,6 +327,27 @@ func groupMediaSeriesCards(items []model.Media) []SeriesCard {
|
|||||||
return cards
|
return cards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func betterSeriesRepresentative(candidate, current model.Media) bool {
|
||||||
|
// A theatrical feature can have local poster.jpg/background.jpg files and
|
||||||
|
// therefore a higher artwork score than its TV episodes. Keep the TV row as
|
||||||
|
// the visible identity of a mixed series card so a movie cannot hijack the
|
||||||
|
// series title, overview, and artwork.
|
||||||
|
candidateTheatrical := mediaLooksLikeTheatricalFeature(&candidate)
|
||||||
|
currentTheatrical := mediaLooksLikeTheatricalFeature(¤t)
|
||||||
|
if candidateTheatrical != currentTheatrical {
|
||||||
|
return !candidateTheatrical
|
||||||
|
}
|
||||||
|
|
||||||
|
currentArtwork := seriesArtworkScore(candidate)
|
||||||
|
representativeArtwork := seriesArtworkScore(current)
|
||||||
|
if currentArtwork != representativeArtwork {
|
||||||
|
return currentArtwork > representativeArtwork
|
||||||
|
}
|
||||||
|
cur := candidate.SeasonNum*10000 + candidate.EpisodeNum
|
||||||
|
rep := current.SeasonNum*10000 + current.EpisodeNum
|
||||||
|
return cur > 0 && (rep == 0 || cur < rep)
|
||||||
|
}
|
||||||
|
|
||||||
func seriesMediaTime(media model.Media) time.Time {
|
func seriesMediaTime(media model.Media) time.Time {
|
||||||
if releaseDate := strings.TrimSpace(media.ReleaseDate); releaseDate != "" {
|
if releaseDate := strings.TrimSpace(media.ReleaseDate); releaseDate != "" {
|
||||||
if parsed, err := time.Parse("2006-01-02", releaseDate); err == nil {
|
if parsed, err := time.Parse("2006-01-02", releaseDate); err == nil {
|
||||||
|
|||||||
@@ -410,15 +410,19 @@ func TestGroupMediaSeriesCardsKeepsTheatricalMovieWithTVSeries(t *testing.T) {
|
|||||||
LibraryID: "anime",
|
LibraryID: "anime",
|
||||||
Title: "摇曳露营△",
|
Title: "摇曳露营△",
|
||||||
Path: `/media/动漫/摇曳露营△ (2018)/Season 01/摇曳露营△.S01E01.mkv`,
|
Path: `/media/动漫/摇曳露营△ (2018)/Season 01/摇曳露营△.S01E01.mkv`,
|
||||||
|
PosterURL: "https://image.tmdb.org/t/p/w500/episode.jpg",
|
||||||
SeasonNum: 1,
|
SeasonNum: 1,
|
||||||
EpisodeNum: 1,
|
EpisodeNum: 1,
|
||||||
}
|
}
|
||||||
theatrical := model.Media{
|
theatrical := model.Media{
|
||||||
Base: model.Base{ID: "theatrical"},
|
Base: model.Base{ID: "theatrical"},
|
||||||
LibraryID: "anime",
|
LibraryID: "anime",
|
||||||
Title: "摇曳露营△ 剧场版",
|
Title: "摇曳露营△ 剧场版",
|
||||||
Path: `/media/动漫/摇曳露营△ (2018)/摇曳露营△ 剧场版 (2022)/Eiga.Yurukyan.2022.Bluray.mkv`,
|
Path: `/media/动漫/摇曳露营△ (2018)/摇曳露营△ 剧场版 (2022)/Eiga.Yurukyan.2022.Bluray.mkv`,
|
||||||
TMDbID: 566466,
|
PosterURL: "/media/动漫/摇曳露营△ (2018)/剧场版/poster.jpg",
|
||||||
|
BackdropURL: "/media/动漫/摇曳露营△ (2018)/剧场版/background.jpg",
|
||||||
|
Overview: "剧场版简介",
|
||||||
|
TMDbID: 566466,
|
||||||
}
|
}
|
||||||
|
|
||||||
cards := groupMediaSeriesCards([]model.Media{episode, theatrical})
|
cards := groupMediaSeriesCards([]model.Media{episode, theatrical})
|
||||||
@@ -428,6 +432,12 @@ func TestGroupMediaSeriesCardsKeepsTheatricalMovieWithTVSeries(t *testing.T) {
|
|||||||
if cards[0].Count != 2 {
|
if cards[0].Count != 2 {
|
||||||
t.Fatalf("series card count=%d, want TV episode plus theatrical movie", cards[0].Count)
|
t.Fatalf("series card count=%d, want TV episode plus theatrical movie", cards[0].Count)
|
||||||
}
|
}
|
||||||
|
if cards[0].Rep.ID != episode.ID {
|
||||||
|
t.Fatalf("series representative=%q, want TV episode %q", cards[0].Rep.ID, episode.ID)
|
||||||
|
}
|
||||||
|
if cards[0].Rep.Title != episode.Title {
|
||||||
|
t.Fatalf("series representative title=%q, want %q", cards[0].Rep.Title, episode.Title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupMediaSeriesCardsDoesNotCollideMovieAndTVExternalIDs(t *testing.T) {
|
func TestGroupMediaSeriesCardsDoesNotCollideMovieAndTVExternalIDs(t *testing.T) {
|
||||||
|
|||||||
@@ -480,20 +480,31 @@ export function groupSeries(items: Media[] = []): SeriesCard[] {
|
|||||||
if (betterSeriesLinkMedia(m, g.linkMedia)) {
|
if (betterSeriesLinkMedia(m, g.linkMedia)) {
|
||||||
g.linkMedia = m
|
g.linkMedia = m
|
||||||
}
|
}
|
||||||
const currentArtwork = artworkScore(m)
|
if (betterSeriesRepresentative(m, g.rep)) {
|
||||||
const representativeArtwork = artworkScore(g.rep)
|
|
||||||
if (currentArtwork > representativeArtwork) {
|
|
||||||
g.rep = m
|
g.rep = m
|
||||||
} else if (currentArtwork === representativeArtwork) {
|
|
||||||
const cur = (m.season_num ?? 0) * 10000 + (m.episode_num ?? 0)
|
|
||||||
const rep = (g.rep.season_num ?? 0) * 10000 + (g.rep.episode_num ?? 0)
|
|
||||||
if (cur > 0 && (rep === 0 || cur < rep)) g.rep = m
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Array.from(groups.values())
|
return Array.from(groups.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function betterSeriesRepresentative(candidate: Media, current: Media): boolean {
|
||||||
|
// A theatrical feature can have local poster/background artwork that scores
|
||||||
|
// higher than the TV poster. Keep the TV row as the series-card identity so
|
||||||
|
// the movie cannot replace the whole series title and overview.
|
||||||
|
const candidateTheatrical = isTheatricalFeature(candidate)
|
||||||
|
const currentTheatrical = isTheatricalFeature(current)
|
||||||
|
if (candidateTheatrical !== currentTheatrical) return !candidateTheatrical
|
||||||
|
|
||||||
|
const candidateArtwork = artworkScore(candidate)
|
||||||
|
const currentArtwork = artworkScore(current)
|
||||||
|
if (candidateArtwork !== currentArtwork) return candidateArtwork > currentArtwork
|
||||||
|
|
||||||
|
const cur = (candidate.season_num ?? 0) * 10000 + (candidate.episode_num ?? 0)
|
||||||
|
const rep = (current.season_num ?? 0) * 10000 + (current.episode_num ?? 0)
|
||||||
|
return cur > 0 && (rep === 0 || cur < rep)
|
||||||
|
}
|
||||||
|
|
||||||
function repeatedSeriesExternalRawKey(media: Media): string {
|
function repeatedSeriesExternalRawKey(media: Media): string {
|
||||||
if (!isEpisodeLike(media) && !pathLooksEpisodic(media)) return ''
|
if (!isEpisodeLike(media) && !pathLooksEpisodic(media)) return ''
|
||||||
let identity = ''
|
let identity = ''
|
||||||
|
|||||||
Reference in New Issue
Block a user