private async Task <Either <BaseError, MediaItemScanResult <PlexEpisode> > > UpdateMetadata( MediaItemScanResult <PlexEpisode> result, PlexEpisode incoming) { PlexEpisode existing = result.Item; var toUpdate = existing.EpisodeMetadata .Where(em => incoming.EpisodeMetadata.Any(em2 => em2.EpisodeNumber == em.EpisodeNumber)) .ToList(); var toRemove = existing.EpisodeMetadata.Except(toUpdate).ToList(); var toAdd = incoming.EpisodeMetadata .Where(em => existing.EpisodeMetadata.All(em2 => em2.EpisodeNumber != em.EpisodeNumber)) .ToList(); foreach (EpisodeMetadata metadata in toRemove) { await _televisionRepository.RemoveMetadata(existing, metadata); } foreach (EpisodeMetadata metadata in toAdd) { metadata.EpisodeId = existing.Id; metadata.Episode = existing; existing.EpisodeMetadata.Add(metadata); await _metadataRepository.Add(metadata); } // TODO: update existing metadata return(result); }
private async Task <string> GetPlayoutItemPath(PlayoutItem playoutItem) { MediaVersion version = playoutItem.MediaItem.GetHeadVersion(); MediaFile file = version.MediaFiles.Head(); string path = file.Path; return(playoutItem.MediaItem switch { PlexMovie plexMovie => await _plexPathReplacementService.GetReplacementPlexPath( plexMovie.LibraryPathId, path), PlexEpisode plexEpisode => await _plexPathReplacementService.GetReplacementPlexPath( plexEpisode.LibraryPathId, path), JellyfinMovie jellyfinMovie => await _jellyfinPathReplacementService.GetReplacementJellyfinPath( jellyfinMovie.LibraryPathId, path), JellyfinEpisode jellyfinEpisode => await _jellyfinPathReplacementService.GetReplacementJellyfinPath( jellyfinEpisode.LibraryPathId, path), EmbyMovie embyMovie => await _embyPathReplacementService.GetReplacementEmbyPath( embyMovie.LibraryPathId, path), EmbyEpisode embyEpisode => await _embyPathReplacementService.GetReplacementEmbyPath( embyEpisode.LibraryPathId, path), _ => path });
private async Task <Either <BaseError, PlexEpisode> > UpdateStatistics( PlexEpisode existing, PlexEpisode incoming, PlexConnection connection, PlexServerAuthToken token) { MediaVersion existingVersion = existing.MediaVersions.Head(); MediaVersion incomingVersion = incoming.MediaVersions.Head(); if (incomingVersion.DateUpdated > existingVersion.DateUpdated || string.IsNullOrWhiteSpace(existingVersion.SampleAspectRatio)) { Either <BaseError, MediaVersion> maybeStatistics = await _plexServerApiClient.GetStatistics(incoming.Key.Split("/").Last(), connection, token); await maybeStatistics.Match( async mediaVersion => { existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio ?? "1:1"; existingVersion.VideoScanKind = mediaVersion.VideoScanKind; existingVersion.DateUpdated = incomingVersion.DateUpdated; await _metadataRepository.UpdatePlexStatistics(existingVersion); }, _ => Task.CompletedTask); } return(Right <BaseError, PlexEpisode>(existing)); }
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable <PlexEpisode> allEpisodes, EpisodeRequests episode, SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb) { PlexEpisode epExists = null; if (useImdb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.ImdbId == item.ImdbId.ToString()); } if (useTheMovieDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.TheMovieDbId == item.TheMovieDbId.ToString()); } if (useTvDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.TvDbId == item.TvDbId.ToString()); } if (epExists != null) { episode.Available = true; } }
public async Task <PlexEpisode> Add(PlexEpisode content) { await Db.PlexEpisode.AddAsync(content); await InternalSaveChanges(); return(content); }
private async Task <bool> ShouldScanItem( PlexLibrary library, List <PlexPathReplacement> pathReplacements, List <PlexItemEtag> existingEpisodes, PlexEpisode incoming, bool deepScan) { // deep scan will pull every episode individually from the plex api if (!deepScan) { Option <PlexItemEtag> maybeExisting = existingEpisodes.Find(ie => ie.Key == incoming.Key); string existingTag = await maybeExisting .Map(e => e.Etag ?? string.Empty) .IfNoneAsync(string.Empty); MediaItemState existingState = await maybeExisting .Map(e => e.State) .IfNoneAsync(MediaItemState.Normal); string plexPath = incoming.MediaVersions.Head().MediaFiles.Head().Path; string localPath = _plexPathReplacementService.GetReplacementPlexPath( pathReplacements, plexPath, false); // if media is unavailable, only scan if file now exists if (existingState == MediaItemState.Unavailable) { if (!_localFileSystem.FileExists(localPath)) { return(false); } } else if (existingTag == incoming.Etag) { if (!_localFileSystem.FileExists(localPath)) { foreach (int id in await _plexTelevisionRepository.FlagUnavailable(library, incoming)) { await _searchIndex.RebuildItems(_searchRepository, new List <int> { id }); } } // _logger.LogDebug("NOOP: etag has not changed for plex episode with key {Key}", incoming.Key); return(false); } // _logger.LogDebug( // "UPDATE: Etag has changed for episode {Episode}", // $"s{season.SeasonNumber}e{incoming.EpisodeMetadata.Head().EpisodeNumber}"); } return(true); }
private async Task <Either <BaseError, PlexEpisode> > UpdateArtwork(PlexEpisode existing, PlexEpisode incoming) { EpisodeMetadata existingMetadata = existing.EpisodeMetadata.Head(); EpisodeMetadata incomingMetadata = incoming.EpisodeMetadata.Head(); if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) { await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Thumbnail); } return(existing); }
private async Task <string> GetPlayoutItemPath(PlayoutItem playoutItem) { MediaVersion version = playoutItem.MediaItem switch { Movie m => m.MediaVersions.Head(), Episode e => e.MediaVersions.Head(), _ => throw new ArgumentOutOfRangeException(nameof(playoutItem)) }; MediaFile file = version.MediaFiles.Head(); string path = file.Path; return(playoutItem.MediaItem switch { PlexMovie plexMovie => await GetReplacementPlexPath(plexMovie.LibraryPathId, path), PlexEpisode plexEpisode => await GetReplacementPlexPath(plexEpisode.LibraryPathId, path), _ => path });
private async Task <Either <BaseError, MediaItemScanResult <PlexEpisode> > > UpdateArtwork( MediaItemScanResult <PlexEpisode> result, PlexEpisode incoming) { PlexEpisode existing = result.Item; foreach (EpisodeMetadata incomingMetadata in incoming.EpisodeMetadata) { Option <EpisodeMetadata> maybeExistingMetadata = existing.EpisodeMetadata .Find(em => em.EpisodeNumber == incomingMetadata.EpisodeNumber); if (maybeExistingMetadata.IsSome) { EpisodeMetadata existingMetadata = maybeExistingMetadata.ValueUnsafe(); await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Thumbnail); await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated); } } return(result); }
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable <PlexEpisode> allEpisodes, EpisodeRequests episode, SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log) { PlexEpisode epExists = null; try { if (useImdb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.ImdbId == item.ImdbId); } if (useTheMovieDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.TheMovieDbId == item.TheMovieDbId); } if (useTvDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.TvDbId == item.TvDbId); } } catch (Exception e) { log.LogError(e, "Exception thrown when attempting to check if something is available"); } if (epExists != null) { episode.Available = true; } }
private async Task <Either <BaseError, MediaItemScanResult <PlexEpisode> > > UpdateSubtitles( List <PlexPathReplacement> pathReplacements, MediaItemScanResult <PlexEpisode> result, PlexEpisode incoming) { try { PlexEpisode existing = result.Item; string localPath = _plexPathReplacementService.GetReplacementPlexPath( pathReplacements, incoming.MediaVersions.Head().MediaFiles.Head().Path, false); await _localSubtitlesProvider.UpdateSubtitles(existing, localPath, false); return(result); } catch (Exception ex) { return(BaseError.New(ex.ToString())); } }
public async Task DeleteEpisode(PlexEpisode content) { Db.PlexEpisode.Remove(content); await InternalSaveChanges(); }
private async Task <Either <BaseError, MediaItemScanResult <PlexEpisode> > > UpdateStatistics( List <PlexPathReplacement> pathReplacements, MediaItemScanResult <PlexEpisode> result, PlexEpisode incoming, PlexLibrary library, PlexConnection connection, PlexServerAuthToken token, string ffmpegPath, string ffprobePath, bool deepScan) { PlexEpisode existing = result.Item; MediaVersion existingVersion = existing.MediaVersions.Head(); MediaVersion incomingVersion = incoming.MediaVersions.Head(); if (result.IsAdded || existing.Etag != incoming.Etag || deepScan || existingVersion.Streams.Count == 0) { foreach (MediaFile incomingFile in incomingVersion.MediaFiles.HeadOrNone()) { foreach (MediaFile existingFile in existingVersion.MediaFiles.HeadOrNone()) { if (incomingFile.Path != existingFile.Path) { _logger.LogDebug( "Plex episode has moved from {OldPath} to {NewPath}", existingFile.Path, incomingFile.Path); existingFile.Path = incomingFile.Path; await _televisionRepository.UpdatePath(existingFile.Id, incomingFile.Path); } } } Either <BaseError, bool> refreshResult = true; string localPath = _plexPathReplacementService.GetReplacementPlexPath( pathReplacements, incoming.MediaVersions.Head().MediaFiles.Head().Path, false); if ((existing.Etag != incoming.Etag || existingVersion.Streams.Count == 0) && _localFileSystem.FileExists(localPath)) { _logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath); refreshResult = await _localStatisticsProvider.RefreshStatistics( ffmpegPath, ffprobePath, existing, localPath); } foreach (BaseError error in refreshResult.LeftToSeq()) { _logger.LogWarning( "Unable to refresh {Attribute} for media item {Path}. Error: {Error}", "Statistics", localPath, error.Value); } foreach (var _ in refreshResult.RightToSeq()) { foreach (MediaItem updated in await _searchRepository.GetItemToIndex(incoming.Id)) { await _searchIndex.UpdateItems( _searchRepository, new List <MediaItem> { updated }); } Either <BaseError, Tuple <EpisodeMetadata, MediaVersion> > maybeStatistics = await _plexServerApiClient.GetEpisodeMetadataAndStatistics( library, incoming.Key.Split("/").Last(), connection, token); foreach (Tuple <EpisodeMetadata, MediaVersion> tuple in maybeStatistics.RightToSeq()) { (EpisodeMetadata incomingMetadata, MediaVersion mediaVersion) = tuple; Option <EpisodeMetadata> maybeExisting = existing.EpisodeMetadata .Find(em => em.EpisodeNumber == incomingMetadata.EpisodeNumber); foreach (EpisodeMetadata existingMetadata in maybeExisting) { foreach (MetadataGuid guid in existingMetadata.Guids .Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { existingMetadata.Guids.Remove(guid); await _metadataRepository.RemoveGuid(guid); } foreach (MetadataGuid guid in incomingMetadata.Guids .Filter(g => existingMetadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { existingMetadata.Guids.Add(guid); await _metadataRepository.AddGuid(existingMetadata, guid); } foreach (Tag tag in existingMetadata.Tags .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Tags.Remove(tag); await _metadataRepository.RemoveTag(tag); } foreach (Tag tag in incomingMetadata.Tags .Filter(g => existingMetadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Tags.Add(tag); await _televisionRepository.AddTag(existingMetadata, tag); } } existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio; existingVersion.VideoScanKind = mediaVersion.VideoScanKind; existingVersion.DateUpdated = mediaVersion.DateUpdated; await _metadataRepository.UpdatePlexStatistics(existingVersion.Id, mediaVersion); } } } return(result); }
public void DeleteWithoutSave(PlexEpisode content) { Db.PlexEpisode.Remove(content); }
public Task <Either <BaseError, MediaItemScanResult <PlexEpisode> > > GetOrAddPlexEpisode( PlexLibrary library, PlexEpisode item) => throw new NotSupportedException();
public Task <Unit> SetPlexEtag(PlexEpisode episode, string etag) => throw new NotSupportedException();
public async Task <RuleResult> Execute(SearchViewModel obj) { PlexServerContent item = null; var useImdb = false; var useTheMovieDb = false; var useTvDb = false; if (obj.ImdbId.HasValue()) { item = await PlexContentRepository.Get(obj.ImdbId); if (item != null) { useImdb = true; } } if (item == null) { if (obj.TheMovieDbId.HasValue()) { item = await PlexContentRepository.Get(obj.TheMovieDbId); if (item != null) { useTheMovieDb = true; } } if (item == null) { if (obj.TheTvDbId.HasValue()) { item = await PlexContentRepository.Get(obj.TheTvDbId); if (item != null) { useTvDb = true; } } } } if (item != null) { obj.Available = true; obj.PlexUrl = item.Url; obj.Quality = item.Quality; if (obj.Type == RequestType.TvShow) { var search = (SearchTvShowViewModel)obj; // Let's go through the episodes now if (search.SeasonRequests.Any()) { var allEpisodes = PlexContentRepository.GetAllEpisodes(); foreach (var season in search.SeasonRequests) { foreach (var episode in season.Episodes) { PlexEpisode epExists = null; if (useImdb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.ImdbId == item.ImdbId.ToString()); } if (useTheMovieDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.TheMovieDbId == item.TheMovieDbId.ToString()); } if (useTvDb) { epExists = await allEpisodes.FirstOrDefaultAsync(x => x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.TvDbId == item.TvDbId.ToString()); } if (epExists != null) { episode.Available = true; } } } if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available))) { search.FullyAvailable = true; } } } } return(Success()); }
public async Task <Either <BaseError, PlexEpisode> > GetOrAddPlexEpisode(PlexLibrary library, PlexEpisode item) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Option <PlexEpisode> maybeExisting = await dbContext.PlexEpisodes .AsNoTracking() .Include(i => i.EpisodeMetadata) .ThenInclude(mm => mm.Artwork) .Include(i => i.MediaVersions) .ThenInclude(mv => mv.MediaFiles) .OrderBy(i => i.Key) .SingleOrDefaultAsync(i => i.Key == item.Key); return(await maybeExisting.Match( plexEpisode => Right <BaseError, PlexEpisode>(plexEpisode).AsTask(), async() => await AddPlexEpisode(dbContext, library, item))); }
public async Task DeleteEpisode(PlexEpisode content) { Db.PlexEpisode.Remove(content); await Db.SaveChangesAsync(); }