diff options
| author | mysticalsoap <github@esotericsuds.com> | 2026-09-15 16:31:24 -0400 |
|---|---|---|
| committer | mysticalsoap <github@esotericsuds.com> | 2026-10-15 17:35:24 -0400 |
| commit | a92a574be09dc415198ed7e5775ffd9ef2b44a58 (patch) | |
| tree | a517b698ebb8b3a820576a47789565b7907f09f0 | |
| parent | 2416fb56b4904fcf1fbf561be0c05b37d2d0886f (diff) | |
Keep container creation time out of the premiere date chain
6 files changed, 147 insertions, 31 deletions
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c9d618c84a..6cf4d22581 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -170,8 +170,10 @@ namespace MediaBrowser.MediaEncoding.Probing FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? FFProbeHelpers.GetDictionaryDateTime(tags, "date_released") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "date") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "creation_time"); + FFProbeHelpers.GetDictionaryDateTime(tags, "date"); + + // creation_time is when the file was written, not a release date + info.ContainerCreationDate = FFProbeHelpers.GetDictionaryDateTime(tags, "creation_time"); // Set common metadata for music (audio) and music videos (video) info.Album = tags.GetValueOrDefault("album"); diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs index 453aeb028a..2e67daba63 100644 --- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs +++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs @@ -61,6 +61,8 @@ namespace MediaBrowser.Model.MediaInfo public DateTime? PremiereDate { get; set; } + public DateTime? ContainerCreationDate { get; set; } + public BaseItemPerson[] People { get; set; } public Dictionary<string, string> ProviderIds { get; set; } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 81d4b640a3..2b265d8b76 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -188,7 +188,7 @@ namespace MediaBrowser.Providers.MediaInfo // To properly handle fallback values, we make a clone of those fields when valid. var trackTitle = (string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title)?.Trim(); var trackAlbum = (string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album)?.Trim(); - var trackYear = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year; + var trackYear = track.Year is null or 0 ? mediaInfo.ProductionYear ?? mediaInfo.ContainerCreationDate?.Year : track.Year; var trackTrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber; var trackDiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 97bc316f00..b093d428e6 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -434,12 +434,7 @@ namespace MediaBrowser.Providers.MediaInfo // Extras have no release date of their own, they inherit it from the item they belong to. var useContainerDates = video.ExtraType is null; - - // creation_time is the mux time, so ignore container dates that contradict a known year. - var containerYear = data.PremiereDate?.Year ?? data.ProductionYear; - var containerDatesAgree = video.ProductionYear is null || containerYear == video.ProductionYear; - - if (useContainerDates && containerDatesAgree && data.ProductionYear is not null) + if (useContainerDates && data.ProductionYear is not null) { if (video.ProductionYear is null || replaceData) { @@ -447,11 +442,20 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (useContainerDates && containerDatesAgree && data.PremiereDate is not null) + if (useContainerDates) { - if (video.PremiereDate is null || replaceData) + // creation_time is the mux time, so it only stands in where the tags gave no date + // and it cannot contradict a year we already know, which in practice is home video. + var containerDate = data.PremiereDate; + if (containerDate is null + && (video.ProductionYear is null || data.ContainerCreationDate?.ToLocalTime().Year == video.ProductionYear)) + { + containerDate = data.ContainerCreationDate; + } + + if (containerDate is not null && (video.PremiereDate is null || replaceData)) { - video.PremiereDate = data.PremiereDate; + video.PremiereDate = containerDate; } } diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index cf9930abe1..e68abf6947 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -291,6 +291,19 @@ namespace Jellyfin.MediaEncoding.Tests.Probing } [Fact] + public void GetMediaInfo_ContainerCreationTime_KeptOutOfPremiereDate() + { + var bytes = File.ReadAllBytes("Test Data/Probing/video_progressive_no_field_order.json"); + + var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions); + MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_progressive_no_field_order.mp4", MediaProtocol.File); + + Assert.Equal(new DateTime(2020, 1, 20, 13, 56, 34, DateTimeKind.Utc), res.ContainerCreationDate); + Assert.Null(res.PremiereDate); + Assert.Null(res.ProductionYear); + } + + [Fact] public void GetMediaInfo_ProgressiveVideoNoFieldOrder_Success() { var bytes = File.ReadAllBytes("Test Data/Probing/video_progressive_no_field_order.json"); diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs index dc781af771..c4f25f3cb0 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs @@ -94,15 +94,60 @@ public class FFProbeVideoInfoTests [Theory] [InlineData(false)] [InlineData(true)] - public void FetchEmbeddedInfo_MismatchedYear_IgnoresContainerDates(bool replaceAllMetadata) + public void FetchEmbeddedInfo_Extra_IgnoresContainerDates(bool replaceAllMetadata) { var video = new Video { - ProductionYear = 2007 + ExtraType = ExtraType.Trailer, + ProductionYear = 1982, + PremiereDate = new DateTime(1982, 6, 25, 0, 0, 0, DateTimeKind.Utc) }; _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithDates(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); + Assert.Equal(1982, video.ProductionYear); + Assert.Equal(new DateTime(1982, 6, 25, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); + } + + [Fact] + public void FetchEmbeddedInfo_ExtraWithoutDates_StaysWithoutDates() + { + var video = new Video + { + ExtraType = ExtraType.Trailer + }; + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithDates(), CreateRefreshOptions(false), new LibraryOptions()); + + Assert.Null(video.ProductionYear); + Assert.Null(video.PremiereDate); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FetchEmbeddedInfo_NoYear_AppliesContainerCreationDate(bool replaceAllMetadata) + { + var video = new Video(); + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); + + Assert.Equal(new DateTime(2025, 10, 24, 19, 38, 17, DateTimeKind.Utc), video.PremiereDate); + Assert.Equal(2025, video.ProductionYear); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FetchEmbeddedInfo_KnownYear_IgnoresContainerCreationDate(bool replaceAllMetadata) + { + var video = new Video + { + ProductionYear = 2007 + }; + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); + Assert.Equal(2007, video.ProductionYear); Assert.Null(video.PremiereDate); } @@ -110,60 +155,104 @@ public class FFProbeVideoInfoTests [Theory] [InlineData(false)] [InlineData(true)] - public void FetchEmbeddedInfo_MatchingYear_AppliesContainerDates(bool replaceAllMetadata) + public void FetchEmbeddedInfo_MatchingYear_AppliesContainerCreationDate(bool replaceAllMetadata) { var video = new Video { - ProductionYear = 2016 + ProductionYear = 2025 + }; + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); + + Assert.Equal(2025, video.ProductionYear); + Assert.Equal(new DateTime(2025, 10, 24, 19, 38, 17, DateTimeKind.Utc), video.PremiereDate); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FetchEmbeddedInfo_KnownYear_StillAppliesTaggedPremiereDate(bool replaceAllMetadata) + { + var video = new Video + { + ProductionYear = 2007 }; _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithDates(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); - Assert.Equal(2016, video.ProductionYear); Assert.Equal(new DateTime(2016, 5, 4, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); } [Fact] - public void FetchEmbeddedInfo_NoContainerDates_DerivesYearFromPremiereDate() + public void FetchEmbeddedInfo_KnownYear_ReplacingAppliesTaggedProductionYear() { var video = new Video { - PremiereDate = new DateTime(1982, 6, 25, 0, 0, 0, DateTimeKind.Utc) + ProductionYear = 2007 }; - _fFProbeVideoInfo.FetchEmbeddedInfo(video, new MediaBrowser.Model.MediaInfo.MediaInfo(), CreateRefreshOptions(false), new LibraryOptions()); + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithDates(), CreateRefreshOptions(true), new LibraryOptions()); - Assert.Equal(1982, video.ProductionYear); - Assert.Equal(new DateTime(1982, 6, 25, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); + Assert.Equal(2016, video.ProductionYear); } [Theory] [InlineData(false)] [InlineData(true)] - public void FetchEmbeddedInfo_Extra_IgnoresContainerDates(bool replaceAllMetadata) + public void FetchEmbeddedInfo_TaggedDateAndSameYearCreationDate_PrefersTaggedDate(bool replaceAllMetadata) + { + var video = new Video(); + var mediaInfo = new MediaBrowser.Model.MediaInfo.MediaInfo + { + ProductionYear = 2025, + PremiereDate = new DateTime(2025, 5, 4, 0, 0, 0, DateTimeKind.Utc), + ContainerCreationDate = new DateTime(2025, 10, 24, 19, 38, 17, DateTimeKind.Utc) + }; + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, mediaInfo, CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); + + Assert.Equal(new DateTime(2025, 5, 4, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); + } + + [Fact] + public void FetchEmbeddedInfo_ExistingPremiereDate_KeepsItOverCreationDate() { var video = new Video { - ExtraType = ExtraType.Trailer, - ProductionYear = 1982, - PremiereDate = new DateTime(1982, 6, 25, 0, 0, 0, DateTimeKind.Utc) + ProductionYear = 2025, + PremiereDate = new DateTime(2025, 3, 1, 0, 0, 0, DateTimeKind.Utc) }; - _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithDates(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(false), new LibraryOptions()); - Assert.Equal(1982, video.ProductionYear); - Assert.Equal(new DateTime(1982, 6, 25, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); + Assert.Equal(new DateTime(2025, 3, 1, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); } [Fact] - public void FetchEmbeddedInfo_ExtraWithoutDates_StaysWithoutDates() + public void FetchEmbeddedInfo_ExistingPremiereDate_ReplacingUsesCreationDate() + { + var video = new Video + { + ProductionYear = 2025, + PremiereDate = new DateTime(2025, 3, 1, 0, 0, 0, DateTimeKind.Utc) + }; + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(true), new LibraryOptions()); + + Assert.Equal(new DateTime(2025, 10, 24, 19, 38, 17, DateTimeKind.Utc), video.PremiereDate); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FetchEmbeddedInfo_ExtraWithCreationDate_StaysWithoutDates(bool replaceAllMetadata) { var video = new Video { ExtraType = ExtraType.Trailer }; - _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithDates(), CreateRefreshOptions(false), new LibraryOptions()); + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); Assert.Null(video.ProductionYear); Assert.Null(video.PremiereDate); @@ -176,6 +265,12 @@ public class FFProbeVideoInfoTests PremiereDate = new DateTime(2016, 5, 4, 0, 0, 0, DateTimeKind.Utc) }; + private static MediaBrowser.Model.MediaInfo.MediaInfo CreateMediaInfoWithCreationDate() + => new() + { + ContainerCreationDate = new DateTime(2025, 10, 24, 19, 38, 17, DateTimeKind.Utc) + }; + private static MetadataRefreshOptions CreateRefreshOptions(bool replaceAllMetadata) => new(Mock.Of<IDirectoryService>()) { |
