aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormysticalsoap <github@esotericsuds.com>2026-09-15 16:31:24 -0400
committermysticalsoap <github@esotericsuds.com>2026-10-15 17:35:24 -0400
commita92a574be09dc415198ed7e5775ffd9ef2b44a58 (patch)
treea517b698ebb8b3a820576a47789565b7907f09f0
parent2416fb56b4904fcf1fbf561be0c05b37d2d0886f (diff)
Keep container creation time out of the premiere date chain
-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.cs22
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs13
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/FFProbeVideoInfoTests.cs133
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>())
{