1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-10-29 23:12:39 +01:00

New: Episode mappings in .plexmatch metadata files

Closes #5784
This commit is contained in:
Mark McDowall 2024-10-26 14:20:55 -07:00 committed by GitHub
parent 41ddacc395
commit 03b9c957b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 125 additions and 13 deletions

View File

@ -5,6 +5,7 @@ using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
@ -25,13 +26,15 @@ namespace NzbDrone.Core.Extras
IHandle<MediaCoversUpdatedEvent>,
IHandle<EpisodeFolderCreatedEvent>,
IHandle<SeriesScannedEvent>,
IHandle<SeriesRenamedEvent>
IHandle<SeriesRenamedEvent>,
IHandle<DownloadsProcessedEvent>
{
private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService;
private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService;
private readonly List<IManageExtraFiles> _extraFileManagers;
private readonly Dictionary<int, Series> _seriesWithImportedFiles;
public ExtraService(IMediaFileService mediaFileService,
IEpisodeService episodeService,
@ -45,6 +48,7 @@ namespace NzbDrone.Core.Extras
_diskProvider = diskProvider;
_configService = configService;
_extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
_seriesWithImportedFiles = new Dictionary<int, Series>();
}
public void ImportEpisode(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly)
@ -100,6 +104,11 @@ namespace NzbDrone.Core.Extras
private void CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{
lock (_seriesWithImportedFiles)
{
_seriesWithImportedFiles.TryAdd(series.Id, series);
}
foreach (var extraFileManager in _extraFileManagers)
{
extraFileManager.CreateAfterEpisodeImport(series, episodeFile);
@ -161,6 +170,26 @@ namespace NzbDrone.Core.Extras
}
}
public void Handle(DownloadsProcessedEvent message)
{
var allSeries = new List<Series>();
lock (_seriesWithImportedFiles)
{
allSeries.AddRange(_seriesWithImportedFiles.Values);
_seriesWithImportedFiles.Clear();
}
foreach (var series in allSeries)
{
foreach (var extraFileManager in _extraFileManagers)
{
extraFileManager.CreateAfterEpisodesImported(series);
}
}
}
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
{
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);

View File

@ -17,6 +17,7 @@ namespace NzbDrone.Core.Extras.Files
int Order { get; }
IEnumerable<ExtraFile> CreateAfterMediaCoverUpdate(Series series);
IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles);
IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series);
IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
IEnumerable<ExtraFile> CreateAfterEpisodeFolder(Series series, string seriesFolder, string seasonFolder);
IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);
@ -46,6 +47,7 @@ namespace NzbDrone.Core.Extras.Files
public abstract int Order { get; }
public abstract IEnumerable<ExtraFile> CreateAfterMediaCoverUpdate(Series series);
public abstract IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles);
public abstract IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series);
public abstract IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
public abstract IEnumerable<ExtraFile> CreateAfterEpisodeFolder(Series series, string seriesFolder, string seasonFolder);
public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);

View File

@ -92,7 +92,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{
return null;
}

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Metadata.Files;
@ -10,6 +11,15 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
{
public class PlexMetadata : MetadataBase<PlexMetadataSettings>
{
private readonly IEpisodeService _episodeService;
private readonly IMediaFileService _mediaFileService;
public PlexMetadata(IEpisodeService episodeService, IMediaFileService mediaFileService)
{
_episodeService = episodeService;
_mediaFileService = mediaFileService;
}
public override string Name => "Plex";
public override MetadataFile FindMetadataFile(Series series, string path)
@ -37,7 +47,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{
if (!Settings.SeriesPlexMatchFile)
{
@ -51,6 +61,25 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
content.AppendLine($"TvdbId: {series.TvdbId}");
content.AppendLine($"ImdbId: {series.ImdbId}");
if (Settings.EpisodeMappings)
{
var episodes = _episodeService.GetEpisodeBySeries(series.Id);
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
foreach (var episodeFile in episodeFiles)
{
var episodesInFile = episodes.Where(e => e.EpisodeFileId == episodeFile.Id);
var episodeFormat = $"S{episodeFile.SeasonNumber:00}{string.Join("-", episodesInFile.Select(e => $"E{e.EpisodeNumber:00}"))}";
if (episodeFile.SeasonNumber == 0)
{
episodeFormat = $"SP{episodesInFile.First():00}";
}
content.Append($"Episode: {episodeFormat}: {episodeFile.RelativePath}");
}
}
return new MetadataFileResult(".plexmatch", content.ToString());
}

View File

@ -21,6 +21,9 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
[FieldDefinition(0, Label = "MetadataPlexSettingsSeriesPlexMatchFile", Type = FieldType.Checkbox, Section = MetadataSectionType.Metadata, HelpText = "MetadataPlexSettingsSeriesPlexMatchFileHelpText")]
public bool SeriesPlexMatchFile { get; set; }
[FieldDefinition(0, Label = "MetadataPlexSettingsEpisodeMappings", Type = FieldType.Checkbox, Section = MetadataSectionType.Metadata, HelpText = "MetadataPlexSettingsEpisodeMappingsHelpText")]
public bool EpisodeMappings { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -124,7 +124,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{
// Series metadata is not supported
return null;

View File

@ -113,7 +113,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{
// Series metadata is not supported
return null;

View File

@ -137,8 +137,13 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
return null;
}
public override MetadataFileResult SeriesMetadata(Series series)
public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{
if (reason == SeriesMetadataReason.EpisodesImported)
{
return null;
}
var xmlResult = string.Empty;
if (Settings.SeriesMetadata)

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.ThingiProvider;
@ -10,10 +10,17 @@ namespace NzbDrone.Core.Extras.Metadata
{
string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile);
MetadataFile FindMetadataFile(Series series, string path);
MetadataFileResult SeriesMetadata(Series series);
MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason);
MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
List<ImageFileResult> SeriesImages(Series series);
List<ImageFileResult> SeasonImages(Series series, Season season);
List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
}
public enum SeriesMetadataReason
{
Scan,
EpisodeFolderCreated,
EpisodesImported
}
}

View File

@ -38,7 +38,7 @@ namespace NzbDrone.Core.Extras.Metadata
public abstract MetadataFile FindMetadataFile(Series series, string path);
public abstract MetadataFileResult SeriesMetadata(Series series);
public abstract MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason);
public abstract MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
public abstract List<ImageFileResult> SeriesImages(Series series);
public abstract List<ImageFileResult> SeasonImages(Series series, Season season);

View File

@ -99,7 +99,7 @@ namespace NzbDrone.Core.Extras.Metadata
{
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles, SeriesMetadataReason.Scan));
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
@ -115,6 +115,31 @@ namespace NzbDrone.Core.Extras.Metadata
return files;
}
public override IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series)
{
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
_cleanMetadataService.Clean(series);
if (!_diskProvider.FolderExists(series.Path))
{
_logger.Info("Series folder does not exist, skipping metadata creation");
return Enumerable.Empty<MetadataFile>();
}
var files = new List<MetadataFile>();
foreach (var consumer in _metadataFactory.Enabled())
{
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles);
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles, SeriesMetadataReason.EpisodesImported));
}
_metadataFileService.Upsert(files);
return files;
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{
var files = new List<MetadataFile>();
@ -147,7 +172,7 @@ namespace NzbDrone.Core.Extras.Metadata
if (seriesFolder.IsNotNullOrWhiteSpace())
{
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles));
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles, SeriesMetadataReason.EpisodeFolderCreated));
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles));
}
@ -218,9 +243,9 @@ namespace NzbDrone.Core.Extras.Metadata
return seriesMetadata.Where(c => c.Consumer == consumer.GetType().Name).ToList();
}
private MetadataFile ProcessSeriesMetadata(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles)
private MetadataFile ProcessSeriesMetadata(IMetadata consumer, Series series, List<MetadataFile> existingMetadataFiles, SeriesMetadataReason reason)
{
var seriesMetadata = consumer.SeriesMetadata(series);
var seriesMetadata = consumer.SeriesMetadata(series, reason);
if (seriesMetadata == null)
{

View File

@ -46,6 +46,11 @@ namespace NzbDrone.Core.Extras.Others
return Enumerable.Empty<ExtraFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series)
{
return Enumerable.Empty<ExtraFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{
return Enumerable.Empty<ExtraFile>();

View File

@ -53,6 +53,11 @@ namespace NzbDrone.Core.Extras.Subtitles
return Enumerable.Empty<SubtitleFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series)
{
return Enumerable.Empty<SubtitleFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{
return Enumerable.Empty<SubtitleFile>();

View File

@ -1151,6 +1151,8 @@
"Message": "Message",
"Metadata": "Metadata",
"MetadataLoadError": "Unable to load Metadata",
"MetadataPlexSettingsEpisodeMappings": "Episode Mappings",
"MetadataPlexSettingsEpisodeMappingsHelpText": "Include episode mappings for all files in .plexmatch file",
"MetadataPlexSettingsSeriesPlexMatchFile": "Series Plex Match File",
"MetadataPlexSettingsSeriesPlexMatchFileHelpText": "Creates a .plexmatch file in the series folder",
"MetadataProvidedBy": "Metadata is provided by {provider}",