diff --git a/Shoko.Server/API/v3/Controllers/SeriesController.cs b/Shoko.Server/API/v3/Controllers/SeriesController.cs index 04cd7f961..243a6fe12 100644 --- a/Shoko.Server/API/v3/Controllers/SeriesController.cs +++ b/Shoko.Server/API/v3/Controllers/SeriesController.cs @@ -1570,7 +1570,8 @@ public async Task OverrideTMDBEpisodeMappingsBySeriesID( /// Shoko Series ID. /// The specified TMDB Show ID to search for links. This parameter is used to select a specific show. /// The specified TMDB Season ID to search for links. If not provided, links are searched for any season of the selected or first linked show. - /// Determines whether to retain any and all existing links. + /// Determines whether to retain existing links when picking episodes. + /// Determines whether to consider existing links for other series when picking episodes. /// The page size. /// The page index. /// A preview of the automagically matched episodes. @@ -1581,6 +1582,7 @@ public async Task OverrideTMDBEpisodeMappingsBySeriesID( [FromQuery] int? tmdbShowID, [FromQuery] int? tmdbSeasonID, [FromQuery] bool keepExisting = true, + [FromQuery] bool? considerExistingOtherLinks = null, [FromQuery, Range(0, 1000)] int pageSize = 50, [FromQuery, Range(1, int.MaxValue)] int page = 1 ) @@ -1612,7 +1614,7 @@ public async Task OverrideTMDBEpisodeMappingsBySeriesID( return ValidationProblem("The selected tmdbSeasonID does not belong to the selected tmdbShowID", "tmdbSeasonID"); } - return _tmdbLinkingService.MatchAnidbToTmdbEpisodes(series.AniDB_ID, tmdbShowID.Value, tmdbSeasonID, keepExisting, saveToDatabase: false) + return _tmdbLinkingService.MatchAnidbToTmdbEpisodes(series.AniDB_ID, tmdbShowID.Value, tmdbSeasonID, useExisting: keepExisting, useExistingOtherShows: considerExistingOtherLinks, saveToDatabase: false) .ToListResult(x => new TmdbEpisode.CrossReference(x), page, pageSize); } @@ -1674,7 +1676,7 @@ public async Task AutoTMDBEpisodeMappingsBySeriesID( if (isMissing) await _tmdbLinkingService.AddShowLink(series.AniDB_ID, body.TmdbShowID.Value, additiveLink: true); else - _tmdbLinkingService.MatchAnidbToTmdbEpisodes(series.AniDB_ID, body.TmdbShowID.Value, body.TmdbSeasonID, body.KeepExisting, saveToDatabase: true); + _tmdbLinkingService.MatchAnidbToTmdbEpisodes(series.AniDB_ID, body.TmdbShowID.Value, body.TmdbSeasonID, useExisting: body.KeepExisting, useExistingOtherShows: body.ConsiderExistingOtherLinks, saveToDatabase: true); if (tmdbShow.CreatedAt == tmdbShow.LastUpdatedAt) { diff --git a/Shoko.Server/API/v3/Models/Shoko/Series.cs b/Shoko.Server/API/v3/Models/Shoko/Series.cs index 5bae7af3f..dc69b3297 100644 --- a/Shoko.Server/API/v3/Models/Shoko/Series.cs +++ b/Shoko.Server/API/v3/Models/Shoko/Series.cs @@ -711,10 +711,15 @@ public class AutoMatchTmdbEpisodesBody public int? TmdbSeasonID { get; set; } /// - /// Determines whether to retain any and all existing links. + /// Determines whether to retain existing links for the current series. /// [DefaultValue(true)] public bool KeepExisting { get; set; } = true; + + /// + /// Determines whether to consider existing links for other series when picking episodes. + /// + public bool? ConsiderExistingOtherLinks { get; set; } } public class OverrideTmdbEpisodeMappingBody diff --git a/Shoko.Server/Providers/TMDB/TmdbLinkingService.cs b/Shoko.Server/Providers/TMDB/TmdbLinkingService.cs index d31364960..d9bc36c40 100644 --- a/Shoko.Server/Providers/TMDB/TmdbLinkingService.cs +++ b/Shoko.Server/Providers/TMDB/TmdbLinkingService.cs @@ -14,6 +14,7 @@ using Shoko.Server.Scheduling; using Shoko.Server.Scheduling.Jobs.TMDB; using Shoko.Server.Server; +using Shoko.Server.Settings; using Shoko.Server.Utilities; using CrossRefSource = Shoko.Models.Enums.CrossRefSource; @@ -32,6 +33,8 @@ public class TmdbLinkingService private readonly ISchedulerFactory _schedulerFactory; + private readonly ISettingsProvider _settingsProvider; + private readonly TmdbImageService _imageService; private readonly AnimeSeriesRepository _animeSeries; @@ -55,6 +58,7 @@ public class TmdbLinkingService public TmdbLinkingService( ILogger logger, ISchedulerFactory schedulerFactory, + ISettingsProvider settingsProvider, TmdbImageService imageService, AnimeSeriesRepository animeSeries, AniDB_AnimeRepository anidbAnime, @@ -69,6 +73,7 @@ CrossRef_AniDB_TMDB_EpisodeRepository xrefAnidbTmdbEpisodes { _logger = logger; _schedulerFactory = schedulerFactory; + _settingsProvider = settingsProvider; _imageService = imageService; _animeSeries = animeSeries; _anidbAnime = anidbAnime; @@ -382,7 +387,7 @@ public bool SetEpisodeLink(int anidbEpisodeId, int tmdbEpisodeId, bool additiveL return true; } - public IReadOnlyList MatchAnidbToTmdbEpisodes(int anidbAnimeId, int tmdbShowId, int? tmdbSeasonId, bool useExisting = false, bool saveToDatabase = false, bool useExistingOtherShows = true) + public IReadOnlyList MatchAnidbToTmdbEpisodes(int anidbAnimeId, int tmdbShowId, int? tmdbSeasonId, bool useExisting = false, bool saveToDatabase = false, bool? useExistingOtherShows = null) { var anime = _anidbAnime.GetByAnimeID(anidbAnimeId); if (anime == null) @@ -416,6 +421,21 @@ public IReadOnlyList MatchAnidbToTmdbEpisodes(int a var tmdbEpisodes = tmdbEpisodeDict.Values .Where(episode => episode.SeasonNumber == 0 || !tmdbSeasonId.HasValue || episode.TmdbSeasonID == tmdbSeasonId.Value) .ToList(); + var considerExistingOtherLinks = useExistingOtherShows ?? _settingsProvider.GetSettings().TMDB.ConsiderExistingOtherLinks; + if (considerExistingOtherLinks) + { + var otherShowsExisting = existing.Values.SelectMany(xref => xref).ExceptBy(anidbEpisodes.Keys.Append(0), xref => xref.AnidbEpisodeID).ToList(); + foreach (var link in otherShowsExisting) + { + _logger.LogTrace("Skipping existing episode link: AniDB episode (EpisodeID={EpisodeID},AnimeID={AnimeID}) → TMDB episode (EpisodeID={TmdbID})", link.AnidbEpisodeID, link.AnidbAnimeID, link.TmdbEpisodeID); + + // Exclude the linked episodes from the auto-match candidates. + var index = tmdbEpisodes.FindIndex(episode => episode.TmdbEpisodeID == link.TmdbEpisodeID); + if (index >= 0) + tmdbEpisodes.RemoveAt(index); + } + } + var tmdbNormalEpisodes = isOVA ? tmdbEpisodes : tmdbEpisodes .Where(episode => episode.SeasonNumber != 0) .OrderBy(episode => episode.SeasonNumber) diff --git a/Shoko.Server/Settings/TMDBSettings.cs b/Shoko.Server/Settings/TMDBSettings.cs index df5c5e322..1b9a2f668 100644 --- a/Shoko.Server/Settings/TMDBSettings.cs +++ b/Shoko.Server/Settings/TMDBSettings.cs @@ -23,6 +23,17 @@ public class TMDBSettings /// public bool AutoLinkRestricted { get; set; } = false; + /// + /// Determines whether to consider existing cross-reference links to other + /// AniDB anime when linking an AniDB anime to a TMDB show. + /// + /// + /// This setting also applies to the auto-matching process and can be + /// overridden on a per request basis for the API when previewing or + /// linking. + /// + public bool ConsiderExistingOtherLinks { get; set; } = false; + /// /// Indicates that all titles should be stored locally for the TMDB entity, /// otherwise it will use