31 changes: 28 additions & 3 deletions Emby.Server.Implementations/Data/SqliteItemRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public class SqliteItemRepository : BaseSqliteRepository, IItemRepository

private const string SaveItemCommandText =
@"replace into TypedBaseItems
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId,ChannelGroup)
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId,@ChannelGroup)";

private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost;
Expand Down Expand Up @@ -132,7 +132,8 @@ public class SqliteItemRepository : BaseSqliteRepository, IItemRepository
"ExternalId",
"SeriesPresentationUniqueKey",
"ShowId",
"OwnerId"
"OwnerId",
"ChannelGroup"
};

private static readonly string _retrieveItemColumnsSelectQuery = $"select {string.Join(',', _retrieveItemColumns)} from TypedBaseItems where guid = @guid";
Expand Down Expand Up @@ -513,6 +514,7 @@ const string CreateMediaAttachmentsTableCommand
AddColumn(connection, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ShowId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "OwnerId", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "ChannelGroup", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Width", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Height", "INT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Size", "BIGINT", existingColumnNames);
Expand Down Expand Up @@ -1019,6 +1021,8 @@ private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, Sql
saveItemStatement.TryBind("@OwnerId", ownerId);
}

saveItemStatement.TryBind("@ChannelGroup", item.ChannelGroup);

saveItemStatement.ExecuteNonQuery();
}

Expand Down Expand Up @@ -1872,6 +1876,11 @@ private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool
item.OwnerId = ownerId;
}

if (reader.TryGetString(index, out var channelGroup))
{
item.ChannelGroup = channelGroup;
}

return item;
}

Expand Down Expand Up @@ -3693,6 +3702,22 @@ private List<string> GetWhereClauses(InternalItemsQuery query, SqliteCommand? st
clauseBuilder.Length = 0;
}

if (query.ChannelGroups.Count > 0)
{
clauseBuilder.Append('(');
for (var i = 0; i < query.ChannelGroups.Count; i++)
{
clauseBuilder.Append("(json_extract(data, '$.ChannelGroup')=@ChannelGroup")
.Append(i)
.Append(") OR ");
statement?.TryBind("@ChannelGroup" + i, query.ChannelGroups[i]);
}

clauseBuilder.Length -= Or.Length;
whereClauses.Add(clauseBuilder.Append(')').ToString());
clauseBuilder.Length = 0;
}

if (tags.Count > 0)
{
clauseBuilder.Append('(');
Expand Down
1 change: 1 addition & 0 deletions Emby.Server.Implementations/Dto/DtoService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,7 @@ private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem owner, D
}

dto.ChannelId = item.ChannelId;
dto.ChannelGroup = item.ChannelGroup;

if (item.SourceType == SourceType.Channel)
{
Expand Down
19 changes: 18 additions & 1 deletion Jellyfin.Api/Controllers/FilterController.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System;
using System.Linq;
using System.Threading;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
Expand All @@ -23,16 +25,19 @@ namespace Jellyfin.Api.Controllers;
public class FilterController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
private readonly ILiveTvManager _liveTvManager;
private readonly IUserManager _userManager;

/// <summary>
/// Initializes a new instance of the <see cref="FilterController"/> class.
/// </summary>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
public FilterController(ILibraryManager libraryManager, IUserManager userManager)
public FilterController(ILibraryManager libraryManager, ILiveTvManager liveTvManager, IUserManager userManager)
{
_libraryManager = libraryManager;
_liveTvManager = liveTvManager;
_userManager = userManager;
}

Expand Down Expand Up @@ -67,6 +72,11 @@ public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
{
item = _libraryManager.GetParentItem(parentId, user?.Id);
}
else if (includeItemTypes.Length == 1
&& includeItemTypes[0] == BaseItemKind.LiveTvChannel)
{
item = _liveTvManager.GetInternalLiveTvFolder(CancellationToken.None);
}

var query = new InternalItemsQuery
{
Expand Down Expand Up @@ -113,6 +123,13 @@ public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
.Where(i => !string.IsNullOrWhiteSpace(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Order()
.ToArray(),

ChannelGroups = itemList
.Select(i => i.ChannelGroup)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.Order()
.ToArray()
};
}
Expand Down
5 changes: 4 additions & 1 deletion Jellyfin.Api/Controllers/LiveTvController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public ActionResult<LiveTvInfo> GetLiveTvInfo()
/// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
/// <param name="enableImageTypes">"Optional. The image types to include in the output.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="channelGroups">Optional. If specified, results will be filtered based on group. This allows multiple, pipe delimited.</param>
/// <param name="enableUserData">Optional. Include user data.</param>
/// <param name="sortBy">Optional. Key to sort by.</param>
/// <param name="sortOrder">Optional. Sort order.</param>
Expand Down Expand Up @@ -153,6 +154,7 @@ public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] channelGroups,
[FromQuery] bool? enableUserData,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
[FromQuery] SortOrder? sortOrder,
Expand Down Expand Up @@ -182,7 +184,8 @@ public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
IsSports = isSports,
SortBy = sortBy,
SortOrder = sortOrder ?? SortOrder.Ascending,
AddCurrentProgram = addCurrentProgram
AddCurrentProgram = addCurrentProgram,
ChannelGroups = channelGroups
},
dtoOptions,
CancellationToken.None);
Expand Down
6 changes: 6 additions & 0 deletions MediaBrowser.Controller/Entities/BaseItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,12 @@ public BaseItem DisplayParent
[JsonIgnore]
public string Overview { get; set; }

/// <summary>
/// Gets or sets the group of the channel.
/// </summary>
/// <value>The group of the channel.</value>
public string ChannelGroup { get; set; }

/// <summary>
/// Gets or sets the studios.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions MediaBrowser.Controller/Entities/InternalItemsQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public InternalItemsQuery()
ExcludeTags = Array.Empty<string>();
GenreIds = Array.Empty<Guid>();
Genres = Array.Empty<string>();
ChannelGroups = Array.Empty<string>();
GroupByPresentationUniqueKey = true;
ImageTypes = Array.Empty<ImageType>();
IncludeItemTypes = Array.Empty<BaseItemKind>();
Expand Down Expand Up @@ -100,6 +101,8 @@ public InternalItemsQuery(User? user)

public IReadOnlyList<string> Genres { get; set; }

public IReadOnlyList<string> ChannelGroups { get; set; }

public bool? IsSpecialSeason { get; set; }

public bool? IsMissing { get; set; }
Expand Down
6 changes: 6 additions & 0 deletions MediaBrowser.Model/Dto/BaseItemDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,5 +792,11 @@ public class BaseItemDto : IHasProviderIds, IItemDto, IHasServerId
/// </summary>
/// <value>The current program.</value>
public BaseItemDto CurrentProgram { get; set; }

/// <summary>
/// Gets or sets channel group.
/// </summary>
/// <value>The channel group.</value>
public string ChannelGroup { get; set; }
}
}
7 changes: 7 additions & 0 deletions MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#pragma warning disable CS1591

using System;
using System.Collections.Generic;
using Jellyfin.Data.Enums;

namespace MediaBrowser.Model.LiveTv
Expand All @@ -15,6 +16,7 @@ public LiveTvChannelQuery()
{
EnableUserData = true;
SortBy = Array.Empty<ItemSortBy>();
ChannelGroups = Array.Empty<string>();
}

/// <summary>
Expand Down Expand Up @@ -106,5 +108,10 @@ public LiveTvChannelQuery()
/// </summary>
/// <value>The sort order.</value>
public SortOrder? SortOrder { get; set; }

/// <summary>
/// Gets or sets the channel groups to filter on.
/// </summary>
public IReadOnlyList<string> ChannelGroups { get; set; }
}
}
3 changes: 3 additions & 0 deletions MediaBrowser.Model/Querying/QueryFiltersLegacy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public QueryFiltersLegacy()
Tags = Array.Empty<string>();
OfficialRatings = Array.Empty<string>();
Years = Array.Empty<int>();
ChannelGroups = Array.Empty<string>();
}

public string[] Genres { get; set; }
Expand All @@ -22,5 +23,7 @@ public QueryFiltersLegacy()
public string[] OfficialRatings { get; set; }

public int[] Years { get; set; }

public string[] ChannelGroups { get; set; }
}
}
1 change: 1 addition & 0 deletions src/Jellyfin.LiveTv/Guide/GuideManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ private async Task<LiveTvChannel> GetChannel(
item = new LiveTvChannel
{
Name = channelInfo.Name,
ChannelGroup = channelInfo.ChannelGroup,
Id = id,
DateCreated = DateTime.UtcNow
};
Expand Down
1 change: 1 addition & 0 deletions src/Jellyfin.LiveTv/LiveTvManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOp
IsLiked = query.IsLiked,
StartIndex = query.StartIndex,
Limit = query.Limit,
ChannelGroups = query.ChannelGroups,
DtoOptions = dtoOptions
};

Expand Down
4 changes: 3 additions & 1 deletion src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,13 @@ private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string

channel.Path = trimmedLine;
channels.Add(channel);
_logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
_logger.LogInformation("Parsed channel: {ChannelGroup} {ChannelName}", channel.ChannelGroup, channel.Name);
extInf = string.Empty;
}
}

_logger.LogInformation("Parsing of channels complete!");

return channels;
}

Expand Down