mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 12:06:38 +08:00
优化
This commit is contained in:
@@ -24,6 +24,13 @@ func (s *ScraperService) ManualSearch(ctx context.Context, media *model.Media, q
|
||||
mediaType = lib.Type
|
||||
}
|
||||
}
|
||||
// Keep manual scraping consistent with automatic scraping for theatrical
|
||||
// features stored inside anime libraries. The UI may pass the library type
|
||||
// ("anime"), which otherwise makes TMDb stop after a TV result and hide the
|
||||
// actual movie candidate.
|
||||
if mediaLooksLikeTheatricalFeature(media) {
|
||||
mediaType = "movie"
|
||||
}
|
||||
mediaType = normalizeMediaType(mediaType, queries[0], "")
|
||||
providers := manualSearchProviderSet(provider)
|
||||
year := mediaYearHint(media)
|
||||
|
||||
@@ -313,6 +313,80 @@ func TestManualSearchReturnsMovieFallbackForTVTypedTMDbSearch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualSearchAnimeTheatricalPrefersTMDbMovie(t *testing.T) {
|
||||
var paths []string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
paths = append(paths, r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.URL.Path {
|
||||
case "/search/movie":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"results": []map[string]any{{
|
||||
"id": 635302,
|
||||
"title": "鬼灭之刃 剧场版 无限列车篇",
|
||||
"original_title": "劇場版「鬼滅の刃」無限列車編",
|
||||
"release_date": "2020-10-16",
|
||||
}},
|
||||
})
|
||||
case "/search/tv":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"results": []map[string]any{{
|
||||
"id": 85937,
|
||||
"name": "鬼灭之刃",
|
||||
"original_name": "鬼滅の刃",
|
||||
"first_air_date": "2019-04-06",
|
||||
}},
|
||||
})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Library{}, &model.Series{}, &model.Media{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
cfg := &config.Config{}
|
||||
cfg.Secrets.TMDbAPIKey = "test-key"
|
||||
cfg.Secrets.TMDbAPIProxy = upstream.URL
|
||||
log := zap.NewNop()
|
||||
scraper := NewScraperService(cfg, log, repos, NewTMDbProvider(cfg, log, nil), nil, nil, nil, NewHub(log))
|
||||
|
||||
lib := model.Library{Name: "动漫", Path: `/media/anime`, Type: "anime", Enabled: true}
|
||||
if err := repos.DB.Create(&lib).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
media := model.Media{
|
||||
LibraryID: lib.ID,
|
||||
Title: "鬼灭之刃 剧场版 无限列车篇",
|
||||
Path: `/media/anime/鬼灭之刃/鬼灭之刃 剧场版 无限列车篇.mkv`,
|
||||
}
|
||||
if err := repos.DB.Create(&media).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
results, err := scraper.ManualSearch(t.Context(), &media, media.Title, "tmdb", "anime")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(results) == 0 || results[0].TMDbID != 635302 || results[0].MediaType != "movie" {
|
||||
t.Fatalf("manual theatrical results=%#v, paths=%v", results, paths)
|
||||
}
|
||||
if len(paths) == 0 || paths[0] != "/search/movie" {
|
||||
t.Fatalf("TMDb search paths=%v, want movie first", paths)
|
||||
}
|
||||
for _, path := range paths {
|
||||
if path == "/search/tv" {
|
||||
t.Fatalf("manual theatrical search unexpectedly queried TV after finding movie: paths=%v", paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualSearchAllProvidersTMDbNumericIDTriesMovieAndTVNamespaces(t *testing.T) {
|
||||
var paths []string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -173,7 +173,7 @@ func seriesTitleFromMediaPath(path string) string {
|
||||
if last := parts[len(parts)-1]; !seriesPathPartLooksLikeFile(last) && !seriesSeasonDirRE.MatchString(filepath.Base(last)) {
|
||||
dirIndex = len(parts) - 1
|
||||
}
|
||||
for dirIndex >= 0 && seriesSeasonDirRE.MatchString(filepath.Base(parts[dirIndex])) {
|
||||
for dirIndex >= 0 && (seriesSeasonDirRE.MatchString(filepath.Base(parts[dirIndex])) || isTheatricalFolder(parts[dirIndex])) {
|
||||
dirIndex--
|
||||
}
|
||||
if dirIndex < 0 {
|
||||
|
||||
@@ -404,6 +404,33 @@ func TestGroupMediaSeriesCardsKeepsMovieVersionsAsOneMovie(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupMediaSeriesCardsKeepsTheatricalMovieWithTVSeries(t *testing.T) {
|
||||
episode := model.Media{
|
||||
Base: model.Base{ID: "episode"},
|
||||
LibraryID: "anime",
|
||||
Title: "超人高校生们",
|
||||
Path: `/media/动漫/超人高校生们/Season 01/超人高校生们.S01E01.mkv`,
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
}
|
||||
theatrical := model.Media{
|
||||
Base: model.Base{ID: "theatrical"},
|
||||
LibraryID: "anime",
|
||||
Title: "超人高校生们",
|
||||
Path: `/media/动漫/超人高校生们/剧场版/超人高校生们 剧场版.mkv`,
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
}
|
||||
|
||||
cards := groupMediaSeriesCards([]model.Media{episode, theatrical})
|
||||
if len(cards) != 1 {
|
||||
t.Fatalf("cards=%#v, want theatrical movie retained with TV series", cards)
|
||||
}
|
||||
if cards[0].Count != 2 {
|
||||
t.Fatalf("series card count=%d, want TV episode plus theatrical movie", cards[0].Count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupMediaSeriesCardsDoesNotCollideMovieAndTVExternalIDs(t *testing.T) {
|
||||
movie := model.Media{
|
||||
Base: model.Base{ID: "movie"},
|
||||
|
||||
@@ -66,6 +66,28 @@ func TestMetadataMatchCompatibilityForTheatricalFeatures(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaLooksLikeTheatricalFeatureIgnoresStaleEpisodeIdentity(t *testing.T) {
|
||||
media := &model.Media{
|
||||
Title: "未命名",
|
||||
Path: `/media/anime/超人高校生们/剧场版/超人高校生们 剧场版.mkv`,
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
}
|
||||
if !mediaLooksLikeTheatricalFeature(media) {
|
||||
t.Fatal("theatrical path should override stale persisted season/episode fields")
|
||||
}
|
||||
|
||||
realEpisode := &model.Media{
|
||||
Title: "剧场版制作幕后",
|
||||
Path: `/media/anime/超人高校生们/Season 01/超人高校生们.S01E01.mkv`,
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
}
|
||||
if mediaLooksLikeTheatricalFeature(realEpisode) {
|
||||
t.Fatal("an actual episode marker in the path must remain episodic")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrapeQueryCandidatesForAnimeTheatricalMix(t *testing.T) {
|
||||
lib := &model.Library{
|
||||
Path: `/media/anime`,
|
||||
|
||||
@@ -40,9 +40,10 @@ func mediaLooksLikeTheatricalFeature(m *model.Media) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
if m.SeasonNum > 0 || m.EpisodeNum > 0 {
|
||||
return false
|
||||
}
|
||||
// Trust an episode marker that is actually present in the path, but do not
|
||||
// trust persisted season/episode fields here. Older scans could incorrectly
|
||||
// assign those fields to a theatrical file, which would permanently prevent
|
||||
// both manual re-scraping and separation from the TV series.
|
||||
if season, ep := ParseEpisode(m.Path); season > 0 || ep > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user