diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
index ed02fe6a1d..e421601092 100644
--- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
+++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
@@ -14,6 +14,7 @@ using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
+using Jellyfin.LiveTv;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -1109,9 +1110,8 @@ namespace Jellyfin.LiveTv.Channels
item.Path = mediaSource?.Path;
}
- if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
+ if (LiveTvChannelImageHelper.UpdateChannelImageIfNeeded(item, null, info.ImageUrl))
{
- item.SetImagePath(ImageType.Primary, info.ImageUrl);
_logger.LogDebug("Forcing update due to ImageUrl {0}", item.Name);
forceUpdate = true;
}
diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
index 556516674b..b8545cbb64 100644
--- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs
+++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
+using Jellyfin.LiveTv;
using Jellyfin.LiveTv.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Dto;
@@ -448,18 +449,9 @@ public class GuideManager : IGuideManager
item.Name = channelInfo.Name;
- if (!item.HasImage(ImageType.Primary))
+ if (LiveTvChannelImageHelper.UpdateChannelImageIfNeeded(item, channelInfo.ImagePath, channelInfo.ImageUrl))
{
- if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
- {
- item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
- forceUpdate = true;
- }
- else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
- {
- item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
- forceUpdate = true;
- }
+ forceUpdate = true;
}
if (isNew)
diff --git a/src/Jellyfin.LiveTv/LiveTvChannelImageHelper.cs b/src/Jellyfin.LiveTv/LiveTvChannelImageHelper.cs
new file mode 100644
index 0000000000..a590193b5f
--- /dev/null
+++ b/src/Jellyfin.LiveTv/LiveTvChannelImageHelper.cs
@@ -0,0 +1,33 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+
+namespace Jellyfin.LiveTv;
+
+///
+/// Helpers for keeping Live TV channel icons in sync with guide data.
+///
+internal static class LiveTvChannelImageHelper
+{
+ ///
+ /// Applies the channel icon from guide or tuner metadata.
+ /// Called on each guide refresh so remote icons are re-downloaded even when the URL is unchanged.
+ ///
+ /// The channel item.
+ /// The local image path from the tuner, if any.
+ /// The remote image URL from the guide provider, if any.
+ /// true when the item image metadata was updated.
+ internal static bool UpdateChannelImageIfNeeded(BaseItem item, string? imagePath, string? imageUrl)
+ {
+ var newImageSource = !string.IsNullOrWhiteSpace(imagePath)
+ ? imagePath
+ : imageUrl;
+
+ if (string.IsNullOrWhiteSpace(newImageSource))
+ {
+ return false;
+ }
+
+ item.SetImagePath(ImageType.Primary, newImageSource);
+ return true;
+ }
+}
diff --git a/tests/Jellyfin.LiveTv.Tests/LiveTvChannelImageHelperTests.cs b/tests/Jellyfin.LiveTv.Tests/LiveTvChannelImageHelperTests.cs
new file mode 100644
index 0000000000..f44cb88834
--- /dev/null
+++ b/tests/Jellyfin.LiveTv.Tests/LiveTvChannelImageHelperTests.cs
@@ -0,0 +1,51 @@
+using Jellyfin.LiveTv;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Entities;
+using Xunit;
+
+namespace Jellyfin.LiveTv.Tests;
+
+public class LiveTvChannelImageHelperTests
+{
+ [Fact]
+ public void UpdateChannelImageIfNeeded_NoSource_DoesNotUpdate()
+ {
+ var channel = new LiveTvChannel { Name = "Test Channel" };
+
+ var updated = LiveTvChannelImageHelper.UpdateChannelImageIfNeeded(channel, null, null);
+
+ Assert.False(updated);
+ Assert.False(channel.HasImage(ImageType.Primary));
+ }
+
+ [Fact]
+ public void UpdateChannelImageIfNeeded_WithUrl_AppliesUrl()
+ {
+ var channel = new LiveTvChannel { Name = "Test Channel" };
+
+ var updated = LiveTvChannelImageHelper.UpdateChannelImageIfNeeded(
+ channel,
+ null,
+ "https://example.com/icon.png");
+
+ Assert.True(updated);
+ Assert.True(channel.HasImage(ImageType.Primary));
+ Assert.Equal("https://example.com/icon.png", channel.GetImagePath(ImageType.Primary));
+ }
+
+ [Fact]
+ public void UpdateChannelImageIfNeeded_SameUrl_StillUpdates()
+ {
+ var channel = new LiveTvChannel { Name = "Test Channel" };
+ LiveTvChannelImageHelper.UpdateChannelImageIfNeeded(channel, null, "https://example.com/icon.png");
+
+ var updated = LiveTvChannelImageHelper.UpdateChannelImageIfNeeded(
+ channel,
+ null,
+ "https://example.com/icon.png");
+
+ Assert.True(updated);
+ Assert.Equal("https://example.com/icon.png", channel.GetImagePath(ImageType.Primary));
+ }
+}