mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 20:46:37 +08:00
fix library preview series counts
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user