Repository: Ombi-app/Ombi (2026-06-12T06:01:40Z)

Issue:
Movie with the same TMDB ID as an available serie is wrongly displayed as available
### Summary

As TMDB has separated IDs for movies an tv series, serie/movie can have the same ID and can only be distinguished by knowing if the id is one of a serie or a movie.

When a serie is available on my jellyfin server, the movie with the same ID is displayed as available and clicking on "Watch on Jellyfin" button just redirects on the serie.

The inverted case does not have the same behavior.

  
> My Ombi container identifies both Crazy Dad (details/movie/87428) and Why Women Kill (/details/tv/87428) as available on my Jellyfin server.
> But in reality i only have Why Women Kill (TMDB Id : 87428) in Jellyfin.
> 
> 
> I just tried a little experiment :
> 
> 1. On Jellyfin i have the Wolf of Wall Street (TMDB Id : 106646), it is _correctly_ shown as available by ombi with the ID /details/movie/106646, and the **serie** with the same ID /details/tv/106646 (Wasserwelten der Tiere) is _correctly_ shown as unavailable.
> 2. On Jellyfin i have Hannibal (TMDB Id : 40008), it is _correctly_ shown as available by ombi with the ID /details/tv/40008, but the **movie** with the same ID /details/movie/40008 (Some strange chinese movie) is  **_incorrectly_** shown as available.
> 
> 
> 
> It seems like Ombi is mixing TV and Movies to display availability and show "Watch on Jellyfin" in the specific case where a movie has the same id as an available serie but not the inverse. 

 _Originally posted by @rlouazon in [#5087](https://github.com/Ombi-app/Ombi/issues/5087#issuecomment-4652145659)_
  
  


### Ombi Version

4.53.4

### What platform(s) does this occur on?

Docker (on jellyfin media server)

### What database are you using?

MySQL

### Relevant log output

```shell

```


Discussion:

Patch:
diff --git a/src/Ombi.Core/Rule/Rules/Search/MediaServerAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/MediaServerAvailabilityRule.cs
index 2f8defe42..7bd5067b9 100644
--- a/src/Ombi.Core/Rule/Rules/Search/MediaServerAvailabilityRule.cs
+++ b/src/Ombi.Core/Rule/Rules/Search/MediaServerAvailabilityRule.cs
@@ -166,11 +166,19 @@ protected static async Task<ContentLookupResult> FindContentByProviderIds(
             var result = new ContentLookupResult();
             IMediaServerContent item = null;
 
+            // TheMovieDb (and potentially other providers) use separate ID namespaces for
+            // movies and TV shows, so the same ID can refer to both a movie and a series.
+            // Only accept content whose type matches the thing we are searching for, otherwise
+            // a movie can be wrongly marked as available because a series shares its ID (and vice versa).
+            var expectedType = obj is SearchMovieViewModel ? MediaType.Movie : MediaType.Series;
+            bool Matches(IMediaServerContent content) => content != null && content.Type == expectedType;
+
             if (obj.ImdbId.HasValue())
             {
-                item = await getByImdbId(obj.ImdbId);
-                if (item != null)
+                var match = await getByImdbId(obj.ImdbId);
+                if (Matches(match))
                 {
+                    item = match;
                     result.UseImdb = true;
                 }
             }
@@ -179,9 +187,10 @@ protected static async Task<ContentLookupResult> FindContentByProviderIds(
             {
                 if (lookupById && obj.Id > 0)
                 {
-                    item = await getByTheMovieDbId(obj.Id.ToString());
-                    if (item != null)
+                    var match = await getByTheMovieDbId(obj.Id.ToString());
+                    if (Matches(match))
                     {
+                        item = match;
                         obj.TheMovieDbId = obj.Id.ToString();
                         result.UseTheMovieDb = true;
                     }
@@ -189,18 +198,20 @@ protected static async Task<ContentLookupResult> FindContentByProviderIds(
 
                 if (item == null && obj.TheMovieDbId.HasValue())
                 {
-                    item = await getByTheMovieDbId(obj.TheMovieDbId);
-                    if (item != null)
+                    var match = await getByTheMovieDbId(obj.TheMovieDbId);
+                    if (Matches(match))
                     {
+                        item = match;
                         result.UseTheMovieDb = true;
                     }
                 }
 
                 if (item == null && obj.TheTvDbId.HasValue())
                 {
-                    item = await getByTvDbId(obj.TheTvDbId);
-                    if (item != null)
+                    var match = await getByTvDbId(obj.TheTvDbId);
+                    if (Matches(match))
                     {
+                        item = match;
                         result.UseTvDb = true;
                     }
                 }
diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs
index acb544978..0a8f90230 100644
--- a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs
+++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs
@@ -70,9 +70,9 @@ private async Task ProcessMovies()
                     embyContent = await _repo.GetByImdbId(movie.ImdbId);
                 }
                 
-                if (embyContent == null)
+                if (embyContent == null || embyContent.Type != MediaType.Movie)
                 {
-                    // We don't have this yet
+                    // We don't have this yet (a series may share the same TheMovieDbId, so ensure it's a movie)
                     continue;
                 }
 
diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs
index c29ecac2c..54fb0c9dc 100644
--- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs
+++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs
@@ -97,9 +97,9 @@ private async Task ProcessMovies()
                     jellyfinContent = await _repo.GetByImdbId(movie.ImdbId);
                 }
                 
-                if (jellyfinContent == null)
+                if (jellyfinContent == null || jellyfinContent.Type != MediaType.Movie)
                 {
-                    // We don't have this yet
+                    // We don't have this yet (a series may share the same TheMovieDbId, so ensure it's a movie)
                     continue;
                 }
 


Test patch:
diff --git a/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs
index f56876e48..7f046aca5 100644
--- a/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs
+++ b/src/Ombi.Core.Tests/Rule/Search/JellyfinAvailabilityRuleTests.cs
@@ -117,5 +117,25 @@ public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInJellyfin()
             Assert.True(result.Success);
             Assert.False(search.Available);
         }
+
+        [Test]
+        public async Task Movie_ShouldNotBe_Available_WhenOnlyASeriesSharesTheMovieDbId()
+        {
+            // TheMovieDb has separate ID namespaces for movies and TV, so a series can share the same id.
+            ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new JellyfinContent
+            {
+                TheMovieDbId = "87428",
+                Quality = "1080",
+                Type = MediaType.Series
+            });
+            var search = new SearchMovieViewModel()
+            {
+                TheMovieDbId = "87428",
+            };
+            var result = await Rule.Execute(search);
+
+            Assert.True(result.Success);
+            Assert.False(search.Available);
+        }
     }
 }