From 1f31beab5b890d23be38936a405eb788e40fd2cb Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:35:04 -0600 Subject: [PATCH] fix plex other video library detection (#2679) --- CHANGELOG.md | 3 ++ .../Repositories/MediaSourceRepository.cs | 33 ++++++++++++++---- .../Plex/PlexServerApiClient.cs | 34 ++++++++----------- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f47548082..108b8c467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed - Fix startup on systems unsupported by NvEncSharp +- Fix detection of Plex Other Video libraries using `Plex Personal Media` agent + - If the library is already detected as a Movies library in ETV, synchronization must be disabled for the library to change it to an Other Videos library + - A warning will be logged when this scenario is detected ## [25.9.0] - 2025-11-29 ### Added diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs index afc12068e..7903c2bf1 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs @@ -6,10 +6,12 @@ using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Jellyfin; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace ErsatzTV.Infrastructure.Data.Repositories; -public class MediaSourceRepository(IDbContextFactory dbContextFactory) : IMediaSourceRepository +public class MediaSourceRepository(IDbContextFactory dbContextFactory, ILogger logger) + : IMediaSourceRepository { public async Task Add(PlexMediaSource plexMediaSource) { @@ -166,23 +168,42 @@ public class MediaSourceRepository(IDbContextFactory dbContextFactory dbContext.PlexLibraries.Remove(delete); } - // update library path (for other video metadata) foreach (PlexLibrary incoming in toUpdate) { Option maybeExisting = await dbContext.PlexLibraries .Include(l => l.Paths) .SelectOneAsync(l => l.Key, l => l.Key == incoming.Key, cancellationToken); - foreach (LibraryPath existing in maybeExisting.Map(l => l.Paths.HeadOrNone())) + foreach (PlexLibrary existingLibrary in maybeExisting) { - foreach (LibraryPath path in incoming.Paths.HeadOrNone()) + // update library type, but only if not synchronized + if (incoming.MediaKind != existingLibrary.MediaKind) { - existing.Path = path.Path; + if (existingLibrary.ShouldSyncItems) + { + logger.LogWarning( + "Plex library \"{Name}\" should be type {NewType} (currently {OldType}) but cannot be updated while synchronization is enabled for this library.", + incoming.Name, + incoming.MediaKind, + existingLibrary.MediaKind); + } + else + { + existingLibrary.MediaKind = incoming.MediaKind; + } + } + + // update library path (for other video metadata) + foreach (LibraryPath existing in existingLibrary.Paths.HeadOrNone()) + { + foreach (LibraryPath path in incoming.Paths.HeadOrNone()) + { + existing.Path = path.Path; + } } } } - await dbContext.SaveChangesAsync(cancellationToken); return deletedMediaIds; diff --git a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs index 8314f9159..47dd0e318 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs @@ -14,16 +14,10 @@ using Refit; namespace ErsatzTV.Infrastructure.Plex; -public class PlexServerApiClient : IPlexServerApiClient +public class PlexServerApiClient(PlexEtag plexEtag, ILogger logger) : IPlexServerApiClient { - private readonly ILogger _logger; - private readonly PlexEtag _plexEtag; - - public PlexServerApiClient(PlexEtag plexEtag, ILogger logger) - { - _plexEtag = plexEtag; - _logger = logger; - } + private const string PersonalMediaAgent = "com.plexapp.agents.none"; + private const string PlexPersonalMediaAgent = "tv.plex.agents.none"; public async Task Ping( PlexConnection connection, @@ -537,7 +531,7 @@ public class PlexServerApiClient : IPlexServerApiClient { Key = response.Key, Name = response.Title, - MediaKind = response.Agent == "com.plexapp.agents.none" && response.Language == "xn" + MediaKind = response.Agent is PersonalMediaAgent or PlexPersonalMediaAgent ? LibraryMediaKind.OtherVideos : LibraryMediaKind.Movies, ShouldSyncItems = false, @@ -565,13 +559,13 @@ public class PlexServerApiClient : IPlexServerApiClient return new PlexCollection { Key = item.RatingKey, - Etag = _plexEtag.ForCollection(item), + Etag = plexEtag.ForCollection(item), Name = item.Title }; } catch (Exception ex) { - _logger.LogWarning(ex, "Error projecting Plex collection"); + logger.LogWarning(ex, "Error projecting Plex collection"); return None; } } @@ -591,7 +585,7 @@ public class PlexServerApiClient : IPlexServerApiClient } catch (Exception ex) { - _logger.LogWarning(ex, "Error projecting Plex collection media item"); + logger.LogWarning(ex, "Error projecting Plex collection media item"); return None; } } @@ -610,7 +604,7 @@ public class PlexServerApiClient : IPlexServerApiClient } catch (Exception ex) { - _logger.LogWarning(ex, "Error projecting Plex tag"); + logger.LogWarning(ex, "Error projecting Plex tag"); return None; } } @@ -651,7 +645,7 @@ public class PlexServerApiClient : IPlexServerApiClient var movie = new PlexMovie { - Etag = _plexEtag.ForMovie(response), + Etag = plexEtag.ForMovie(response), Key = response.Key, MovieMetadata = [metadata], MediaVersions = [version], @@ -884,7 +878,7 @@ public class PlexServerApiClient : IPlexServerApiClient var show = new PlexShow { Key = response.Key, - Etag = _plexEtag.ForShow(response), + Etag = plexEtag.ForShow(response), ShowMetadata = new List { metadata }, TraktListItems = new List() }; @@ -1046,7 +1040,7 @@ public class PlexServerApiClient : IPlexServerApiClient var season = new PlexSeason { Key = response.Key, - Etag = _plexEtag.ForSeason(response), + Etag = plexEtag.ForSeason(response), SeasonNumber = response.Index, SeasonMetadata = new List { metadata }, TraktListItems = new List() @@ -1092,7 +1086,7 @@ public class PlexServerApiClient : IPlexServerApiClient var episode = new PlexEpisode { Key = response.Key, - Etag = _plexEtag.ForEpisode(response), + Etag = plexEtag.ForEpisode(response), EpisodeMetadata = [metadata], MediaVersions = [version], TraktListItems = [] @@ -1238,7 +1232,7 @@ public class PlexServerApiClient : IPlexServerApiClient var otherVideo = new PlexOtherVideo { - Etag = _plexEtag.ForMovie(response), + Etag = plexEtag.ForMovie(response), Key = response.Key, OtherVideoMetadata = [metadata], MediaVersions = [version], @@ -1431,7 +1425,7 @@ public class PlexServerApiClient : IPlexServerApiClient } else { - _logger.LogWarning("Unsupported guid format from Plex; ignoring: {Guid}", guid); + logger.LogWarning("Unsupported guid format from Plex; ignoring: {Guid}", guid); } return None;