mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-12 00:06:37 +08:00
bug处理
This commit is contained in:
@@ -189,19 +189,40 @@ func (s *MediaService) ListMediaEpisodes(ctx context.Context, mediaID string, vi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有聚合到多集,尝试同父目录匹配
|
// 如果没有聚合到多集,尝试同父目录匹配(排除合集目录和公共分类目录,且同目录文件不能是互不相同的独立电影)
|
||||||
if len(out) <= 1 && target.Path != "" {
|
if len(out) <= 1 && target.Path != "" {
|
||||||
targetDir := filepath.Dir(strings.ReplaceAll(target.Path, "\\", "/"))
|
targetDir := filepath.Dir(strings.ReplaceAll(target.Path, "\\", "/"))
|
||||||
dirMatches := make([]model.Media, 0)
|
parentBase := filepath.Base(targetDir)
|
||||||
for _, row := range rows {
|
if !mediaParentLooksLikeCollection(target.Path) && !seriesTitleIsGenericContainer(parentBase, *target) {
|
||||||
if row.Path != "" && filepath.Dir(strings.ReplaceAll(row.Path, "\\", "/")) == targetDir {
|
targetTitleNorm := normalizeSeriesTitle(target.Title)
|
||||||
dirMatches = append(dirMatches, row)
|
targetDirNorm := normalizeSeriesTitle(parentBase)
|
||||||
|
dirMatches := make([]model.Media, 0)
|
||||||
|
for _, row := range rows {
|
||||||
|
if row.Path == "" || filepath.Dir(strings.ReplaceAll(row.Path, "\\", "/")) != targetDir {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if row.ID == target.ID {
|
||||||
|
dirMatches = append(dirMatches, row)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rowTitleNorm := normalizeSeriesTitle(row.Title)
|
||||||
|
allowMatch := false
|
||||||
|
if isGenericMovieTitle(rowTitleNorm) || isGenericMovieTitle(targetTitleNorm) {
|
||||||
|
allowMatch = true
|
||||||
|
} else if rowTitleNorm != "" && rowTitleNorm == targetTitleNorm {
|
||||||
|
allowMatch = true
|
||||||
|
} else if rowTitleNorm != "" && targetDirNorm != "" && rowTitleNorm == targetDirNorm {
|
||||||
|
allowMatch = true
|
||||||
|
}
|
||||||
|
if allowMatch {
|
||||||
|
dirMatches = append(dirMatches, row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dirMatches) > 1 {
|
||||||
|
out = dirMatches
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(dirMatches) > 1 {
|
|
||||||
out = dirMatches
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(out) == 0 {
|
if len(out) == 0 {
|
||||||
out = []model.Media{*target}
|
out = []model.Media{*target}
|
||||||
|
|||||||
@@ -463,8 +463,49 @@ func TestGroupMediaSeriesCardsKeepsIndependentMoviesSeparateInSharedSubdirectory
|
|||||||
{LibraryID: "movies", Title: "cd1", Path: `/media/电影/指环王 (2001)/cd1.mkv`},
|
{LibraryID: "movies", Title: "cd1", Path: `/media/电影/指环王 (2001)/cd1.mkv`},
|
||||||
{LibraryID: "movies", Title: "cd2", Path: `/media/电影/指环王 (2001)/cd2.mkv`},
|
{LibraryID: "movies", Title: "cd2", Path: `/media/电影/指环王 (2001)/cd2.mkv`},
|
||||||
}
|
}
|
||||||
cdCards := groupMediaSeriesCards(cdItems)
|
cdCards := groupMediaSeriesCards(cdItems)
|
||||||
if len(cdCards) != 1 {
|
if len(cdCards) != 1 {
|
||||||
t.Fatalf("got %d cards for cd1/cd2, want 1 folded movie card", len(cdCards))
|
t.Fatalf("got %d cards for cd1/cd2, want 1 folded movie card", len(cdCards))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListMediaEpisodesKeepsIndependentMoviesSeparate(t *testing.T) {
|
||||||
|
db := newServiceTestDB(t, &model.Library{}, &model.Media{})
|
||||||
|
repos := repository.New(db)
|
||||||
|
lib := model.Library{Base: model.Base{ID: "lib-movies"}, Name: "电影", Type: "movies", Enabled: true}
|
||||||
|
if err := repos.DB.Create(&lib).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m1 := model.Media{
|
||||||
|
Base: model.Base{ID: "m1"},
|
||||||
|
LibraryID: lib.ID,
|
||||||
|
Title: "老师2024偷窥篇",
|
||||||
|
Path: `/media/小姐姐/国产/nana/老师2024偷窥篇.strm`,
|
||||||
|
}
|
||||||
|
m2 := model.Media{
|
||||||
|
Base: model.Base{ID: "m2"},
|
||||||
|
LibraryID: lib.ID,
|
||||||
|
Title: "紫光灯下的肉体诱惑",
|
||||||
|
Path: `/media/小姐姐/国产/nana/紫光灯下的肉体诱惑.strm`,
|
||||||
|
}
|
||||||
|
m3 := model.Media{
|
||||||
|
Base: model.Base{ID: "m3"},
|
||||||
|
LibraryID: lib.ID,
|
||||||
|
Title: "修洗衣机",
|
||||||
|
Path: `/media/小姐姐/国产/nana/修洗衣机.strm`,
|
||||||
|
}
|
||||||
|
if err := repos.DB.Create(&[]model.Media{m1, m2, m3}).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := NewMediaService(&config.Config{}, zap.NewNop(), repos)
|
||||||
|
eps, err := svc.ListMediaEpisodes(t.Context(), "m1", MediaVisibility{IncludeNSFW: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(eps) != 1 || eps[0].ID != "m1" {
|
||||||
|
t.Fatalf("ListMediaEpisodes got %#v, want exactly m1", eps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ArrowLeft, Film, LoaderCircle, Menu, Search, Star, X } from 'lucide-rea
|
|||||||
import { imageURL } from '../api/client'
|
import { imageURL } from '../api/client'
|
||||||
import { mediaAPI } from '../api/library'
|
import { mediaAPI } from '../api/library'
|
||||||
import type { Media, PlayProfile, User } from '../types'
|
import type { Media, PlayProfile, User } from '../types'
|
||||||
|
import { favouriteMediaLink } from '../utils/mediaNavigation'
|
||||||
import { resolveHeaderBack } from './layoutNavigation'
|
import { resolveHeaderBack } from './layoutNavigation'
|
||||||
import { LayoutThemeToggle } from './LayoutThemeToggle'
|
import { LayoutThemeToggle } from './LayoutThemeToggle'
|
||||||
import { LayoutUserMenu } from './LayoutUserMenu'
|
import { LayoutUserMenu } from './LayoutUserMenu'
|
||||||
@@ -149,17 +150,17 @@ function LayoutHeaderSearch() {
|
|||||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleSelect = (mediaId: string) => {
|
const handleSelect = (item: Media) => {
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
setQuery('')
|
setQuery('')
|
||||||
navigate(`/media/${mediaId}`)
|
navigate(favouriteMediaLink(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
} else if (e.key === 'Enter' && results.length > 0) {
|
} else if (e.key === 'Enter' && results.length > 0) {
|
||||||
handleSelect(results[0].id)
|
handleSelect(results[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +221,7 @@ function LayoutHeaderSearch() {
|
|||||||
{results.map((item) => (
|
{results.map((item) => (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => handleSelect(item.id)}
|
onClick={() => handleSelect(item)}
|
||||||
className="flex w-full items-center gap-3 rounded-xl p-2 text-left transition-colors hover:bg-[var(--app-hover)] group"
|
className="flex w-full items-center gap-3 rounded-xl p-2 text-left transition-colors hover:bg-[var(--app-hover)] group"
|
||||||
>
|
>
|
||||||
<div className="relative h-12 w-9 shrink-0 overflow-hidden rounded-lg bg-[var(--app-panel-soft)]">
|
<div className="relative h-12 w-9 shrink-0 overflow-hidden rounded-lg bg-[var(--app-panel-soft)]">
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function MediaDetailPage() {
|
|||||||
media={media}
|
media={media}
|
||||||
isAdmin={role === 'admin'}
|
isAdmin={role === 'admin'}
|
||||||
favourite={detail.favourite}
|
favourite={detail.favourite}
|
||||||
playTargetId={detail.firstPlayableEpisode?.id}
|
playTargetId={media.id || detail.firstPlayableEpisode?.id}
|
||||||
scrapeEpisodeArtwork={detail.scrapeEpisodeArtwork}
|
scrapeEpisodeArtwork={detail.scrapeEpisodeArtwork}
|
||||||
onToggleFavourite={detail.toggleFavourite}
|
onToggleFavourite={detail.toggleFavourite}
|
||||||
onScrapeEpisodeArtworkChange={detail.setScrapeEpisodeArtwork}
|
onScrapeEpisodeArtworkChange={detail.setScrapeEpisodeArtwork}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function MediaDetailPlaybackActions({
|
|||||||
onToggleFavourite,
|
onToggleFavourite,
|
||||||
playTargetId,
|
playTargetId,
|
||||||
}: MediaDetailPlaybackActionsProps) {
|
}: MediaDetailPlaybackActionsProps) {
|
||||||
const targetId = playTargetId || media.id
|
const targetId = media.id || playTargetId || ''
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
<Link to={`/play/${targetId}`} state={{ from: `/media/${media.id}` }} className="btn-primary px-6 py-3.5 shadow-sm">
|
<Link to={`/play/${targetId}`} state={{ from: `/media/${media.id}` }} className="btn-primary px-6 py-3.5 shadow-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user