fix library preview series counts

This commit is contained in:
ShukeBta
2026-07-02 01:16:37 +08:00
parent 5ecb5e2144
commit 5daee11c77
4 changed files with 83 additions and 5 deletions
+31 -4
View File
@@ -5,6 +5,7 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"time"
"github.com/ShukeBta/MediaStationGo/internal/model" "github.com/ShukeBta/MediaStationGo/internal/model"
) )
@@ -16,6 +17,11 @@ type SeriesCard struct {
Count int `json:"count"` Count int `json:"count"`
} }
type seriesCardGroup struct {
card SeriesCard
latest time.Time
}
func (s *MediaService) ListLibrarySeriesCards(ctx context.Context, libraryID string, visibility MediaVisibility) ([]SeriesCard, int64, error) { func (s *MediaService) ListLibrarySeriesCards(ctx context.Context, libraryID string, visibility MediaVisibility) ([]SeriesCard, int64, error) {
rows, _, err := s.listAllMediaVisible(ctx, libraryID, visibility) rows, _, err := s.listAllMediaVisible(ctx, libraryID, visibility)
if err != nil { if err != nil {
@@ -73,7 +79,7 @@ func groupMediaSeriesCards(items []model.Media) []SeriesCard {
if len(items) == 0 { if len(items) == 0 {
return nil return nil
} }
cards := make([]SeriesCard, 0) groups := make([]seriesCardGroup, 0)
byKey := make(map[string]int, len(items)) byKey := make(map[string]int, len(items))
for _, item := range items { for _, item := range items {
key := mediaSeriesKey(item) key := mediaSeriesKey(item)
@@ -81,7 +87,11 @@ func groupMediaSeriesCards(items []model.Media) []SeriesCard {
continue continue
} }
if idx, ok := byKey[key]; ok { if idx, ok := byKey[key]; ok {
card := &cards[idx] group := &groups[idx]
if latest := seriesMediaTime(item); latest.After(group.latest) {
group.latest = latest
}
card := &group.card
card.Count++ card.Count++
if betterSeriesLinkMedia(item, card.LinkMedia) { if betterSeriesLinkMedia(item, card.LinkMedia) {
card.LinkMedia = item card.LinkMedia = item
@@ -99,12 +109,29 @@ func groupMediaSeriesCards(items []model.Media) []SeriesCard {
} }
continue continue
} }
byKey[key] = len(cards) byKey[key] = len(groups)
cards = append(cards, SeriesCard{Key: key, Rep: item, LinkMedia: item, Count: 1}) groups = append(groups, seriesCardGroup{
card: SeriesCard{Key: key, Rep: item, LinkMedia: item, Count: 1},
latest: seriesMediaTime(item),
})
}
sort.SliceStable(groups, func(i, j int) bool {
return groups[i].latest.After(groups[j].latest)
})
cards := make([]SeriesCard, 0, len(groups))
for _, group := range groups {
cards = append(cards, group.card)
} }
return cards return cards
} }
func seriesMediaTime(media model.Media) time.Time {
if media.UpdatedAt.After(media.CreatedAt) {
return media.UpdatedAt
}
return media.CreatedAt
}
func betterSeriesLinkMedia(candidate, current model.Media) bool { func betterSeriesLinkMedia(candidate, current model.Media) bool {
candidateScore := librarySpecificityScore(candidate) candidateScore := librarySpecificityScore(candidate)
currentScore := librarySpecificityScore(current) currentScore := librarySpecificityScore(current)
+40
View File
@@ -2,6 +2,7 @@ package service
import ( import (
"testing" "testing"
"time"
"github.com/ShukeBta/MediaStationGo/internal/model" "github.com/ShukeBta/MediaStationGo/internal/model"
) )
@@ -28,6 +29,45 @@ func TestMediaSeriesKeyCollapsesNestedSpecialFolders(t *testing.T) {
} }
} }
func TestGroupMediaSeriesCardsSortsByLatestEpisodeTime(t *testing.T) {
now := time.Date(2026, 7, 2, 12, 0, 0, 0, time.UTC)
newerFirstEpisode := model.Media{
Base: model.Base{CreatedAt: now.Add(-72 * time.Hour), UpdatedAt: now.Add(-72 * time.Hour)},
LibraryID: "lib-tv",
Title: "更新合集",
Path: `F:\media\电视剧\国产剧\更新合集\Season 01\更新合集.S01E01.mkv`,
SeasonNum: 1,
EpisodeNum: 1,
}
newerLatestEpisode := model.Media{
Base: model.Base{CreatedAt: now, UpdatedAt: now},
LibraryID: "lib-tv",
Title: "更新合集",
Path: `F:\media\电视剧\国产剧\更新合集\Season 01\更新合集.S01E02.mkv`,
SeasonNum: 1,
EpisodeNum: 2,
}
olderSeries := model.Media{
Base: model.Base{CreatedAt: now.Add(-24 * time.Hour), UpdatedAt: now.Add(-24 * time.Hour)},
LibraryID: "lib-tv",
Title: "较早合集",
Path: `F:\media\电视剧\国产剧\较早合集\Season 01\较早合集.S01E01.mkv`,
SeasonNum: 1,
EpisodeNum: 1,
}
cards := groupMediaSeriesCards([]model.Media{olderSeries, newerFirstEpisode, newerLatestEpisode})
if len(cards) != 2 {
t.Fatalf("cards=%#v, want two series cards", cards)
}
if cards[0].Key != mediaSeriesKey(newerFirstEpisode) {
t.Fatalf("first card key=%q, want latest series key=%q", cards[0].Key, mediaSeriesKey(newerFirstEpisode))
}
if cards[0].Count != 2 {
t.Fatalf("latest series count=%d, want 2", cards[0].Count)
}
}
func TestMediaSeriesKeyCollapsesSpecialTitleSuffix(t *testing.T) { func TestMediaSeriesKeyCollapsesSpecialTitleSuffix(t *testing.T) {
main := model.Media{ main := model.Media{
LibraryID: "lib-tv", LibraryID: "lib-tv",
+8 -1
View File
@@ -7,7 +7,7 @@ import {
LibrariesEmptyState, LibrariesEmptyState,
LibrariesHeader, LibrariesHeader,
} from './LibrariesPageSections' } from './LibrariesPageSections'
import { latestLibraryCards, type LibraryPreview } from './librariesPageModel' import { isSeriesLibraryType, latestLibraryCards, type LibraryPreview } from './librariesPageModel'
export function LibrariesPage() { export function LibrariesPage() {
const [previews, setPreviews] = useState<LibraryPreview[]>([]) const [previews, setPreviews] = useState<LibraryPreview[]>([])
@@ -38,6 +38,13 @@ export function LibrariesPage() {
const libs = await libraryAPI.list() const libs = await libraryAPI.list()
const rows = await Promise.all(libs.map(async (library) => { const rows = await Promise.all(libs.map(async (library) => {
try { try {
if (isSeriesLibraryType(library.type)) {
const [seriesPage, mediaPage] = await Promise.all([
libraryAPI.listSeries(library.id, 1, 10),
libraryAPI.listMedia(library.id, 1, 1, { groupVersions: false }),
])
return { library, items: [], total: mediaPage.total, cards: seriesPage.items ?? [] } satisfies LibraryPreview
}
const page = await libraryAPI.listMedia(library.id, 1, 160, { groupVersions: false }) const page = await libraryAPI.listMedia(library.id, 1, 160, { groupVersions: false })
const cards = latestLibraryCards(page.items) const cards = latestLibraryCards(page.items)
return { library, items: page.items, total: page.total, cards } satisfies LibraryPreview return { library, items: page.items, total: page.total, cards } satisfies LibraryPreview
+4
View File
@@ -8,6 +8,10 @@ export type LibraryPreview = {
cards: SeriesCard[] cards: SeriesCard[]
} }
export function isSeriesLibraryType(type?: string) {
return type === 'tv' || type === 'anime' || type === 'variety'
}
export function latestLibraryCards(items: Media[]): SeriesCard[] { export function latestLibraryCards(items: Media[]): SeriesCard[] {
return groupSeries(items) return groupSeries(items)
.sort((a, b) => mediaTime(b.rep) - mediaTime(a.rep) || artworkScore(b.rep) - artworkScore(a.rep)) .sort((a, b) => mediaTime(b.rep) - mediaTime(a.rep) || artworkScore(b.rep) - artworkScore(a.rep))