diff options
| author | Bond-009 <bond.009@outlook.com> | 2026-11-06 09:36:52 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-11-06 09:36:52 +0100 |
| commit | deb2c9c59297003e9fa3372c9fdecee4f1beb1bf (patch) | |
| tree | b5d86856b3a90806de75df305d6d4a1f19fae247 | |
| parent | de93ba4f545c278fbc81ec9c69e9d4f7814b01bc (diff) | |
| parent | a92a574be09dc415198ed7e5775ffd9ef2b44a58 (diff) | |
Merge pull request #17642 from mysticalsoap/fix-premiere-date-from-container-encode-date
Keep container creation time out of the premiere date chain
7 files changed, 174 insertions, 6 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a01162a3b4..4a63fa3148 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -252,6 +252,7 @@ - [Daniel Aquino](https://github.com/chino) - [klizas](https://github.com/klizas) - [p27ZuH6CNAdzr](https://github.com/p27ZuH6CNAdzr) + - [mysticalsoap](https://github.com/mysticalsoap) # Emby Contributors 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 1cb8e09414..b093d428e6 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -442,11 +442,20 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (useContainerDates && 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)) { - video.PremiereDate = data.PremiereDate; + containerDate = data.ContainerCreationDate; + } + + if (containerDate is not null && (video.PremiereDate is null || replaceData)) + { + 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 59d3f42edf..c4f25f3cb0 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs @@ -123,6 +123,141 @@ public class FFProbeVideoInfoTests 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); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FetchEmbeddedInfo_MatchingYear_AppliesContainerCreationDate(bool replaceAllMetadata) + { + var video = new Video + { + 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(new DateTime(2016, 5, 4, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); + } + + [Fact] + public void FetchEmbeddedInfo_KnownYear_ReplacingAppliesTaggedProductionYear() + { + var video = new Video + { + ProductionYear = 2007 + }; + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithDates(), CreateRefreshOptions(true), new LibraryOptions()); + + Assert.Equal(2016, video.ProductionYear); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + 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 + { + ProductionYear = 2025, + PremiereDate = new DateTime(2025, 3, 1, 0, 0, 0, DateTimeKind.Utc) + }; + + _fFProbeVideoInfo.FetchEmbeddedInfo(video, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(false), new LibraryOptions()); + + Assert.Equal(new DateTime(2025, 3, 1, 0, 0, 0, DateTimeKind.Utc), video.PremiereDate); + } + + [Fact] + 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, CreateMediaInfoWithCreationDate(), CreateRefreshOptions(replaceAllMetadata), new LibraryOptions()); + + Assert.Null(video.ProductionYear); + Assert.Null(video.PremiereDate); + } + private static MediaBrowser.Model.MediaInfo.MediaInfo CreateMediaInfoWithDates() => new() { @@ -130,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>()) { |
