mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +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 刮削出来的那几个才有)。若仍按外部
|
// 没有 douban_id/thetvdb_id(MetaTube 刮削出来的那几个才有)。若仍按外部
|
||||||
// ID 预先收窄候选集,同番号的其它分片会被 SQL 直接排除,表现就是库里折叠
|
// ID 预先收窄候选集,同番号的其它分片会被 SQL 直接排除,表现就是库里折叠
|
||||||
// 出了 N 个版本、详情页却只列出带 ID 的那几个。因此成人条目不预先收窄,
|
// 出了 N 个版本、详情页却只列出带 ID 的那几个。因此成人条目不预先收窄,
|
||||||
// 直接在该库范围内比对版本键。
|
// 直接在该库范围内比对版本键。未刮削但文件名带番号的分片同理:同一部片的
|
||||||
|
// 各分片元数据来源不一致(刮削 vs pending),收窄后标题也对不上。
|
||||||
var candidates []model.Media
|
var candidates []model.Media
|
||||||
narrowed := false
|
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)
|
candidates, narrowed, err = s.repo.Media.ListVersionCandidates(ctx, libraryIDs, *media, 5000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -198,9 +201,15 @@ func (s *MediaService) listVersionSiblings(ctx context.Context, media *model.Med
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.attachLibraryMetadata(ctx, candidates)
|
s.attachLibraryMetadata(ctx, candidates)
|
||||||
|
// 同番号只要有一个分片被确认为成人内容,尚未刮削的分片也按番号折叠,
|
||||||
|
// 与列表页的分组结果保持一致。
|
||||||
|
vouched := adultCodesVouchedByNSFW(candidates)
|
||||||
|
if vouchKey := adultVouchedGroupKey(*media, vouched); vouchKey != "" {
|
||||||
|
key = vouchKey
|
||||||
|
}
|
||||||
matched := make([]model.Media, 0, 4)
|
matched := make([]model.Media, 0, 4)
|
||||||
for _, row := range candidates {
|
for _, row := range candidates {
|
||||||
if mediaVersionGroupKey(row) == key {
|
if mediaVersionGroupKeyWithAdultVouch(row, vouched) == key {
|
||||||
matched = append(matched, row)
|
matched = append(matched, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ func groupMediaVersions(items []model.Media) []MediaItem {
|
|||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// 同一番号只要有一个分片被刮削标记为成人内容,就认定整部片都是成人条目:
|
||||||
|
// 未刮削的分片(nsfw=false)也按番号折叠。否则「一个分片刮削成功、其余仍
|
||||||
|
// pending」时,已刮削的走番号分组、其余落到标题分组,整部片被拆成两张卡。
|
||||||
|
adultCodes := adultCodesVouchedByNSFW(items)
|
||||||
type group struct {
|
type group struct {
|
||||||
key string
|
key string
|
||||||
rows []model.Media
|
rows []model.Media
|
||||||
@@ -84,7 +88,7 @@ func groupMediaVersions(items []model.Media) []MediaItem {
|
|||||||
groups := make([]group, 0, len(items))
|
groups := make([]group, 0, len(items))
|
||||||
byKey := make(map[string]int, len(items))
|
byKey := make(map[string]int, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
key := mediaVersionGroupKey(item)
|
key := mediaVersionGroupKeyWithAdultVouch(item, adultCodes)
|
||||||
if key == "" {
|
if key == "" {
|
||||||
groups = append(groups, group{rows: []model.Media{item}})
|
groups = append(groups, group{rows: []model.Media{item}})
|
||||||
continue
|
continue
|
||||||
@@ -135,16 +139,65 @@ func GroupEpisodeVersionsForDisplay(items []model.Media) []MediaItem {
|
|||||||
return grouped
|
return grouped
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaVersionGroupKey(m model.Media) string {
|
// mediaVersionLibraryKey 返回版本身份里使用的库标识。
|
||||||
// 远程 Emby 挂载条目保持独立,不与其它远程条目或本地条目折叠合并。
|
func mediaVersionLibraryKey(m model.Media) string {
|
||||||
if IsEmbyRemoteID(m.ID) {
|
|
||||||
return fmt.Sprintf("embyremote:%s", m.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
libKey := strings.ToLower(strings.TrimSpace(m.LibraryID))
|
libKey := strings.ToLower(strings.TrimSpace(m.LibraryID))
|
||||||
if libKey == "" {
|
if libKey == "" {
|
||||||
libKey = strings.ToLower(strings.TrimSpace(m.DisplayLibraryID))
|
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)
|
specialKind := mediaSpecialKind(m.Path)
|
||||||
season, episode := m.SeasonNum, m.EpisodeNum
|
season, episode := m.SeasonNum, m.EpisodeNum
|
||||||
if specialKind != "" && specialKind != mediaSpecialTheatrical && episode <= 0 {
|
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