Browse Source

add configurable channel group and categories (#651)

pull/655/head
Jason Dove 4 years ago committed by GitHub
parent
commit
f6c42f3ff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Channels/ChannelViewModel.cs
  3. 2
      ErsatzTV.Application/Channels/Commands/CreateChannel.cs
  4. 4
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  5. 2
      ErsatzTV.Application/Channels/Commands/UpdateChannel.cs
  6. 4
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  7. 2
      ErsatzTV.Application/Channels/Mapper.cs
  8. 2
      ErsatzTV.Core/Domain/Channel.cs
  9. 15
      ErsatzTV.Core/Iptv/ChannelGuide.cs
  10. 2
      ErsatzTV.Core/Iptv/ChannelPlaylist.cs
  11. 4
      ErsatzTV.Infrastructure/Data/Configurations/ChannelConfiguration.cs
  12. 3872
      ErsatzTV.Infrastructure/Migrations/20220222024640_Add_ChannelGroup_ChannelCategories.Designer.cs
  13. 36
      ErsatzTV.Infrastructure/Migrations/20220222024640_Add_ChannelGroup_ChannelCategories.cs
  14. 9
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  15. 5
      ErsatzTV/Pages/ChannelEditor.razor
  16. 1
      ErsatzTV/Validators/ChannelEditViewModelValidator.cs
  17. 6
      ErsatzTV/ViewModels/ChannelEditViewModel.cs

3
CHANGELOG.md

@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add improved but experimental transcoder logic, which can be toggled on and off in `Settings`
- Fix `HLS Segmenter` bug when source video packet contains no duration (`N/A`)
### Added
- Add configurable channel group (M3U) and categories (XMLTV)
### Changed
- Disable framerate normalization by default and on all ffmpeg profiles
- If framerate normalization is desired (not typically needed), it can be re-enabled manually

2
ErsatzTV.Application/Channels/ChannelViewModel.cs

@ -6,6 +6,8 @@ namespace ErsatzTV.Application.Channels @@ -6,6 +6,8 @@ namespace ErsatzTV.Application.Channels
int Id,
string Number,
string Name,
string Group,
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredLanguageCode,

2
ErsatzTV.Application/Channels/Commands/CreateChannel.cs

@ -9,6 +9,8 @@ namespace ErsatzTV.Application.Channels.Commands @@ -9,6 +9,8 @@ namespace ErsatzTV.Application.Channels.Commands
(
string Name,
string Number,
string Group,
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredLanguageCode,

4
ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs

@ -27,7 +27,7 @@ namespace ErsatzTV.Application.Channels.Commands @@ -27,7 +27,7 @@ namespace ErsatzTV.Application.Channels.Commands
CreateChannel request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Channel> validation = await Validate(dbContext, request);
return await validation.Apply(c => PersistChannel(dbContext, c));
}
@ -65,6 +65,8 @@ namespace ErsatzTV.Application.Channels.Commands @@ -65,6 +65,8 @@ namespace ErsatzTV.Application.Channels.Commands
{
Name = name,
Number = number,
Group = request.Group,
Categories = request.Categories,
FFmpegProfileId = ffmpegProfileId,
StreamingMode = request.StreamingMode,
Artwork = artwork,

2
ErsatzTV.Application/Channels/Commands/UpdateChannel.cs

@ -10,6 +10,8 @@ namespace ErsatzTV.Application.Channels.Commands @@ -10,6 +10,8 @@ namespace ErsatzTV.Application.Channels.Commands
int ChannelId,
string Name,
string Number,
string Group,
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredLanguageCode,

4
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -28,7 +28,7 @@ namespace ErsatzTV.Application.Channels.Commands @@ -28,7 +28,7 @@ namespace ErsatzTV.Application.Channels.Commands
UpdateChannel request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Channel> validation = await Validate(dbContext, request);
return await validation.Apply(c => ApplyUpdateRequest(dbContext, c, request));
}
@ -37,6 +37,8 @@ namespace ErsatzTV.Application.Channels.Commands @@ -37,6 +37,8 @@ namespace ErsatzTV.Application.Channels.Commands
{
c.Name = update.Name;
c.Number = update.Number;
c.Group = update.Group;
c.Categories = update.Categories;
c.FFmpegProfileId = update.FFmpegProfileId;
c.PreferredLanguageCode = update.PreferredLanguageCode;
c.Artwork ??= new List<Artwork>();

2
ErsatzTV.Application/Channels/Mapper.cs

@ -11,6 +11,8 @@ namespace ErsatzTV.Application.Channels @@ -11,6 +11,8 @@ namespace ErsatzTV.Application.Channels
channel.Id,
channel.Number,
channel.Name,
channel.Group,
channel.Categories,
channel.FFmpegProfileId,
GetLogo(channel),
channel.PreferredLanguageCode,

2
ErsatzTV.Core/Domain/Channel.cs

@ -13,6 +13,8 @@ namespace ErsatzTV.Core.Domain @@ -13,6 +13,8 @@ namespace ErsatzTV.Core.Domain
public Guid UniqueId { get; init; }
public string Number { get; set; }
public string Name { get; set; }
public string Group { get; set; }
public string Categories { get; set; }
public int FFmpegProfileId { get; set; }
public FFmpegProfile FFmpegProfile { get; set; }
public int? WatermarkId { get; set; }

15
ErsatzTV.Core/Iptv/ChannelGuide.cs

@ -53,6 +53,14 @@ namespace ErsatzTV.Core.Iptv @@ -53,6 +53,14 @@ namespace ErsatzTV.Core.Iptv
xml.WriteString(channel.Name);
xml.WriteEndElement(); // display-name
foreach (string category in GetCategories(channel.Categories))
{
xml.WriteStartElement("category");
xml.WriteAttributeString("lang", "en");
xml.WriteString(category);
xml.WriteEndElement(); // category
}
xml.WriteStartElement("icon");
string logo = Optional(channel.Artwork).Flatten()
.Filter(a => a.ArtworkKind == ArtworkKind.Logo)
@ -491,6 +499,13 @@ namespace ErsatzTV.Core.Iptv @@ -491,6 +499,13 @@ namespace ErsatzTV.Core.Iptv
}).Flatten();
}
private static List<string> GetCategories(string categories) =>
(categories ?? string.Empty).Split(',')
.Map(s => s.Trim())
.Filter(s => !string.IsNullOrWhiteSpace(s))
.Distinct()
.ToList();
private record ContentRating(Option<string> System, string Value);
}
}

2
ErsatzTV.Core/Iptv/ChannelPlaylist.cs

@ -52,7 +52,7 @@ namespace ErsatzTV.Core.Iptv @@ -52,7 +52,7 @@ namespace ErsatzTV.Core.Iptv
string acodec = channel.FFmpegProfile.AudioCodec;
sb.AppendLine(
$"#EXTINF:0 tvg-id=\"{channel.Number}.etv\" channel-id=\"{shortUniqueId}\" channel-number=\"{channel.Number}\" CUID=\"{shortUniqueId}\" tvg-chno=\"{channel.Number}\" tvg-name=\"{channel.Name}\" tvg-logo=\"{logo}\" group-title=\"ErsatzTV\" tvc-stream-vcodec=\"{vcodec}\" tvc-stream-acodec=\"{acodec}\", {channel.Name}");
$"#EXTINF:0 tvg-id=\"{channel.Number}.etv\" channel-id=\"{shortUniqueId}\" channel-number=\"{channel.Number}\" CUID=\"{shortUniqueId}\" tvg-chno=\"{channel.Number}\" tvg-name=\"{channel.Name}\" tvg-logo=\"{logo}\" group-title=\"{channel.Group}\" tvc-stream-vcodec=\"{vcodec}\" tvc-stream-acodec=\"{acodec}\", {channel.Name}");
sb.AppendLine($"{_scheme}://{_host}/iptv/channel/{channel.Number}.{format}");
}

4
ErsatzTV.Infrastructure/Data/Configurations/ChannelConfiguration.cs

@ -33,6 +33,10 @@ namespace ErsatzTV.Infrastructure.Data.Configurations @@ -33,6 +33,10 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
.HasForeignKey(i => i.FallbackFillerId)
.OnDelete(DeleteBehavior.SetNull)
.IsRequired(false);
builder.Property(c => c.Group)
.IsRequired()
.HasDefaultValue("ErsatzTV");
}
}
}

3872
ErsatzTV.Infrastructure/Migrations/20220222024640_Add_ChannelGroup_ChannelCategories.Designer.cs generated

File diff suppressed because it is too large Load Diff

36
ErsatzTV.Infrastructure/Migrations/20220222024640_Add_ChannelGroup_ChannelCategories.cs

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_ChannelGroup_ChannelCategories : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Categories",
table: "Channel",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Group",
table: "Channel",
type: "TEXT",
nullable: false,
defaultValue: "ErsatzTV");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Categories",
table: "Channel");
migrationBuilder.DropColumn(
name: "Group",
table: "Channel");
}
}
}

9
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -218,12 +218,21 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -218,12 +218,21 @@ namespace ErsatzTV.Infrastructure.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Categories")
.HasColumnType("TEXT");
b.Property<int>("FFmpegProfileId")
.HasColumnType("INTEGER");
b.Property<int?>("FallbackFillerId")
.HasColumnType("INTEGER");
b.Property<string>("Group")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("ErsatzTV");
b.Property<string>("Name")
.HasColumnType("TEXT");

5
ErsatzTV/Pages/ChannelEditor.razor

@ -31,6 +31,8 @@ @@ -31,6 +31,8 @@
<MudCardContent>
<MudTextField Label="Number" @bind-Value="_model.Number" For="@(() => _model.Number)" Immediate="true"/>
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
<MudTextField Class="mt-3" Label="Group" @bind-Value="_model.Group" For="@(() => _model.Group)"/>
<MudTextField Class="mt-3" Label="Categories" @bind-Value="_model.Categories" For="@(() => _model.Categories)" Placeholder="Comma-separated list of categories"/>
<MudSelect Class="mt-3" Label="Streaming Mode" @bind-Value="_model.StreamingMode" For="@(() => _model.StreamingMode)">
<MudSelectItem Value="@(StreamingMode.TransportStreamHybrid)">MPEG-TS</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.TransportStream)">MPEG-TS (Legacy)</MudSelectItem>
@ -133,6 +135,8 @@ @@ -133,6 +135,8 @@
{
_model.Id = channelViewModel.Id;
_model.Name = channelViewModel.Name;
_model.Group = channelViewModel.Group;
_model.Categories = channelViewModel.Categories;
_model.Number = channelViewModel.Number;
_model.FFmpegProfileId = channelViewModel.FFmpegProfileId;
_model.Logo = channelViewModel.Logo;
@ -153,6 +157,7 @@ @@ -153,6 +157,7 @@
int maxNumber = Optional(channelNumbers).Flatten().DefaultIfEmpty(0).Max();
_model.Number = (maxNumber + 1).ToString();
_model.Name = "New Channel";
_model.Group = "ErsatzTV";
_model.FFmpegProfileId = ffmpegSettings.DefaultFFmpegProfileId;
_model.StreamingMode = StreamingMode.TransportStreamHybrid;
}

1
ErsatzTV/Validators/ChannelEditViewModelValidator.cs

@ -15,6 +15,7 @@ namespace ErsatzTV.Validators @@ -15,6 +15,7 @@ namespace ErsatzTV.Validators
.WithMessage("Invalid channel number; one decimal is allowed for subchannels");
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.Group).NotEmpty();
RuleFor(x => x.FFmpegProfileId).GreaterThan(0);
RuleFor(x => x.PreferredLanguageCode)

6
ErsatzTV/ViewModels/ChannelEditViewModel.cs

@ -7,6 +7,8 @@ namespace ErsatzTV.ViewModels @@ -7,6 +7,8 @@ namespace ErsatzTV.ViewModels
{
public int Id { get; set; }
public string Name { get; set; }
public string Group { get; set; }
public string Categories { get; set; }
public string Number { get; set; }
public int FFmpegProfileId { get; set; }
public string PreferredLanguageCode { get; set; }
@ -20,6 +22,8 @@ namespace ErsatzTV.ViewModels @@ -20,6 +22,8 @@ namespace ErsatzTV.ViewModels
Id,
Name,
Number,
Group,
Categories,
FFmpegProfileId,
Logo,
PreferredLanguageCode,
@ -31,6 +35,8 @@ namespace ErsatzTV.ViewModels @@ -31,6 +35,8 @@ namespace ErsatzTV.ViewModels
new(
Name,
Number,
Group,
Categories,
FFmpegProfileId,
Logo,
PreferredLanguageCode,

Loading…
Cancel
Save