mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 03:06:38 +08:00
优化
This commit is contained in:
@@ -182,10 +182,13 @@ func (s *MediaService) listVersionSiblings(ctx context.Context, media *model.Med
|
||||
// 没有 douban_id/thetvdb_id(MetaTube 刮削出来的那几个才有)。若仍按外部
|
||||
// ID 预先收窄候选集,同番号的其它分片会被 SQL 直接排除,表现就是库里折叠
|
||||
// 出了 N 个版本、详情页却只列出带 ID 的那几个。因此成人条目不预先收窄,
|
||||
// 直接在该库范围内比对版本键。
|
||||
// 直接在该库范围内比对版本键。未刮削但文件名带番号的分片同理:同一部片的
|
||||
// 各分片元数据来源不一致(刮削 vs pending),收窄后标题也对不上。
|
||||
var candidates []model.Media
|
||||
narrowed := false
|
||||
if mediaAdultGroupCode(*media) == "" {
|
||||
hasAdultCode := mediaAdultGroupCode(*media) != "" ||
|
||||
canonicalAdultGroupCode(AdultCodeFromMediaPath(media.Path)) != ""
|
||||
if !hasAdultCode {
|
||||
candidates, narrowed, err = s.repo.Media.ListVersionCandidates(ctx, libraryIDs, *media, 5000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -198,9 +201,15 @@ func (s *MediaService) listVersionSiblings(ctx context.Context, media *model.Med
|
||||
}
|
||||
}
|
||||
s.attachLibraryMetadata(ctx, candidates)
|
||||
// 同番号只要有一个分片被确认为成人内容,尚未刮削的分片也按番号折叠,
|
||||
// 与列表页的分组结果保持一致。
|
||||
vouched := adultCodesVouchedByNSFW(candidates)
|
||||
if vouchKey := adultVouchedGroupKey(*media, vouched); vouchKey != "" {
|
||||
key = vouchKey
|
||||
}
|
||||
matched := make([]model.Media, 0, 4)
|
||||
for _, row := range candidates {
|
||||
if mediaVersionGroupKey(row) == key {
|
||||
if mediaVersionGroupKeyWithAdultVouch(row, vouched) == key {
|
||||
matched = append(matched, row)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,10 @@ func groupMediaVersions(items []model.Media) []MediaItem {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
// 同一番号只要有一个分片被刮削标记为成人内容,就认定整部片都是成人条目:
|
||||
// 未刮削的分片(nsfw=false)也按番号折叠。否则「一个分片刮削成功、其余仍
|
||||
// pending」时,已刮削的走番号分组、其余落到标题分组,整部片被拆成两张卡。
|
||||
adultCodes := adultCodesVouchedByNSFW(items)
|
||||
type group struct {
|
||||
key string
|
||||
rows []model.Media
|
||||
@@ -84,7 +88,7 @@ func groupMediaVersions(items []model.Media) []MediaItem {
|
||||
groups := make([]group, 0, len(items))
|
||||
byKey := make(map[string]int, len(items))
|
||||
for _, item := range items {
|
||||
key := mediaVersionGroupKey(item)
|
||||
key := mediaVersionGroupKeyWithAdultVouch(item, adultCodes)
|
||||
if key == "" {
|
||||
groups = append(groups, group{rows: []model.Media{item}})
|
||||
continue
|
||||
@@ -135,16 +139,65 @@ func GroupEpisodeVersionsForDisplay(items []model.Media) []MediaItem {
|
||||
return grouped
|
||||
}
|
||||
|
||||
func mediaVersionGroupKey(m model.Media) string {
|
||||
// 远程 Emby 挂载条目保持独立,不与其它远程条目或本地条目折叠合并。
|
||||
if IsEmbyRemoteID(m.ID) {
|
||||
return fmt.Sprintf("embyremote:%s", m.ID)
|
||||
}
|
||||
|
||||
// mediaVersionLibraryKey 返回版本身份里使用的库标识。
|
||||
func mediaVersionLibraryKey(m model.Media) string {
|
||||
libKey := strings.ToLower(strings.TrimSpace(m.LibraryID))
|
||||
if libKey == "" {
|
||||
libKey = strings.ToLower(strings.TrimSpace(m.DisplayLibraryID))
|
||||
}
|
||||
return libKey
|
||||
}
|
||||
|
||||
// adultCodesVouchedByNSFW 收集「已被刮削确认为成人内容」的番号。
|
||||
func adultCodesVouchedByNSFW(items []model.Media) map[string]bool {
|
||||
codes := map[string]bool{}
|
||||
for _, item := range items {
|
||||
if !item.NSFW {
|
||||
continue
|
||||
}
|
||||
if code := canonicalAdultGroupCode(AdultCodeFromMediaPath(item.Path)); code != "" {
|
||||
codes[code] = true
|
||||
continue
|
||||
}
|
||||
if code := mediaAdultGroupCode(item); code != "" {
|
||||
codes[code] = true
|
||||
}
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
// mediaVersionGroupKeyWithAdultVouch 是版本身份的统一入口:番号已被同组分片
|
||||
// 确认时优先按番号折叠,否则沿用 mediaVersionGroupKey 的原判定。
|
||||
func mediaVersionGroupKeyWithAdultVouch(m model.Media, vouched map[string]bool) string {
|
||||
if key := adultVouchedGroupKey(m, vouched); key != "" {
|
||||
return key
|
||||
}
|
||||
return mediaVersionGroupKey(m)
|
||||
}
|
||||
|
||||
// adultVouchedGroupKey 在番号已被同组分片确认时,返回番号分组键;
|
||||
// 其余情况返回空串,表示沿用 mediaVersionGroupKey 的原判定。
|
||||
func adultVouchedGroupKey(m model.Media, vouched map[string]bool) string {
|
||||
if len(vouched) == 0 || m.NSFW {
|
||||
return ""
|
||||
}
|
||||
code := canonicalAdultGroupCode(AdultCodeFromMediaPath(m.Path))
|
||||
if code == "" || !vouched[code] {
|
||||
return ""
|
||||
}
|
||||
libKey := mediaVersionLibraryKey(m)
|
||||
if libKey == "" {
|
||||
libKey = "_"
|
||||
}
|
||||
return fmt.Sprintf("adult:%s:%s", libKey, code)
|
||||
}
|
||||
|
||||
func mediaVersionGroupKey(m model.Media) string { // 远程 Emby 挂载条目保持独立,不与其它远程条目或本地条目折叠合并。
|
||||
if IsEmbyRemoteID(m.ID) {
|
||||
return fmt.Sprintf("embyremote:%s", m.ID)
|
||||
}
|
||||
|
||||
libKey := mediaVersionLibraryKey(m)
|
||||
specialKind := mediaSpecialKind(m.Path)
|
||||
season, episode := m.SeasonNum, m.EpisodeNum
|
||||
if specialKind != "" && specialKind != mediaSpecialTheatrical && episode <= 0 {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/truewhile/MeBox/internal/model"
|
||||
)
|
||||
|
||||
// 真实场景:同一部片的分片里只有第 4 个被刮削成功(nsfw=true),其余仍是 pending。
|
||||
// 修复前:已刮削的分片走「番号」分组、其余落到「标题」分组,7 个分片会裂成两张卡
|
||||
// (一张只有第 4 个,另一张是其余 6 个)。
|
||||
func TestGroupMediaVersionsKeepsUnscrapedPartsWithScrapedSibling(t *testing.T) {
|
||||
const libID = "lib-1"
|
||||
const dir = "/media/云下载/sivr-270/"
|
||||
|
||||
parts := func(nums ...int) []model.Media {
|
||||
out := make([]model.Media, 0, len(nums))
|
||||
for _, n := range nums {
|
||||
out = append(out, model.Media{
|
||||
LibraryID: libID,
|
||||
Title: "sivr 270",
|
||||
Path: dir + "sivr-270-" + strconv.Itoa(n) + ".strm",
|
||||
Container: "strm",
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
items := parts(1, 2, 3)
|
||||
items = append(items, model.Media{
|
||||
LibraryID: libID,
|
||||
Title: "SIVR-270-【VR】河北彩花×ご奉仕ナース",
|
||||
OriginalName: "SIVR-270",
|
||||
Path: dir + "sivr-270-4.strm",
|
||||
NSFW: true,
|
||||
ScrapeStatus: "matched",
|
||||
Container: "strm",
|
||||
})
|
||||
items = append(items, parts(5, 6, 7)...)
|
||||
|
||||
grouped := GroupMediaVersions(items)
|
||||
if len(grouped) != 1 {
|
||||
paths := make([]string, 0, len(grouped))
|
||||
for _, g := range grouped {
|
||||
paths = append(paths, g.Path)
|
||||
}
|
||||
t.Fatalf("分成 %d 张卡 %v,期望折叠成 1 张", len(grouped), paths)
|
||||
}
|
||||
if len(grouped[0].Versions) != 7 {
|
||||
t.Fatalf("版本数 = %d,期望 7", len(grouped[0].Versions))
|
||||
}
|
||||
}
|
||||
|
||||
// 反向保护:没有任何分片被确认为成人内容时,不能仅凭文件名里的番号形态折叠,
|
||||
// 否则普通媒体也可能被误并进同一张卡。
|
||||
func TestGroupMediaVersionsIgnoresUnvouchedAdultCode(t *testing.T) {
|
||||
items := []model.Media{
|
||||
{LibraryID: "lib-2", Title: "Alpha Movie", Path: "/media/movies/Alpha Movie/ABC-123-1080p.mkv"},
|
||||
{LibraryID: "lib-2", Title: "Beta Movie", Path: "/media/movies/Beta Movie/ABC-123-2160p.mkv"},
|
||||
}
|
||||
grouped := GroupMediaVersions(items)
|
||||
if len(grouped) != 2 {
|
||||
t.Fatalf("分成 %d 张卡,期望 2 张(未被确认成人内容的番号不参与折叠)", len(grouped))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user