1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-19 21:46:43 -04: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
14 changed files with 125 additions and 13 deletions
+30 -1
View File
@@ -5,6 +5,7 @@ using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Extras.Files; using NzbDrone.Core.Extras.Files;
using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
@@ -25,13 +26,15 @@ namespace NzbDrone.Core.Extras
IHandle<MediaCoversUpdatedEvent>, IHandle<MediaCoversUpdatedEvent>,
IHandle<EpisodeFolderCreatedEvent>, IHandle<EpisodeFolderCreatedEvent>,
IHandle<SeriesScannedEvent>, IHandle<SeriesScannedEvent>,
IHandle<SeriesRenamedEvent> IHandle<SeriesRenamedEvent>,
IHandle<DownloadsProcessedEvent>
{ {
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly List<IManageExtraFiles> _extraFileManagers; private readonly List<IManageExtraFiles> _extraFileManagers;
private readonly Dictionary<int, Series> _seriesWithImportedFiles;
public ExtraService(IMediaFileService mediaFileService, public ExtraService(IMediaFileService mediaFileService,
IEpisodeService episodeService, IEpisodeService episodeService,
@@ -45,6 +48,7 @@ namespace NzbDrone.Core.Extras
_diskProvider = diskProvider; _diskProvider = diskProvider;
_configService = configService; _configService = configService;
_extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList(); _extraFileManagers = extraFileManagers.OrderBy(e => e.Order).ToList();
_seriesWithImportedFiles = new Dictionary<int, Series>();
} }
public void ImportEpisode(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly) public void ImportEpisode(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly)
@@ -100,6 +104,11 @@ namespace NzbDrone.Core.Extras
private void CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile) private void CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{ {
lock (_seriesWithImportedFiles)
{
_seriesWithImportedFiles.TryAdd(series.Id, series);
}
foreach (var extraFileManager in _extraFileManagers) foreach (var extraFileManager in _extraFileManagers)
{ {
extraFileManager.CreateAfterEpisodeImport(series, episodeFile); 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) private List<EpisodeFile> GetEpisodeFiles(int seriesId)
{ {
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId); var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Extras.Files
int Order { get; } int Order { get; }
IEnumerable<ExtraFile> CreateAfterMediaCoverUpdate(Series series); IEnumerable<ExtraFile> CreateAfterMediaCoverUpdate(Series series);
IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles); IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles);
IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series);
IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile); IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
IEnumerable<ExtraFile> CreateAfterEpisodeFolder(Series series, string seriesFolder, string seasonFolder); IEnumerable<ExtraFile> CreateAfterEpisodeFolder(Series series, string seriesFolder, string seasonFolder);
IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles); IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);
@@ -46,6 +47,7 @@ namespace NzbDrone.Core.Extras.Files
public abstract int Order { get; } public abstract int Order { get; }
public abstract IEnumerable<ExtraFile> CreateAfterMediaCoverUpdate(Series series); public abstract IEnumerable<ExtraFile> CreateAfterMediaCoverUpdate(Series series);
public abstract IEnumerable<ExtraFile> CreateAfterSeriesScan(Series series, List<EpisodeFile> episodeFiles); 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> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
public abstract IEnumerable<ExtraFile> CreateAfterEpisodeFolder(Series series, string seriesFolder, string seasonFolder); public abstract IEnumerable<ExtraFile> CreateAfterEpisodeFolder(Series series, string seriesFolder, string seasonFolder);
public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles); public abstract IEnumerable<ExtraFile> MoveFilesAfterRename(Series series, List<EpisodeFile> episodeFiles);
@@ -92,7 +92,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Kometa
return null; return null;
} }
public override MetadataFileResult SeriesMetadata(Series series) public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{ {
return null; return null;
} }
@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Extras.Metadata.Files; using NzbDrone.Core.Extras.Metadata.Files;
@@ -10,6 +11,15 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
{ {
public class PlexMetadata : MetadataBase<PlexMetadataSettings> 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 string Name => "Plex";
public override MetadataFile FindMetadataFile(Series series, string path) public override MetadataFile FindMetadataFile(Series series, string path)
@@ -37,7 +47,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
return null; return null;
} }
public override MetadataFileResult SeriesMetadata(Series series) public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{ {
if (!Settings.SeriesPlexMatchFile) if (!Settings.SeriesPlexMatchFile)
{ {
@@ -51,6 +61,25 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
content.AppendLine($"TvdbId: {series.TvdbId}"); content.AppendLine($"TvdbId: {series.TvdbId}");
content.AppendLine($"ImdbId: {series.ImdbId}"); 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()); return new MetadataFileResult(".plexmatch", content.ToString());
} }
@@ -21,6 +21,9 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Plex
[FieldDefinition(0, Label = "MetadataPlexSettingsSeriesPlexMatchFile", Type = FieldType.Checkbox, Section = MetadataSectionType.Metadata, HelpText = "MetadataPlexSettingsSeriesPlexMatchFileHelpText")] [FieldDefinition(0, Label = "MetadataPlexSettingsSeriesPlexMatchFile", Type = FieldType.Checkbox, Section = MetadataSectionType.Metadata, HelpText = "MetadataPlexSettingsSeriesPlexMatchFileHelpText")]
public bool SeriesPlexMatchFile { get; set; } 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() public NzbDroneValidationResult Validate()
{ {
return new NzbDroneValidationResult(Validator.Validate(this)); return new NzbDroneValidationResult(Validator.Validate(this));
@@ -124,7 +124,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
return null; return null;
} }
public override MetadataFileResult SeriesMetadata(Series series) public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{ {
// Series metadata is not supported // Series metadata is not supported
return null; return null;
@@ -113,7 +113,7 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Wdtv
return null; return null;
} }
public override MetadataFileResult SeriesMetadata(Series series) public override MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason)
{ {
// Series metadata is not supported // Series metadata is not supported
return null; return null;
@@ -137,8 +137,13 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
return null; 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; var xmlResult = string.Empty;
if (Settings.SeriesMetadata) if (Settings.SeriesMetadata)
@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Extras.Metadata.Files; using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@@ -10,10 +10,17 @@ namespace NzbDrone.Core.Extras.Metadata
{ {
string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile); string GetFilenameAfterMove(Series series, EpisodeFile episodeFile, MetadataFile metadataFile);
MetadataFile FindMetadataFile(Series series, string path); MetadataFile FindMetadataFile(Series series, string path);
MetadataFileResult SeriesMetadata(Series series); MetadataFileResult SeriesMetadata(Series series, SeriesMetadataReason reason);
MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile); MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
List<ImageFileResult> SeriesImages(Series series); List<ImageFileResult> SeriesImages(Series series);
List<ImageFileResult> SeasonImages(Series series, Season season); List<ImageFileResult> SeasonImages(Series series, Season season);
List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile); List<ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
} }
public enum SeriesMetadataReason
{
Scan,
EpisodeFolderCreated,
EpisodesImported
}
} }
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Extras.Metadata
public abstract MetadataFile FindMetadataFile(Series series, string path); 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 MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
public abstract List<ImageFileResult> SeriesImages(Series series); public abstract List<ImageFileResult> SeriesImages(Series series);
public abstract List<ImageFileResult> SeasonImages(Series series, Season season); public abstract List<ImageFileResult> SeasonImages(Series series, Season season);
@@ -99,7 +99,7 @@ namespace NzbDrone.Core.Extras.Metadata
{ {
var consumerFiles = GetMetadataFilesForConsumer(consumer, metadataFiles); 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(ProcessSeriesImages(consumer, series, consumerFiles));
files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles)); files.AddRange(ProcessSeasonImages(consumer, series, consumerFiles));
@@ -115,6 +115,31 @@ namespace NzbDrone.Core.Extras.Metadata
return files; 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) public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{ {
var files = new List<MetadataFile>(); var files = new List<MetadataFile>();
@@ -147,7 +172,7 @@ namespace NzbDrone.Core.Extras.Metadata
if (seriesFolder.IsNotNullOrWhiteSpace()) if (seriesFolder.IsNotNullOrWhiteSpace())
{ {
files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles)); files.AddIfNotNull(ProcessSeriesMetadata(consumer, series, consumerFiles, SeriesMetadataReason.EpisodeFolderCreated));
files.AddRange(ProcessSeriesImages(consumer, series, consumerFiles)); 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(); 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) if (seriesMetadata == null)
{ {
@@ -46,6 +46,11 @@ namespace NzbDrone.Core.Extras.Others
return Enumerable.Empty<ExtraFile>(); return Enumerable.Empty<ExtraFile>();
} }
public override IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series)
{
return Enumerable.Empty<ExtraFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile) public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{ {
return Enumerable.Empty<ExtraFile>(); return Enumerable.Empty<ExtraFile>();
@@ -53,6 +53,11 @@ namespace NzbDrone.Core.Extras.Subtitles
return Enumerable.Empty<SubtitleFile>(); return Enumerable.Empty<SubtitleFile>();
} }
public override IEnumerable<ExtraFile> CreateAfterEpisodesImported(Series series)
{
return Enumerable.Empty<SubtitleFile>();
}
public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile) public override IEnumerable<ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile)
{ {
return Enumerable.Empty<SubtitleFile>(); return Enumerable.Empty<SubtitleFile>();
@@ -1151,6 +1151,8 @@
"Message": "Message", "Message": "Message",
"Metadata": "Metadata", "Metadata": "Metadata",
"MetadataLoadError": "Unable to load Metadata", "MetadataLoadError": "Unable to load Metadata",
"MetadataPlexSettingsEpisodeMappings": "Episode Mappings",
"MetadataPlexSettingsEpisodeMappingsHelpText": "Include episode mappings for all files in .plexmatch file",
"MetadataPlexSettingsSeriesPlexMatchFile": "Series Plex Match File", "MetadataPlexSettingsSeriesPlexMatchFile": "Series Plex Match File",
"MetadataPlexSettingsSeriesPlexMatchFileHelpText": "Creates a .plexmatch file in the series folder", "MetadataPlexSettingsSeriesPlexMatchFileHelpText": "Creates a .plexmatch file in the series folder",
"MetadataProvidedBy": "Metadata is provided by {provider}", "MetadataProvidedBy": "Metadata is provided by {provider}",