Browse Source

add preferred audio title feature (#943)

* use consistent edit/delete icons

* add preferred audio title feature

* update dependencies
pull/944/head
Jason Dove 3 years ago committed by GitHub
parent
commit
46331ed2c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/Channels/ChannelViewModel.cs
  3. 1
      ErsatzTV.Application/Channels/Commands/CreateChannel.cs
  4. 1
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  5. 1
      ErsatzTV.Application/Channels/Commands/UpdateChannel.cs
  6. 3
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  7. 1
      ErsatzTV.Application/Channels/Mapper.cs
  8. 1
      ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
  9. 1
      ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
  10. 4
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  11. 1
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
  12. 4
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  13. 2
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs
  14. 2
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs
  15. 2
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
  16. 2
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs
  17. 1
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs
  18. 1
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  19. 2
      ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj
  20. 4
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  21. 1
      ErsatzTV.Core/Domain/Channel.cs
  22. 1
      ErsatzTV.Core/Domain/PlayoutItem.cs
  23. 1
      ErsatzTV.Core/Domain/ProgramScheduleItem.cs
  24. 2
      ErsatzTV.Core/ErsatzTV.Core.csproj
  25. 4
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  26. 45
      ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs
  27. 1
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  28. 3
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs
  29. 1
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  30. 1
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  31. 1
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  32. 1
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  33. 2
      ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj
  34. 2
      ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj
  35. 4303
      ErsatzTV.Infrastructure/Migrations/20220830202251_Add_PreferredAudioTitle.Designer.cs
  36. 45
      ErsatzTV.Infrastructure/Migrations/20220830202251_Add_PreferredAudioTitle.cs
  37. 11
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  38. 2
      ErsatzTV/ErsatzTV.csproj
  39. 2
      ErsatzTV/Pages/Artist.razor
  40. 2
      ErsatzTV/Pages/ChannelEditor.razor
  41. 3
      ErsatzTV/Pages/ScheduleItemsEditor.razor
  42. 2
      ErsatzTV/Pages/TelevisionEpisodeList.razor
  43. 2
      ErsatzTV/Pages/TelevisionSeasonList.razor
  44. 3
      ErsatzTV/ViewModels/ChannelEditViewModel.cs
  45. 1
      ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs
  46. 11
      ErsatzTV/client-app/src/views/ChannelsPage.vue

7
CHANGELOG.md

@ -10,6 +10,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix scanner crash caused by invalid mtime - Fix scanner crash caused by invalid mtime
- `VAAPI`: Downgrade libva from 2.15 to 2.14 - `VAAPI`: Downgrade libva from 2.15 to 2.14
### Added
- Add `Preferred Audio Title` feature
- Preference can be configured in channel settings and overridden on schedule items
- When a title is specified, audio streams that contain that title (case-insensitive search) will be prioritized
- This can be helpful for creating channels that use commentary tracks
- External tooling exists to easily update title/name metadata if your audio streams don't already have this metadata
## [0.6.6-beta] - 2022-08-17 ## [0.6.6-beta] - 2022-08-17
### Fixed ### Fixed
- Use MIME Type `application/x-mpegurl` for all playlists instead of `application/vnd.apple.mpegurl` - Use MIME Type `application/x-mpegurl` for all playlists instead of `application/vnd.apple.mpegurl`

1
ErsatzTV.Application/Channels/ChannelViewModel.cs

@ -11,6 +11,7 @@ public record ChannelViewModel(
int FFmpegProfileId, int FFmpegProfileId,
string Logo, string Logo,
string PreferredAudioLanguageCode, string PreferredAudioLanguageCode,
string PreferredAudioTitle,
StreamingMode StreamingMode, StreamingMode StreamingMode,
int? WatermarkId, int? WatermarkId,
int? FallbackFillerId, int? FallbackFillerId,

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

@ -12,6 +12,7 @@ public record CreateChannel
int FFmpegProfileId, int FFmpegProfileId,
string Logo, string Logo,
string PreferredAudioLanguageCode, string PreferredAudioLanguageCode,
string PreferredAudioTitle,
StreamingMode StreamingMode, StreamingMode StreamingMode,
int? WatermarkId, int? WatermarkId,
int? FallbackFillerId, int? FallbackFillerId,

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

@ -71,6 +71,7 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
StreamingMode = request.StreamingMode, StreamingMode = request.StreamingMode,
Artwork = artwork, Artwork = artwork,
PreferredAudioLanguageCode = preferredAudioLanguageCode, PreferredAudioLanguageCode = preferredAudioLanguageCode,
PreferredAudioTitle = request.PreferredAudioTitle,
PreferredSubtitleLanguageCode = preferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = preferredSubtitleLanguageCode,
SubtitleMode = request.SubtitleMode, SubtitleMode = request.SubtitleMode,
MusicVideoCreditsMode = request.MusicVideoCreditsMode MusicVideoCreditsMode = request.MusicVideoCreditsMode

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

@ -13,6 +13,7 @@ public record UpdateChannel
int FFmpegProfileId, int FFmpegProfileId,
string Logo, string Logo,
string PreferredAudioLanguageCode, string PreferredAudioLanguageCode,
string PreferredAudioTitle,
StreamingMode StreamingMode, StreamingMode StreamingMode,
int? WatermarkId, int? WatermarkId,
int? FallbackFillerId, int? FallbackFillerId,

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

@ -31,7 +31,7 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Channel> validation = await Validate(dbContext, request); Validation<BaseError, Channel> validation = await Validate(dbContext, request);
return await LanguageExtensions.Apply(validation, c => ApplyUpdateRequest(dbContext, c, request)); return await validation.Apply(c => ApplyUpdateRequest(dbContext, c, request));
} }
private async Task<ChannelViewModel> ApplyUpdateRequest(TvContext dbContext, Channel c, UpdateChannel update) private async Task<ChannelViewModel> ApplyUpdateRequest(TvContext dbContext, Channel c, UpdateChannel update)
@ -42,6 +42,7 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
c.Categories = update.Categories; c.Categories = update.Categories;
c.FFmpegProfileId = update.FFmpegProfileId; c.FFmpegProfileId = update.FFmpegProfileId;
c.PreferredAudioLanguageCode = update.PreferredAudioLanguageCode; c.PreferredAudioLanguageCode = update.PreferredAudioLanguageCode;
c.PreferredAudioTitle = update.PreferredAudioTitle;
c.PreferredSubtitleLanguageCode = update.PreferredSubtitleLanguageCode; c.PreferredSubtitleLanguageCode = update.PreferredSubtitleLanguageCode;
c.SubtitleMode = update.SubtitleMode; c.SubtitleMode = update.SubtitleMode;
c.MusicVideoCreditsMode = update.MusicVideoCreditsMode; c.MusicVideoCreditsMode = update.MusicVideoCreditsMode;

1
ErsatzTV.Application/Channels/Mapper.cs

@ -15,6 +15,7 @@ internal static class Mapper
channel.FFmpegProfileId, channel.FFmpegProfileId,
GetLogo(channel), GetLogo(channel),
channel.PreferredAudioLanguageCode, channel.PreferredAudioLanguageCode,
channel.PreferredAudioTitle,
channel.StreamingMode, channel.StreamingMode,
channel.WatermarkId, channel.WatermarkId,
channel.FallbackFillerId, channel.FallbackFillerId,

1
ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs

@ -26,6 +26,7 @@ public record AddProgramScheduleItem(
int? FallbackFillerId, int? FallbackFillerId,
int? WatermarkId, int? WatermarkId,
string PreferredAudioLanguageCode, string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode, string PreferredSubtitleLanguageCode,
ChannelSubtitleMode? SubtitleMode) : IRequest<Either<BaseError, ProgramScheduleItemViewModel>>, ChannelSubtitleMode? SubtitleMode) : IRequest<Either<BaseError, ProgramScheduleItemViewModel>>,
IProgramScheduleItemRequest; IProgramScheduleItemRequest;

1
ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs

@ -24,6 +24,7 @@ public interface IProgramScheduleItemRequest
int? FallbackFillerId { get; } int? FallbackFillerId { get; }
int? WatermarkId { get; } int? WatermarkId { get; }
string PreferredAudioLanguageCode { get; } string PreferredAudioLanguageCode { get; }
string PreferredAudioTitle { get; }
string PreferredSubtitleLanguageCode { get; } string PreferredSubtitleLanguageCode { get; }
ChannelSubtitleMode? SubtitleMode { get; } ChannelSubtitleMode? SubtitleMode { get; }
} }

4
ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs

@ -180,6 +180,7 @@ public abstract class ProgramScheduleItemCommandBase
FallbackFillerId = item.FallbackFillerId, FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId, WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
SubtitleMode = item.SubtitleMode SubtitleMode = item.SubtitleMode
}, },
@ -203,6 +204,7 @@ public abstract class ProgramScheduleItemCommandBase
FallbackFillerId = item.FallbackFillerId, FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId, WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
SubtitleMode = item.SubtitleMode SubtitleMode = item.SubtitleMode
}, },
@ -227,6 +229,7 @@ public abstract class ProgramScheduleItemCommandBase
FallbackFillerId = item.FallbackFillerId, FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId, WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
SubtitleMode = item.SubtitleMode SubtitleMode = item.SubtitleMode
}, },
@ -252,6 +255,7 @@ public abstract class ProgramScheduleItemCommandBase
FallbackFillerId = item.FallbackFillerId, FallbackFillerId = item.FallbackFillerId,
WatermarkId = item.WatermarkId, WatermarkId = item.WatermarkId,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
SubtitleMode = item.SubtitleMode SubtitleMode = item.SubtitleMode
}, },

1
ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs

@ -26,6 +26,7 @@ public record ReplaceProgramScheduleItem(
int? FallbackFillerId, int? FallbackFillerId,
int? WatermarkId, int? WatermarkId,
string PreferredAudioLanguageCode, string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode, string PreferredSubtitleLanguageCode,
ChannelSubtitleMode? SubtitleMode) : IProgramScheduleItemRequest; ChannelSubtitleMode? SubtitleMode) : IProgramScheduleItemRequest;

4
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -63,6 +63,7 @@ internal static class Mapper
? Watermarks.Mapper.ProjectToViewModel(duration.Watermark) ? Watermarks.Mapper.ProjectToViewModel(duration.Watermark)
: null, : null,
duration.PreferredAudioLanguageCode, duration.PreferredAudioLanguageCode,
duration.PreferredAudioTitle,
duration.PreferredSubtitleLanguageCode, duration.PreferredSubtitleLanguageCode,
duration.SubtitleMode), duration.SubtitleMode),
ProgramScheduleItemFlood flood => ProgramScheduleItemFlood flood =>
@ -110,6 +111,7 @@ internal static class Mapper
? Watermarks.Mapper.ProjectToViewModel(flood.Watermark) ? Watermarks.Mapper.ProjectToViewModel(flood.Watermark)
: null, : null,
flood.PreferredAudioLanguageCode, flood.PreferredAudioLanguageCode,
flood.PreferredAudioTitle,
flood.PreferredSubtitleLanguageCode, flood.PreferredSubtitleLanguageCode,
flood.SubtitleMode), flood.SubtitleMode),
ProgramScheduleItemMultiple multiple => ProgramScheduleItemMultiple multiple =>
@ -158,6 +160,7 @@ internal static class Mapper
? Watermarks.Mapper.ProjectToViewModel(multiple.Watermark) ? Watermarks.Mapper.ProjectToViewModel(multiple.Watermark)
: null, : null,
multiple.PreferredAudioLanguageCode, multiple.PreferredAudioLanguageCode,
multiple.PreferredAudioTitle,
multiple.PreferredSubtitleLanguageCode, multiple.PreferredSubtitleLanguageCode,
multiple.SubtitleMode), multiple.SubtitleMode),
ProgramScheduleItemOne one => ProgramScheduleItemOne one =>
@ -205,6 +208,7 @@ internal static class Mapper
? Watermarks.Mapper.ProjectToViewModel(one.Watermark) ? Watermarks.Mapper.ProjectToViewModel(one.Watermark)
: null, : null,
one.PreferredAudioLanguageCode, one.PreferredAudioLanguageCode,
one.PreferredAudioTitle,
one.PreferredSubtitleLanguageCode, one.PreferredSubtitleLanguageCode,
one.SubtitleMode), one.SubtitleMode),
_ => throw new NotSupportedException( _ => throw new NotSupportedException(

2
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs

@ -30,6 +30,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
FillerPresetViewModel fallbackFiller, FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark, WatermarkViewModel watermark,
string preferredAudioLanguageCode, string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode, string preferredSubtitleLanguageCode,
ChannelSubtitleMode? subtitleMode) : base( ChannelSubtitleMode? subtitleMode) : base(
id, id,
@ -52,6 +53,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
fallbackFiller, fallbackFiller,
watermark, watermark,
preferredAudioLanguageCode, preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode, preferredSubtitleLanguageCode,
subtitleMode) subtitleMode)
{ {

2
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs

@ -28,6 +28,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
FillerPresetViewModel fallbackFiller, FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark, WatermarkViewModel watermark,
string preferredAudioLanguageCode, string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode, string preferredSubtitleLanguageCode,
ChannelSubtitleMode? subtitleMode) : base( ChannelSubtitleMode? subtitleMode) : base(
id, id,
@ -50,6 +51,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
fallbackFiller, fallbackFiller,
watermark, watermark,
preferredAudioLanguageCode, preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode, preferredSubtitleLanguageCode,
subtitleMode) subtitleMode)
{ {

2
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs

@ -29,6 +29,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
FillerPresetViewModel fallbackFiller, FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark, WatermarkViewModel watermark,
string preferredAudioLanguageCode, string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode, string preferredSubtitleLanguageCode,
ChannelSubtitleMode? subtitleMode) : base( ChannelSubtitleMode? subtitleMode) : base(
id, id,
@ -51,6 +52,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
fallbackFiller, fallbackFiller,
watermark, watermark,
preferredAudioLanguageCode, preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode, preferredSubtitleLanguageCode,
subtitleMode) => subtitleMode) =>
Count = count; Count = count;

2
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs

@ -28,6 +28,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
FillerPresetViewModel fallbackFiller, FillerPresetViewModel fallbackFiller,
WatermarkViewModel watermark, WatermarkViewModel watermark,
string preferredAudioLanguageCode, string preferredAudioLanguageCode,
string preferredAudioTitle,
string preferredSubtitleLanguageCode, string preferredSubtitleLanguageCode,
ChannelSubtitleMode? subtitleMode) : base( ChannelSubtitleMode? subtitleMode) : base(
id, id,
@ -50,6 +51,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
fallbackFiller, fallbackFiller,
watermark, watermark,
preferredAudioLanguageCode, preferredAudioLanguageCode,
preferredAudioTitle,
preferredSubtitleLanguageCode, preferredSubtitleLanguageCode,
subtitleMode) subtitleMode)
{ {

1
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs

@ -27,6 +27,7 @@ public abstract record ProgramScheduleItemViewModel(
FillerPresetViewModel FallbackFiller, FillerPresetViewModel FallbackFiller,
WatermarkViewModel Watermark, WatermarkViewModel Watermark,
string PreferredAudioLanguageCode, string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode, string PreferredSubtitleLanguageCode,
ChannelSubtitleMode? SubtitleMode) ChannelSubtitleMode? SubtitleMode)
{ {

1
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -174,6 +174,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
audioPath, audioPath,
subtitles, subtitles,
playoutItemWithPath.PlayoutItem.PreferredAudioLanguageCode ?? channel.PreferredAudioLanguageCode, playoutItemWithPath.PlayoutItem.PreferredAudioLanguageCode ?? channel.PreferredAudioLanguageCode,
playoutItemWithPath.PlayoutItem.PreferredAudioTitle ?? channel.PreferredAudioTitle,
playoutItemWithPath.PlayoutItem.PreferredSubtitleLanguageCode ?? channel.PreferredSubtitleLanguageCode, playoutItemWithPath.PlayoutItem.PreferredSubtitleLanguageCode ?? channel.PreferredSubtitleLanguageCode,
playoutItemWithPath.PlayoutItem.SubtitleMode ?? channel.SubtitleMode, playoutItemWithPath.PlayoutItem.SubtitleMode ?? channel.SubtitleMode,
playoutItemWithPath.PlayoutItem.StartOffset, playoutItemWithPath.PlayoutItem.StartOffset,

2
ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj

@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.3.44"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.3.44">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

4
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -483,6 +483,7 @@ public class TranscodingTests
subtitles, subtitles,
string.Empty, string.Empty,
string.Empty, string.Empty,
string.Empty,
subtitleMode, subtitleMode,
now, now,
now + TimeSpan.FromSeconds(5), now + TimeSpan.FromSeconds(5),
@ -569,7 +570,8 @@ public class TranscodingTests
MediaVersion version, MediaVersion version,
StreamingMode streamingMode, StreamingMode streamingMode,
string channelNumber, string channelNumber,
string preferredAudioLanguage) => string preferredAudioLanguage,
string preferredAudioTitle) =>
Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask(); Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask();
public Task<Option<Domain.Subtitle>> SelectSubtitleStream( public Task<Option<Domain.Subtitle>> SelectSubtitleStream(

1
ErsatzTV.Core/Domain/Channel.cs

@ -23,6 +23,7 @@ public class Channel
public List<Playout> Playouts { get; set; } public List<Playout> Playouts { get; set; }
public List<Artwork> Artwork { get; set; } public List<Artwork> Artwork { get; set; }
public string PreferredAudioLanguageCode { get; set; } public string PreferredAudioLanguageCode { get; set; }
public string PreferredAudioTitle { get; set; }
public string PreferredSubtitleLanguageCode { get; set; } public string PreferredSubtitleLanguageCode { get; set; }
public ChannelSubtitleMode SubtitleMode { get; set; } public ChannelSubtitleMode SubtitleMode { get; set; }
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; } public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }

1
ErsatzTV.Core/Domain/PlayoutItem.cs

@ -24,6 +24,7 @@ public class PlayoutItem
public int? WatermarkId { get; set; } public int? WatermarkId { get; set; }
public bool DisableWatermarks { get; set; } public bool DisableWatermarks { get; set; }
public string PreferredAudioLanguageCode { get; set; } public string PreferredAudioLanguageCode { get; set; }
public string PreferredAudioTitle { get; set; }
public string PreferredSubtitleLanguageCode { get; set; } public string PreferredSubtitleLanguageCode { get; set; }
public ChannelSubtitleMode? SubtitleMode { get; set; } public ChannelSubtitleMode? SubtitleMode { get; set; }
public DateTimeOffset StartOffset => new DateTimeOffset(Start, TimeSpan.Zero).ToLocalTime(); public DateTimeOffset StartOffset => new DateTimeOffset(Start, TimeSpan.Zero).ToLocalTime();

1
ErsatzTV.Core/Domain/ProgramScheduleItem.cs

@ -35,6 +35,7 @@ public abstract class ProgramScheduleItem
public ChannelWatermark Watermark { get; set; } public ChannelWatermark Watermark { get; set; }
public int? WatermarkId { get; set; } public int? WatermarkId { get; set; }
public string PreferredAudioLanguageCode { get; set; } public string PreferredAudioLanguageCode { get; set; }
public string PreferredAudioTitle { get; set; }
public string PreferredSubtitleLanguageCode { get; set; } public string PreferredSubtitleLanguageCode { get; set; }
public ChannelSubtitleMode? SubtitleMode { get; set; } public ChannelSubtitleMode? SubtitleMode { get; set; }
} }

2
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.3.44"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.3.44">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

4
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -49,6 +49,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
string audioPath, string audioPath,
List<Subtitle> subtitles, List<Subtitle> subtitles,
string preferredAudioLanguage, string preferredAudioLanguage,
string preferredAudioTitle,
string preferredSubtitleLanguage, string preferredSubtitleLanguage,
ChannelSubtitleMode subtitleMode, ChannelSubtitleMode subtitleMode,
DateTimeOffset start, DateTimeOffset start,
@ -72,7 +73,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
audioVersion, audioVersion,
channel.StreamingMode, channel.StreamingMode,
channel.Number, channel.Number,
preferredAudioLanguage); preferredAudioLanguage,
preferredAudioTitle);
Option<Subtitle> maybeSubtitle = Option<Subtitle> maybeSubtitle =
await _ffmpegStreamSelector.SelectSubtitleStream( await _ffmpegStreamSelector.SelectSubtitleStream(
subtitles, subtitles,

45
ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs

@ -28,13 +28,14 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
MediaVersion version, MediaVersion version,
StreamingMode streamingMode, StreamingMode streamingMode,
string channelNumber, string channelNumber,
string preferredAudioLanguage) string preferredAudioLanguage,
string preferredAudioTitle)
{ {
if (streamingMode == StreamingMode.HttpLiveStreamingDirect && if (streamingMode == StreamingMode.HttpLiveStreamingDirect &&
string.IsNullOrWhiteSpace(preferredAudioLanguage)) string.IsNullOrWhiteSpace(preferredAudioLanguage) && string.IsNullOrWhiteSpace(preferredAudioTitle))
{ {
_logger.LogDebug( _logger.LogDebug(
"Channel {Number} is HLS Direct with no preferred audio language; using all audio streams", "Channel {Number} is HLS Direct with no preferred audio language or title; using all audio streams",
channelNumber); channelNumber);
return None; return None;
} }
@ -71,18 +72,18 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
if (correctLanguage.Any()) if (correctLanguage.Any())
{ {
_logger.LogDebug( _logger.LogDebug(
"Found {Count} audio streams with preferred audio language code(s) {Code}; selecting stream with most channels", "Found {Count} audio streams with preferred audio language code(s) {Code}",
correctLanguage.Count, correctLanguage.Count,
allCodes); allCodes);
return correctLanguage.OrderByDescending(s => s.Channels).Head(); return PrioritizeAudioTitle(correctLanguage, preferredAudioTitle ?? string.Empty);
} }
_logger.LogDebug( _logger.LogDebug(
"Unable to find audio stream with preferred audio language code(s) {Code}; selecting stream with most channels", "Unable to find audio stream with preferred audio language code(s) {Code}",
allCodes); allCodes);
return audioStreams.OrderByDescending(s => s.Channels).HeadOrNone(); return PrioritizeAudioTitle(audioStreams, preferredAudioTitle ?? string.Empty);
} }
public async Task<Option<Subtitle>> SelectSubtitleStream( public async Task<Option<Subtitle>> SelectSubtitleStream(
@ -163,4 +164,34 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
return None; return None;
} }
private Option<MediaStream> PrioritizeAudioTitle(IReadOnlyCollection<MediaStream> streams, string title)
{
// return correctLanguage.OrderByDescending(s => s.Channels).Head();
if (string.IsNullOrWhiteSpace(title))
{
_logger.LogDebug("No audio title has been specified; selecting stream with most channels");
return streams.OrderByDescending(s => s.Channels).Head();
}
// prioritize matching titles
var matchingTitle = streams
.Filter(ms => (ms.Title ?? string.Empty).Contains(title, StringComparison.OrdinalIgnoreCase))
.ToList();
if (matchingTitle.Any())
{
_logger.LogDebug(
"Found {Count} audio streams with preferred title {Title}; selecting stream with most channels",
matchingTitle.Count,
title);
return matchingTitle.OrderByDescending(s => s.Channels).Head();
}
_logger.LogDebug(
"Unable to find audio stream with preferred title {Title}; selecting stream with most channels",
title);
return streams.OrderByDescending(s => s.Channels).Head();
}
} }

1
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -19,6 +19,7 @@ public interface IFFmpegProcessService
string audioPath, string audioPath,
List<Subtitle> subtitles, List<Subtitle> subtitles,
string preferredAudioLanguage, string preferredAudioLanguage,
string preferredAudioTitle,
string preferredSubtitleLanguage, string preferredSubtitleLanguage,
ChannelSubtitleMode subtitleMode, ChannelSubtitleMode subtitleMode,
DateTimeOffset start, DateTimeOffset start,

3
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs

@ -10,7 +10,8 @@ public interface IFFmpegStreamSelector
MediaVersion version, MediaVersion version,
StreamingMode streamingMode, StreamingMode streamingMode,
string channelNumber, string channelNumber,
string preferredAudioLanguage); string preferredAudioLanguage,
string preferredAudioTitle);
Task<Option<Subtitle>> SelectSubtitleStream( Task<Option<Subtitle>> SelectSubtitleStream(
List<Subtitle> subtitles, List<Subtitle> subtitles,

1
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -81,6 +81,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
CustomTitle = scheduleItem.CustomTitle, CustomTitle = scheduleItem.CustomTitle,
WatermarkId = scheduleItem.WatermarkId, WatermarkId = scheduleItem.WatermarkId,
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode, PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode SubtitleMode = scheduleItem.SubtitleMode
}; };

1
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -62,6 +62,7 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
CustomTitle = scheduleItem.CustomTitle, CustomTitle = scheduleItem.CustomTitle,
WatermarkId = scheduleItem.WatermarkId, WatermarkId = scheduleItem.WatermarkId,
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode, PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode SubtitleMode = scheduleItem.SubtitleMode
}; };

1
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -70,6 +70,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
CustomTitle = scheduleItem.CustomTitle, CustomTitle = scheduleItem.CustomTitle,
WatermarkId = scheduleItem.WatermarkId, WatermarkId = scheduleItem.WatermarkId,
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode, PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode SubtitleMode = scheduleItem.SubtitleMode
}; };

1
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -50,6 +50,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
CustomTitle = scheduleItem.CustomTitle, CustomTitle = scheduleItem.CustomTitle,
WatermarkId = scheduleItem.WatermarkId, WatermarkId = scheduleItem.WatermarkId,
PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode, PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
SubtitleMode = scheduleItem.SubtitleMode SubtitleMode = scheduleItem.SubtitleMode
}; };

2
ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj

@ -9,7 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" /> <PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Moq" Version="4.18.2" /> <PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />

2
ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" /> <PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Moq" Version="4.18.2" /> <PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />

4303
ErsatzTV.Infrastructure/Migrations/20220830202251_Add_PreferredAudioTitle.Designer.cs generated

File diff suppressed because it is too large Load Diff

45
ErsatzTV.Infrastructure/Migrations/20220830202251_Add_PreferredAudioTitle.cs

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

11
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.7"); modelBuilder.HasAnnotation("ProductVersion", "6.0.8");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b => modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{ {
@ -245,6 +245,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("PreferredAudioLanguageCode") b.Property<string>("PreferredAudioLanguageCode")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("PreferredAudioTitle")
.HasColumnType("TEXT");
b.Property<string>("PreferredSubtitleLanguageCode") b.Property<string>("PreferredSubtitleLanguageCode")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -1425,6 +1428,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("PreferredAudioLanguageCode") b.Property<string>("PreferredAudioLanguageCode")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("PreferredAudioTitle")
.HasColumnType("TEXT");
b.Property<string>("PreferredSubtitleLanguageCode") b.Property<string>("PreferredSubtitleLanguageCode")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -1613,6 +1619,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("PreferredAudioLanguageCode") b.Property<string>("PreferredAudioLanguageCode")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("PreferredAudioTitle")
.HasColumnType("TEXT");
b.Property<string>("PreferredSubtitleLanguageCode") b.Property<string>("PreferredSubtitleLanguageCode")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

2
ErsatzTV/ErsatzTV.csproj

@ -55,7 +55,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" /> <PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
<PackageReference Include="FluentValidation" Version="11.2.0" /> <PackageReference Include="FluentValidation" Version="11.2.1" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.1" /> <PackageReference Include="FluentValidation.AspNetCore" Version="11.2.1" />
<PackageReference Include="HtmlSanitizer" Version="7.1.542" /> <PackageReference Include="HtmlSanitizer" Version="7.1.542" />
<PackageReference Include="LanguageExt.Core" Version="4.2.9" /> <PackageReference Include="LanguageExt.Core" Version="4.2.9" />

2
ErsatzTV/Pages/Artist.razor

@ -258,7 +258,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule) if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
{ {
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null), _cts.Token); await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items"); _navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
} }
} }

2
ErsatzTV/Pages/ChannelEditor.razor

@ -54,6 +54,7 @@
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem> <MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudTextField Label="Preferred Audio Title" @bind-Value="_model.PreferredAudioTitle" For="@(() => _model.PreferredAudioTitle)"/>
<MudSelect Class="mt-3" <MudSelect Class="mt-3"
Label="Preferred Subtitle Language" Label="Preferred Subtitle Language"
@bind-Value="_model.PreferredSubtitleLanguageCode" @bind-Value="_model.PreferredSubtitleLanguageCode"
@ -167,6 +168,7 @@
_model.Logo = channelViewModel.Logo; _model.Logo = channelViewModel.Logo;
_model.StreamingMode = channelViewModel.StreamingMode; _model.StreamingMode = channelViewModel.StreamingMode;
_model.PreferredAudioLanguageCode = channelViewModel.PreferredAudioLanguageCode; _model.PreferredAudioLanguageCode = channelViewModel.PreferredAudioLanguageCode;
_model.PreferredAudioTitle = channelViewModel.PreferredAudioTitle;
_model.WatermarkId = channelViewModel.WatermarkId; _model.WatermarkId = channelViewModel.WatermarkId;
_model.FallbackFillerId = channelViewModel.FallbackFillerId; _model.FallbackFillerId = channelViewModel.FallbackFillerId;
_model.PreferredSubtitleLanguageCode = channelViewModel.PreferredSubtitleLanguageCode; _model.PreferredSubtitleLanguageCode = channelViewModel.PreferredSubtitleLanguageCode;

3
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -299,6 +299,7 @@
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem> <MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudTextField Class="mt-3" Label="Preferred Audio Title" @bind-Value="@_selectedItem.PreferredAudioTitle" For="@(() => _selectedItem.PreferredAudioTitle)"/>
<MudSelect Class="mt-3" <MudSelect Class="mt-3"
Label="Preferred Subtitle Language" Label="Preferred Subtitle Language"
@bind-Value="_selectedItem.PreferredSubtitleLanguageCode" @bind-Value="_selectedItem.PreferredSubtitleLanguageCode"
@ -422,6 +423,7 @@
FallbackFiller = item.FallbackFiller, FallbackFiller = item.FallbackFiller,
Watermark = item.Watermark, Watermark = item.Watermark,
PreferredAudioLanguageCode = item.PreferredAudioLanguageCode, PreferredAudioLanguageCode = item.PreferredAudioLanguageCode,
PreferredAudioTitle = item.PreferredAudioTitle,
PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = item.PreferredSubtitleLanguageCode,
SubtitleMode = item.SubtitleMode SubtitleMode = item.SubtitleMode
}; };
@ -500,6 +502,7 @@
item.FallbackFiller?.Id, item.FallbackFiller?.Id,
item.Watermark?.Id, item.Watermark?.Id,
item.PreferredAudioLanguageCode, item.PreferredAudioLanguageCode,
item.PreferredAudioTitle,
item.PreferredSubtitleLanguageCode, item.PreferredSubtitleLanguageCode,
item.SubtitleMode)).ToList(); item.SubtitleMode)).ToList();

2
ErsatzTV/Pages/TelevisionEpisodeList.razor

@ -233,7 +233,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule) if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
{ {
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null), _cts.Token); await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, SeasonId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items"); _navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
} }
} }

2
ErsatzTV/Pages/TelevisionSeasonList.razor

@ -228,7 +228,7 @@
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule) if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule)
{ {
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null), _cts.Token); await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token);
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items"); _navigationManager.NavigateTo($"/schedules/{schedule.Id}/items");
} }
} }

3
ErsatzTV/ViewModels/ChannelEditViewModel.cs

@ -12,6 +12,7 @@ public class ChannelEditViewModel
public string Number { get; set; } public string Number { get; set; }
public int FFmpegProfileId { get; set; } public int FFmpegProfileId { get; set; }
public string PreferredAudioLanguageCode { get; set; } public string PreferredAudioLanguageCode { get; set; }
public string PreferredAudioTitle { get; set; }
public string Logo { get; set; } public string Logo { get; set; }
public StreamingMode StreamingMode { get; set; } public StreamingMode StreamingMode { get; set; }
public int? WatermarkId { get; set; } public int? WatermarkId { get; set; }
@ -30,6 +31,7 @@ public class ChannelEditViewModel
FFmpegProfileId, FFmpegProfileId,
Logo, Logo,
PreferredAudioLanguageCode, PreferredAudioLanguageCode,
PreferredAudioTitle,
StreamingMode, StreamingMode,
WatermarkId, WatermarkId,
FallbackFillerId, FallbackFillerId,
@ -46,6 +48,7 @@ public class ChannelEditViewModel
FFmpegProfileId, FFmpegProfileId,
Logo, Logo,
PreferredAudioLanguageCode, PreferredAudioLanguageCode,
PreferredAudioTitle,
StreamingMode, StreamingMode,
WatermarkId, WatermarkId,
FallbackFillerId, FallbackFillerId,

1
ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs

@ -66,6 +66,7 @@ public class ProgramScheduleItemEditViewModel : INotifyPropertyChanged
public FillerPresetViewModel FallbackFiller { get; set; } public FillerPresetViewModel FallbackFiller { get; set; }
public WatermarkViewModel Watermark { get; set; } public WatermarkViewModel Watermark { get; set; }
public string PreferredAudioLanguageCode { get; set; } public string PreferredAudioLanguageCode { get; set; }
public string PreferredAudioTitle { get; set; }
public string PreferredSubtitleLanguageCode { get; set; } public string PreferredSubtitleLanguageCode { get; set; }
public ChannelSubtitleMode? SubtitleMode { get; set; } public ChannelSubtitleMode? SubtitleMode { get; set; }

11
ErsatzTV/client-app/src/views/ChannelsPage.vue

@ -7,10 +7,13 @@
class="elevation-1" class="elevation-1"
> >
<template v-slot:[`item.actions`]="{ item }"> <template v-slot:[`item.actions`]="{ item }">
<v-icon small class="mr-2" @click="editRow(item.id)"> <v-btn icon class="mr-2" @click="editRow(item.id)">
mdi-lead-pencil <v-icon>mdi-lead-pencil</v-icon>
</v-icon> </v-btn>
<v-icon small @click="deleteRow(item.id)">mdi-delete</v-icon>
<v-btn icon @click.stop="deleteRow(item.id)">
<v-icon>mdi-delete</v-icon></v-btn
>
</template> </template>
</v-data-table> </v-data-table>
</div> </div>

Loading…
Cancel
Save