aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2026-11-06 09:36:52 +0100
committerGitHub <noreply@github.com>2026-11-06 09:36:52 +0100
commitdeb2c9c59297003e9fa3372c9fdecee4f1beb1bf (patch)
treeb5d86856b3a90806de75df305d6d4a1f19fae247
parentde93ba4f545c278fbc81ec9c69e9d4f7814b01bc (diff)
parenta92a574be09dc415198ed7e5775ffd9ef2b44a58 (diff)
Merge pull request #17642 from mysticalsoap/fix-premiere-date-from-container-encode-date
Keep container creation time out of the premiere date chain
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs6
-rw-r--r--MediaBrowser.Model/MediaInfo/MediaInfo.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs15
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs13
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs141
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>())
{