fix plex other video library detection (#2679)
This commit is contained in:
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix startup on systems unsupported by NvEncSharp
|
- 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
|
## [25.9.0] - 2025-11-29
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ using ErsatzTV.Core.Interfaces.Repositories;
|
|||||||
using ErsatzTV.Core.Jellyfin;
|
using ErsatzTV.Core.Jellyfin;
|
||||||
using ErsatzTV.Infrastructure.Extensions;
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace ErsatzTV.Infrastructure.Data.Repositories;
|
namespace ErsatzTV.Infrastructure.Data.Repositories;
|
||||||
|
|
||||||
public class MediaSourceRepository(IDbContextFactory<TvContext> dbContextFactory) : IMediaSourceRepository
|
public class MediaSourceRepository(IDbContextFactory<TvContext> dbContextFactory, ILogger<MediaSourceRepository> logger)
|
||||||
|
: IMediaSourceRepository
|
||||||
{
|
{
|
||||||
public async Task<PlexMediaSource> Add(PlexMediaSource plexMediaSource)
|
public async Task<PlexMediaSource> Add(PlexMediaSource plexMediaSource)
|
||||||
{
|
{
|
||||||
@@ -166,23 +168,42 @@ public class MediaSourceRepository(IDbContextFactory<TvContext> dbContextFactory
|
|||||||
dbContext.PlexLibraries.Remove(delete);
|
dbContext.PlexLibraries.Remove(delete);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update library path (for other video metadata)
|
|
||||||
foreach (PlexLibrary incoming in toUpdate)
|
foreach (PlexLibrary incoming in toUpdate)
|
||||||
{
|
{
|
||||||
Option<PlexLibrary> maybeExisting = await dbContext.PlexLibraries
|
Option<PlexLibrary> maybeExisting = await dbContext.PlexLibraries
|
||||||
.Include(l => l.Paths)
|
.Include(l => l.Paths)
|
||||||
.SelectOneAsync(l => l.Key, l => l.Key == incoming.Key, cancellationToken);
|
.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);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return deletedMediaIds;
|
return deletedMediaIds;
|
||||||
|
|||||||
@@ -14,16 +14,10 @@ using Refit;
|
|||||||
|
|
||||||
namespace ErsatzTV.Infrastructure.Plex;
|
namespace ErsatzTV.Infrastructure.Plex;
|
||||||
|
|
||||||
public class PlexServerApiClient : IPlexServerApiClient
|
public class PlexServerApiClient(PlexEtag plexEtag, ILogger<PlexServerApiClient> logger) : IPlexServerApiClient
|
||||||
{
|
{
|
||||||
private readonly ILogger<PlexServerApiClient> _logger;
|
private const string PersonalMediaAgent = "com.plexapp.agents.none";
|
||||||
private readonly PlexEtag _plexEtag;
|
private const string PlexPersonalMediaAgent = "tv.plex.agents.none";
|
||||||
|
|
||||||
public PlexServerApiClient(PlexEtag plexEtag, ILogger<PlexServerApiClient> logger)
|
|
||||||
{
|
|
||||||
_plexEtag = plexEtag;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> Ping(
|
public async Task<bool> Ping(
|
||||||
PlexConnection connection,
|
PlexConnection connection,
|
||||||
@@ -537,7 +531,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
{
|
{
|
||||||
Key = response.Key,
|
Key = response.Key,
|
||||||
Name = response.Title,
|
Name = response.Title,
|
||||||
MediaKind = response.Agent == "com.plexapp.agents.none" && response.Language == "xn"
|
MediaKind = response.Agent is PersonalMediaAgent or PlexPersonalMediaAgent
|
||||||
? LibraryMediaKind.OtherVideos
|
? LibraryMediaKind.OtherVideos
|
||||||
: LibraryMediaKind.Movies,
|
: LibraryMediaKind.Movies,
|
||||||
ShouldSyncItems = false,
|
ShouldSyncItems = false,
|
||||||
@@ -565,13 +559,13 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
return new PlexCollection
|
return new PlexCollection
|
||||||
{
|
{
|
||||||
Key = item.RatingKey,
|
Key = item.RatingKey,
|
||||||
Etag = _plexEtag.ForCollection(item),
|
Etag = plexEtag.ForCollection(item),
|
||||||
Name = item.Title
|
Name = item.Title
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Error projecting Plex collection");
|
logger.LogWarning(ex, "Error projecting Plex collection");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -591,7 +585,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Error projecting Plex collection media item");
|
logger.LogWarning(ex, "Error projecting Plex collection media item");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,7 +604,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Error projecting Plex tag");
|
logger.LogWarning(ex, "Error projecting Plex tag");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -651,7 +645,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
|
|
||||||
var movie = new PlexMovie
|
var movie = new PlexMovie
|
||||||
{
|
{
|
||||||
Etag = _plexEtag.ForMovie(response),
|
Etag = plexEtag.ForMovie(response),
|
||||||
Key = response.Key,
|
Key = response.Key,
|
||||||
MovieMetadata = [metadata],
|
MovieMetadata = [metadata],
|
||||||
MediaVersions = [version],
|
MediaVersions = [version],
|
||||||
@@ -884,7 +878,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
var show = new PlexShow
|
var show = new PlexShow
|
||||||
{
|
{
|
||||||
Key = response.Key,
|
Key = response.Key,
|
||||||
Etag = _plexEtag.ForShow(response),
|
Etag = plexEtag.ForShow(response),
|
||||||
ShowMetadata = new List<ShowMetadata> { metadata },
|
ShowMetadata = new List<ShowMetadata> { metadata },
|
||||||
TraktListItems = new List<TraktListItem>()
|
TraktListItems = new List<TraktListItem>()
|
||||||
};
|
};
|
||||||
@@ -1046,7 +1040,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
var season = new PlexSeason
|
var season = new PlexSeason
|
||||||
{
|
{
|
||||||
Key = response.Key,
|
Key = response.Key,
|
||||||
Etag = _plexEtag.ForSeason(response),
|
Etag = plexEtag.ForSeason(response),
|
||||||
SeasonNumber = response.Index,
|
SeasonNumber = response.Index,
|
||||||
SeasonMetadata = new List<SeasonMetadata> { metadata },
|
SeasonMetadata = new List<SeasonMetadata> { metadata },
|
||||||
TraktListItems = new List<TraktListItem>()
|
TraktListItems = new List<TraktListItem>()
|
||||||
@@ -1092,7 +1086,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
var episode = new PlexEpisode
|
var episode = new PlexEpisode
|
||||||
{
|
{
|
||||||
Key = response.Key,
|
Key = response.Key,
|
||||||
Etag = _plexEtag.ForEpisode(response),
|
Etag = plexEtag.ForEpisode(response),
|
||||||
EpisodeMetadata = [metadata],
|
EpisodeMetadata = [metadata],
|
||||||
MediaVersions = [version],
|
MediaVersions = [version],
|
||||||
TraktListItems = []
|
TraktListItems = []
|
||||||
@@ -1238,7 +1232,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
|
|
||||||
var otherVideo = new PlexOtherVideo
|
var otherVideo = new PlexOtherVideo
|
||||||
{
|
{
|
||||||
Etag = _plexEtag.ForMovie(response),
|
Etag = plexEtag.ForMovie(response),
|
||||||
Key = response.Key,
|
Key = response.Key,
|
||||||
OtherVideoMetadata = [metadata],
|
OtherVideoMetadata = [metadata],
|
||||||
MediaVersions = [version],
|
MediaVersions = [version],
|
||||||
@@ -1431,7 +1425,7 @@ public class PlexServerApiClient : IPlexServerApiClient
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Unsupported guid format from Plex; ignoring: {Guid}", guid);
|
logger.LogWarning("Unsupported guid format from Plex; ignoring: {Guid}", guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
Reference in New Issue
Block a user