diff --git a/internal/service/adult_scraper_routing_test.go b/internal/service/adult_scraper_routing_test.go index 0750116..57b1497 100644 --- a/internal/service/adult_scraper_routing_test.go +++ b/internal/service/adult_scraper_routing_test.go @@ -96,8 +96,9 @@ func TestAdultProviderRouting(t *testing.T) { if len(candidates) != 1 { t.Fatalf("expected 1 candidate, got %d", len(candidates)) } - if candidates[0].PosterURL != "https://example.com/poster.jpg" || - candidates[0].BackdropURL != "https://example.com/backdrop.jpg" { + wantPoster := mtServer.URL + "/v1/images/primary/javdb/999?auto=false&pos=-1&quality=90&ratio=0.6666666666666666" + wantBackdrop := mtServer.URL + "/v1/images/backdrop/javdb/999?quality=90" + if candidates[0].PosterURL != wantPoster || candidates[0].BackdropURL != wantBackdrop { t.Fatalf("candidate artwork was not enriched: %#v", candidates[0]) } diff --git a/internal/service/manual_scrape.go b/internal/service/manual_scrape.go index d068d5e..b88a21f 100644 --- a/internal/service/manual_scrape.go +++ b/internal/service/manual_scrape.go @@ -137,10 +137,11 @@ func (s *ScraperService) manualRequestMatch(ctx context.Context, req ManualScrap if match != nil { // Older search responses used the portrait poster as a backdrop // fallback. Do not let that placeholder replace a real preview - // image fetched from the selected MetaTube provider. + // image or processed primary image fetched from MetaTube. if strings.TrimSpace(match.BackdropURL) != "" && strings.TrimSpace(req.BackdropURL) == strings.TrimSpace(req.PosterURL) { req.BackdropURL = "" + req.PosterURL = "" } return mergeManualRequestIntoMatch(match, req), nil } diff --git a/internal/service/manual_scrape_test.go b/internal/service/manual_scrape_test.go index 767c7c2..7d06cff 100644 --- a/internal/service/manual_scrape_test.go +++ b/internal/service/manual_scrape_test.go @@ -93,11 +93,13 @@ func TestManualAdultMatchUsesSelectedMetaTubeDetailsAndRealBackdrop(t *testing.T if detailCalls.Load() != 1 { t.Fatalf("selected MetaTube detail calls = %d, want 1", detailCalls.Load()) } - if match.PosterURL != "https://example.com/search-cover.jpg" { - t.Fatalf("selected poster = %q", match.PosterURL) + wantPoster := upstream.URL + "/v1/images/primary/AVE/94600?auto=false&pos=-1&quality=90&ratio=0.6666666666666666" + if match.PosterURL != wantPoster { + t.Fatalf("selected poster = %q, want %q", match.PosterURL, wantPoster) } - if match.BackdropURL != "https://example.com/backdrop.jpg" { - t.Fatalf("backdrop = %q, want detail preview", match.BackdropURL) + wantBackdrop := upstream.URL + "/v1/images/backdrop/AVE/94600?quality=90" + if match.BackdropURL != wantBackdrop { + t.Fatalf("backdrop = %q, want %q", match.BackdropURL, wantBackdrop) } } diff --git a/internal/service/metatube.go b/internal/service/metatube.go index 59a2de0..178eb7a 100644 --- a/internal/service/metatube.go +++ b/internal/service/metatube.go @@ -83,7 +83,7 @@ func (p *MetaTubeProvider) Search(ctx context.Context, cfg MetaTubeConfig, query matches := make([]*Match, 0, len(results)) for _, res := range results { - match := p.convertSearchResultToMatch(query, &res) + match := p.convertSearchResultToMatch(cfg, query, &res) if match != nil { matches = append(matches, match) } @@ -134,7 +134,7 @@ func (p *MetaTubeProvider) GetMovie(ctx context.Context, cfg MetaTubeConfig, pro return nil, fmt.Errorf("decode metatube movie response failed: %w", directErr) } - return p.convertMovieInfoToMatch(&movie), nil + return p.convertMovieInfoToMatch(cfg, &movie), nil } // SearchAndGetBestMatch 执行搜索并拉取首个最佳结果的完整电影详情。 @@ -276,7 +276,7 @@ func (p *MetaTubeProvider) applyAuthHeader(req *http.Request, token string) { req.Header.Set("User-Agent", "MeBox/1.0 (MetaTube Client)") } -func (p *MetaTubeProvider) convertSearchResultToMatch(query string, res *MetaTubeSearchResult) *Match { +func (p *MetaTubeProvider) convertSearchResultToMatch(cfg MetaTubeConfig, query string, res *MetaTubeSearchResult) *Match { if res == nil { return nil } @@ -300,14 +300,20 @@ func (p *MetaTubeProvider) convertSearchResultToMatch(query string, res *MetaTub } } - posterURL := firstNonEmpty(res.BigCoverURL, res.CoverURL, res.BigThumbURL, res.ThumbURL) + posterURL, backdropURL := metaTubeArtworkURLs(cfg, res.Provider, res.ID) + if posterURL == "" { + posterURL = firstNonEmpty(res.BigThumbURL, res.ThumbURL, res.BigCoverURL, res.CoverURL) + } + if backdropURL == "" { + backdropURL = firstNonEmpty(res.BigCoverURL, res.CoverURL, posterURL) + } return &Match{ MediaType: "adult", Title: formattedTitle, OriginalName: code, PosterURL: posterURL, - BackdropURL: posterURL, + BackdropURL: backdropURL, Year: year, ReleaseDate: cleanDateString(res.ReleaseDate), Rating: res.Score, @@ -318,7 +324,7 @@ func (p *MetaTubeProvider) convertSearchResultToMatch(query string, res *MetaTub } } -func (p *MetaTubeProvider) convertMovieInfoToMatch(movie *MetaTubeMovieInfo) *Match { +func (p *MetaTubeProvider) convertMovieInfoToMatch(cfg MetaTubeConfig, movie *MetaTubeMovieInfo) *Match { if movie == nil { return nil } @@ -336,10 +342,16 @@ func (p *MetaTubeProvider) convertMovieInfoToMatch(movie *MetaTubeMovieInfo) *Ma year := parseYearFromDate(movie.ReleaseDate) - posterURL := firstNonEmpty(movie.BigCoverURL, movie.CoverURL, movie.BigThumbURL, movie.ThumbURL) - backdropURL := posterURL - if len(movie.PreviewImages) > 0 && movie.PreviewImages[0] != "" { - backdropURL = movie.PreviewImages[0] + posterURL, backdropURL := metaTubeArtworkURLs(cfg, movie.Provider, movie.ID) + if posterURL == "" { + posterURL = firstNonEmpty(movie.BigThumbURL, movie.ThumbURL, movie.BigCoverURL, movie.CoverURL) + } + if backdropURL == "" { + if len(movie.PreviewImages) > 0 && movie.PreviewImages[0] != "" { + backdropURL = movie.PreviewImages[0] + } else { + backdropURL = firstNonEmpty(movie.BigCoverURL, movie.CoverURL, posterURL) + } } genres := make([]string, 0, len(movie.Genres)+len(movie.Actors)+4) @@ -385,6 +397,33 @@ func (p *MetaTubeProvider) convertMovieInfoToMatch(movie *MetaTubeMovieInfo) *Ma } } +func metaTubeArtworkURLs(cfg MetaTubeConfig, provider, id string) (string, string) { + serverURL := strings.TrimRight(strings.TrimSpace(cfg.ServerURL), "/") + provider = strings.TrimSpace(provider) + id = strings.TrimSpace(id) + if serverURL == "" || provider == "" || id == "" { + return "", "" + } + + imageURL := func(kind string, primary bool) string { + base := fmt.Sprintf("%s/v1/images/%s/%s/%s", serverURL, kind, url.PathEscape(provider), url.PathEscape(id)) + values := url.Values{} + values.Set("quality", "90") + if primary { + values.Set("pos", "-1") + values.Set("auto", "false") + if cfg.CropCover { + values.Set("ratio", strconv.FormatFloat(2.0/3.0, 'f', -1, 64)) + } else { + values.Set("ratio", "-1") + } + } + return base + "?" + values.Encode() + } + + return imageURL("primary", true), imageURL("backdrop", false) +} + func parseYearFromDate(dateStr string) int { dateStr = strings.TrimSpace(dateStr) if len(dateStr) >= 4 { diff --git a/internal/service/metatube_test.go b/internal/service/metatube_test.go index 5dfd569..438617b 100644 --- a/internal/service/metatube_test.go +++ b/internal/service/metatube_test.go @@ -74,6 +74,12 @@ func TestMetaTubeProviderSearch(t *testing.T) { if len(m.Genres) != 1 || m.Genres[0] != "相沢みなみ" { t.Errorf("unexpected genres: %v", m.Genres) } + if want := server.URL + "/v1/images/primary/javdb/123456?auto=false&pos=-1&quality=90&ratio=-1"; m.PosterURL != want { + t.Errorf("poster URL = %q, want %q", m.PosterURL, want) + } + if want := server.URL + "/v1/images/backdrop/javdb/123456?quality=90"; m.BackdropURL != want { + t.Errorf("backdrop URL = %q, want %q", m.BackdropURL, want) + } } func TestMetaTubeProviderGetMovie(t *testing.T) { @@ -112,6 +118,7 @@ func TestMetaTubeProviderGetMovie(t *testing.T) { provider := NewMetaTubeProvider(zap.NewNop()) cfg := MetaTubeConfig{ ServerURL: server.URL, + CropCover: true, } match, err := provider.GetMovie(context.Background(), cfg, "javdb", "123456") @@ -124,11 +131,11 @@ func TestMetaTubeProviderGetMovie(t *testing.T) { if match.Overview != "超绝美少女相沢みなみ出道作品!" { t.Errorf("unexpected overview: %s", match.Overview) } - if match.PosterURL != "https://example.com/big_cover.jpg" { - t.Errorf("unexpected poster URL: %s", match.PosterURL) + if want := server.URL + "/v1/images/primary/javdb/123456?auto=false&pos=-1&quality=90&ratio=0.6666666666666666"; match.PosterURL != want { + t.Errorf("poster URL = %q, want %q", match.PosterURL, want) } - if match.BackdropURL != "https://example.com/preview1.jpg" { - t.Errorf("unexpected backdrop URL: %s", match.BackdropURL) + if want := server.URL + "/v1/images/backdrop/javdb/123456?quality=90"; match.BackdropURL != want { + t.Errorf("backdrop URL = %q, want %q", match.BackdropURL, want) } if match.Year != 2018 { t.Errorf("expected year 2018, got %d", match.Year)