mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 03:06:38 +08:00
137 lines
3.2 KiB
Go
137 lines
3.2 KiB
Go
package service
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/truewhile/MeBox/internal/config"
|
|
"github.com/truewhile/MeBox/internal/model"
|
|
"github.com/truewhile/MeBox/internal/repository"
|
|
)
|
|
|
|
// ScraperService coordinates metadata enrichment across providers.
|
|
type ScraperService struct {
|
|
cfg *config.Config
|
|
log *zap.Logger
|
|
repo *repository.Container
|
|
tmdb *TMDbProvider
|
|
bangumi *BangumiProvider
|
|
thetvdb *TheTVDBProvider
|
|
douban *DoubanProvider
|
|
fanart *FanartProvider
|
|
adult *AdultProvider
|
|
hub *Hub
|
|
cache *RuntimeCacheService
|
|
images *ImageProxy
|
|
|
|
// Caches provider-chain results keyed by media kind/query/year. Per-instance
|
|
// so services with different provider config never share results.
|
|
lookupCache *scrapeLookupCache
|
|
|
|
// Serializes final sidecar replacement. Windows cannot rename over an
|
|
// existing file, and concurrent scrapes can target the same sidecar.
|
|
artworkWriteMu sync.Mutex
|
|
}
|
|
|
|
// NewScraperService is the constructor.
|
|
func NewScraperService(
|
|
cfg *config.Config,
|
|
log *zap.Logger,
|
|
repo *repository.Container,
|
|
tmdb *TMDbProvider,
|
|
bangumi *BangumiProvider,
|
|
thetvdb *TheTVDBProvider,
|
|
fanart *FanartProvider,
|
|
hub *Hub,
|
|
adult ...*AdultProvider,
|
|
) *ScraperService {
|
|
var adultProvider *AdultProvider
|
|
if len(adult) > 0 {
|
|
adultProvider = adult[0]
|
|
}
|
|
return &ScraperService{
|
|
cfg: cfg, log: log, repo: repo,
|
|
tmdb: tmdb, bangumi: bangumi, thetvdb: thetvdb, fanart: fanart, adult: adultProvider, hub: hub,
|
|
lookupCache: newScrapeLookupCache(),
|
|
}
|
|
}
|
|
|
|
func (s *ScraperService) SetDouban(douban *DoubanProvider) {
|
|
s.douban = douban
|
|
}
|
|
|
|
func (s *ScraperService) SetRuntimeCache(cache *RuntimeCacheService) *ScraperService {
|
|
if s != nil {
|
|
s.cache = cache
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *ScraperService) SetImageProxy(images *ImageProxy) *ScraperService {
|
|
if s != nil {
|
|
s.images = images
|
|
}
|
|
return s
|
|
}
|
|
|
|
var tmdbDetailsTimeout = 8 * time.Second
|
|
|
|
const (
|
|
defaultScrapeDelayMinMS = 250
|
|
defaultScrapeDelayMaxMS = 500
|
|
maxScrapeDelayMS = 5 * 60 * 1000
|
|
)
|
|
|
|
// AnyEnabled reports whether at least one provider can run.
|
|
func (s *ScraperService) AnyEnabled() bool {
|
|
if s.tmdb != nil && s.tmdb.Enabled() {
|
|
return true
|
|
}
|
|
if s.bangumi != nil && s.bangumi.Enabled() {
|
|
return true
|
|
}
|
|
if s.thetvdb != nil && s.thetvdb.Enabled() {
|
|
return true
|
|
}
|
|
if s.adult != nil && s.adult.Enabled() {
|
|
return true
|
|
}
|
|
if s.douban != nil && s.douban.Enabled() {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// determineMediaType returns "tv" for TV shows and "movie" for movies.
|
|
// It uses the library type as the primary signal.
|
|
func (s *ScraperService) determineMediaType(lib *model.Library, match *Match) string {
|
|
return s.determineMediaTypeForMedia(lib, nil, match)
|
|
}
|
|
|
|
func (s *ScraperService) determineMediaTypeForMedia(lib *model.Library, media *model.Media, match *Match) string {
|
|
if match != nil {
|
|
switch normalizeOrganizeMediaType(match.MediaType) {
|
|
case "tv", "anime", "variety":
|
|
return "tv"
|
|
case "movie", "adult":
|
|
return "movie"
|
|
}
|
|
}
|
|
if media != nil && mediaIsEpisodic(media, lib) {
|
|
return "tv"
|
|
}
|
|
if lib != nil {
|
|
switch lib.Type {
|
|
case "tv", "anime", "variety", "show", "shows":
|
|
return "tv"
|
|
}
|
|
}
|
|
// Fallback: if Bangumi ID is present, treat as TV/anime.
|
|
if match != nil && match.BangumiID > 0 {
|
|
return "tv"
|
|
}
|
|
return "movie"
|
|
}
|