Browse Source

add optional progress bar to generated song videos (#1945)

* optionally include progress bar in generated song video

* update progress bar size/location

* move everything up 10% when song progress is enabled

* add watermark border to song progress bar

* always show accurate progress bar

* lower progress bar to 90% alpha

* update changelog
pull/1946/head
Jason Dove 9 months ago committed by GitHub
parent
commit
c8ec87b01f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/Channels/ChannelViewModel.cs
  3. 3
      ErsatzTV.Application/Channels/Commands/CreateChannel.cs
  4. 3
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  5. 3
      ErsatzTV.Application/Channels/Commands/UpdateChannel.cs
  6. 1
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  7. 3
      ErsatzTV.Application/Channels/Mapper.cs
  8. 19
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  9. 1
      ErsatzTV.Core/Domain/Channel.cs
  10. 7
      ErsatzTV.Core/Domain/ChannelSongVideoMode.cs
  11. 4
      ErsatzTV.Core/Domain/ChannelWatermark.cs
  12. 20
      ErsatzTV.Core/Domain/MediaItem/BackgroundImageMediaVersion.cs
  13. 9
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  14. 7
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  15. 16
      ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs
  16. 12
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  17. 6
      ErsatzTV.FFmpeg/FFmpegState.cs
  18. 32
      ErsatzTV.FFmpeg/Filter/SongProgressFilter.cs
  19. 2
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  20. 11
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  21. 2
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  22. 2
      ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs
  23. 2
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
  24. 5872
      ErsatzTV.Infrastructure.MySql/Migrations/20241120195710_Add_Channel_SongVideoMode.Designer.cs
  25. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20241120195710_Add_Channel_SongVideoMode.cs
  26. 5
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  27. 5711
      ErsatzTV.Infrastructure.Sqlite/Migrations/20241120195625_Add_Channel_SongVideoMode.Designer.cs
  28. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20241120195625_Add_Channel_SongVideoMode.cs
  29. 5
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  30. 1
      ErsatzTV/ErsatzTV.csproj
  31. 5
      ErsatzTV/Pages/ChannelEditor.razor
  32. BIN
      ErsatzTV/Resources/song_progress_overlay.png
  33. 1
      ErsatzTV/Services/RunOnce/ResourceExtractorService.cs
  34. 8
      ErsatzTV/ViewModels/ChannelEditViewModel.cs

3
CHANGELOG.md

@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `advance` option to `epg_group` YAML playout instruction
- When set to `false`, this option will lock the guide group without starting a new guide group
- This can be helpful for "post roll" items that should be part of the previous item's guide group
- Add `Song Video Mode` to channel settings
- `Default` - existing behavior
- `With Progress` - show animated progress bar at bottom of generated video
### Changed
- **BREAKING CHANGE**: Change channel identifiers used in XMLTV to work around bad behavior in Plex

3
ErsatzTV.Application/Channels/ChannelViewModel.cs

@ -21,7 +21,8 @@ public record ChannelViewModel( @@ -21,7 +21,8 @@ public record ChannelViewModel(
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate)
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode)
{
public string WebEncodedName => WebUtility.UrlEncode(Name);
}

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

@ -19,4 +19,5 @@ public record CreateChannel( @@ -19,4 +19,5 @@ public record CreateChannel(
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate) : IRequest<Either<BaseError, CreateChannelResult>>;
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode) : IRequest<Either<BaseError, CreateChannelResult>>;

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

@ -76,7 +76,8 @@ public class CreateChannelHandler( @@ -76,7 +76,8 @@ public class CreateChannelHandler(
PreferredSubtitleLanguageCode = request.PreferredSubtitleLanguageCode,
SubtitleMode = request.SubtitleMode,
MusicVideoCreditsMode = request.MusicVideoCreditsMode,
MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate
MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate,
SongVideoMode = request.SongVideoMode
};
foreach (int id in watermarkId)

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

@ -20,4 +20,5 @@ public record UpdateChannel( @@ -20,4 +20,5 @@ public record UpdateChannel(
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate) : IRequest<Either<BaseError, ChannelViewModel>>;
string MusicVideoCreditsTemplate,
ChannelSongVideoMode SongVideoMode) : IRequest<Either<BaseError, ChannelViewModel>>;

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

@ -41,6 +41,7 @@ public class UpdateChannelHandler( @@ -41,6 +41,7 @@ public class UpdateChannelHandler(
c.SubtitleMode = update.SubtitleMode;
c.MusicVideoCreditsMode = update.MusicVideoCreditsMode;
c.MusicVideoCreditsTemplate = update.MusicVideoCreditsTemplate;
c.SongVideoMode = update.SongVideoMode;
c.Artwork ??= new List<Artwork>();
if (!string.IsNullOrWhiteSpace(update.Logo))

3
ErsatzTV.Application/Channels/Mapper.cs

@ -24,7 +24,8 @@ internal static class Mapper @@ -24,7 +24,8 @@ internal static class Mapper
channel.PreferredSubtitleLanguageCode,
channel.SubtitleMode,
channel.MusicVideoCreditsMode,
channel.MusicVideoCreditsTemplate);
channel.MusicVideoCreditsTemplate,
channel.SongVideoMode);
internal static ChannelResponseModel ProjectToResponseModel(Channel channel) =>
new(

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

@ -16,6 +16,7 @@ using ErsatzTV.Core.Interfaces.Plex; @@ -16,6 +16,7 @@ using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Streaming;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.FFmpeg.State;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
@ -256,6 +257,24 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -256,6 +257,24 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
ffmpegPath,
ffprobePath,
cancellationToken);
// override watermark as song_progress_overlay.png
if (videoVersion is BackgroundImageMediaVersion { IsSongWithProgress: true })
{
disableWatermarks = false;
playoutItemWatermark = new ChannelWatermark
{
Mode = ChannelWatermarkMode.Permanent,
Size = WatermarkSize.Scaled,
WidthPercent = 100,
HorizontalMarginPercent = 0,
VerticalMarginPercent = 0,
Opacity = 100,
Location = WatermarkLocation.TopLeft,
ImageSource = ChannelWatermarkImageSource.Resource,
Image = "song_progress_overlay.png"
};
}
}
if (playoutItemWithPath.PlayoutItem.MediaItem is Image)

1
ErsatzTV.Core/Domain/Channel.cs

@ -29,6 +29,7 @@ public class Channel @@ -29,6 +29,7 @@ public class Channel
public ChannelSubtitleMode SubtitleMode { get; set; }
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }
public string MusicVideoCreditsTemplate { get; set; }
public ChannelSongVideoMode SongVideoMode { get; set; }
public ChannelProgressMode ProgressMode { get; set; }
public string WebEncodedName => WebUtility.UrlEncode(Name);
}

7
ErsatzTV.Core/Domain/ChannelSongVideoMode.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace ErsatzTV.Core.Domain;
public enum ChannelSongVideoMode
{
Default = 0,
WithProgress = 1
}

4
ErsatzTV.Core/Domain/ChannelWatermark.cs

@ -30,5 +30,7 @@ public enum ChannelWatermarkMode @@ -30,5 +30,7 @@ public enum ChannelWatermarkMode
public enum ChannelWatermarkImageSource
{
Custom = 0,
ChannelLogo = 1
ChannelLogo = 1,
Resource = 100
}

20
ErsatzTV.Core/Domain/MediaItem/BackgroundImageMediaVersion.cs

@ -5,27 +5,27 @@ namespace ErsatzTV.Core.Domain; @@ -5,27 +5,27 @@ namespace ErsatzTV.Core.Domain;
public class BackgroundImageMediaVersion : MediaVersion
{
public static BackgroundImageMediaVersion ForPath(string path, IDisplaySize resolution) =>
public static BackgroundImageMediaVersion ForPath(string path, IDisplaySize resolution, bool isSongWithProgress = false) =>
new()
{
Chapters = new List<MediaChapter>(),
Chapters = [],
// image has been pre-generated with correct size
Height = resolution.Height,
Width = resolution.Width,
SampleAspectRatio = "1:1",
Streams = new List<MediaStream>
{
new()
Streams =
[
new MediaStream
{
MediaStreamKind = MediaStreamKind.Video,
Index = 0,
Codec = VideoFormat.GeneratedImage,
PixelFormat = new PixelFormatUnknown().Name // the resulting pixel format is unknown
}
},
MediaFiles = new List<MediaFile>
{
new() { Path = path }
}
],
MediaFiles = [new MediaFile { Path = path }],
IsSongWithProgress = isSongWithProgress
};
public bool IsSongWithProgress { get; private set; }
}

9
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -396,7 +396,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -396,7 +396,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
hlsSegmentTemplate,
ptsOffset,
playbackSettings.ThreadCount,
qsvExtraHardwareFrames);
qsvExtraHardwareFrames,
videoVersion is BackgroundImageMediaVersion { IsSongWithProgress: true });
_logger.LogDebug("FFmpeg desired state {FrameState}", desiredState);
@ -547,7 +548,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -547,7 +548,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
hlsSegmentTemplate,
ptsOffset,
Option<int>.None,
qsvExtraHardwareFrames);
qsvExtraHardwareFrames,
IsSongWithProgress: false);
var ffmpegSubtitleStream = new ErsatzTV.FFmpeg.MediaStream(0, "ass", StreamKind.Video);
@ -737,7 +739,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -737,7 +739,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
hlsSegmentTemplate,
0,
playbackSettings.ThreadCount,
Optional(channel.FFmpegProfile.QsvExtraHardwareFrames));
Optional(channel.FFmpegProfile.QsvExtraHardwareFrames),
IsSongWithProgress: false);
_logger.LogDebug("FFmpeg desired state {FrameState}", desiredState);

7
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -179,6 +179,13 @@ public class FFmpegProcessService @@ -179,6 +179,13 @@ public class FFmpegProcessService
{
switch (watermark.ImageSource)
{
// used for song progress overlay
case ChannelWatermarkImageSource.Resource:
return new WatermarkOptions(
await watermarkOverride.IfNoneAsync(watermark),
Path.Combine(FileSystemLayout.ResourcesCacheFolder, watermark.Image),
Option<int>.None,
false);
case ChannelWatermarkImageSource.Custom:
string customPath = _imageCache.GetPathForImage(
watermark.Image,

16
ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs

@ -64,12 +64,17 @@ public class SongVideoGenerator : ISongVideoGenerator @@ -64,12 +64,17 @@ public class SongVideoGenerator : ISongVideoGenerator
var boxBlur = false;
const int HORIZONTAL_MARGIN_PERCENT = 3;
const int VERTICAL_MARGIN_PERCENT = 5;
var verticalMarginPercent = 5;
const int WATERMARK_WIDTH_PERCENT = 25;
WatermarkLocation watermarkLocation = NextRandom(2) == 0
? WatermarkLocation.BottomLeft
: WatermarkLocation.BottomRight;
if (channel.SongVideoMode is ChannelSongVideoMode.WithProgress)
{
verticalMarginPercent += 10;
}
foreach (SongMetadata metadata in song.SongMetadata)
{
var fontSize = (int)Math.Round(channel.FFmpegProfile.Resolution.Height / 20.0);
@ -138,7 +143,7 @@ public class SongVideoGenerator : ISongVideoGenerator @@ -138,7 +143,7 @@ public class SongVideoGenerator : ISongVideoGenerator
var leftMargin = (int)Math.Round(leftMarginPercent / 100.0 * channel.FFmpegProfile.Resolution.Width);
var rightMargin = (int)Math.Round(rightMarginPercent / 100.0 * channel.FFmpegProfile.Resolution.Width);
var verticalMargin =
(int)Math.Round(VERTICAL_MARGIN_PERCENT / 100.0 * channel.FFmpegProfile.Resolution.Height);
(int)Math.Round(verticalMarginPercent / 100.0 * channel.FFmpegProfile.Resolution.Height);
subtitleFile = await new SubtitleBuilder(_tempFilePool)
.WithResolution(channel.FFmpegProfile.Resolution)
@ -225,14 +230,17 @@ public class SongVideoGenerator : ISongVideoGenerator @@ -225,14 +230,17 @@ public class SongVideoGenerator : ISongVideoGenerator
watermarkPath,
watermarkLocation,
HORIZONTAL_MARGIN_PERCENT,
VERTICAL_MARGIN_PERCENT,
verticalMarginPercent,
WATERMARK_WIDTH_PERCENT,
cancellationToken);
foreach (string si in maybeSongImage.RightToSeq())
{
videoPath = si;
videoVersion = BackgroundImageMediaVersion.ForPath(si, channel.FFmpegProfile.Resolution);
videoVersion = BackgroundImageMediaVersion.ForPath(
si,
channel.FFmpegProfile.Resolution,
isSongWithProgress: channel.SongVideoMode is ChannelSongVideoMode.WithProgress);
}
return Tuple(videoPath, videoVersion);

12
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -92,7 +92,8 @@ public class PipelineBuilderBaseTests @@ -92,7 +92,8 @@ public class PipelineBuilderBaseTests
Option<string>.None,
0,
Option<int>.None,
Option<int>.None);
Option<int>.None,
false);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
@ -185,7 +186,8 @@ public class PipelineBuilderBaseTests @@ -185,7 +186,8 @@ public class PipelineBuilderBaseTests
Option<string>.None,
0,
Option<int>.None,
Option<int>.None);
Option<int>.None,
false);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
@ -334,7 +336,8 @@ public class PipelineBuilderBaseTests @@ -334,7 +336,8 @@ public class PipelineBuilderBaseTests
Option<string>.None,
0,
Option<int>.None,
Option<int>.None);
Option<int>.None,
false);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
@ -421,7 +424,8 @@ public class PipelineBuilderBaseTests @@ -421,7 +424,8 @@ public class PipelineBuilderBaseTests
Option<string>.None,
0,
Option<int>.None,
Option<int>.None);
Option<int>.None,
false);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),

6
ErsatzTV.FFmpeg/FFmpegState.cs

@ -21,7 +21,8 @@ public record FFmpegState( @@ -21,7 +21,8 @@ public record FFmpegState(
Option<string> HlsSegmentTemplate,
long PtsOffset,
Option<int> ThreadCount,
Option<int> MaybeQsvExtraHardwareFrames)
Option<int> MaybeQsvExtraHardwareFrames,
bool IsSongWithProgress)
{
public int QsvExtraHardwareFrames => MaybeQsvExtraHardwareFrames.IfNone(64);
@ -45,5 +46,6 @@ public record FFmpegState( @@ -45,5 +46,6 @@ public record FFmpegState(
Option<string>.None,
0,
Option<int>.None,
Option<int>.None);
Option<int>.None,
false);
}

32
ErsatzTV.FFmpeg/Filter/SongProgressFilter.cs

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
namespace ErsatzTV.FFmpeg.Filter;
public class SongProgressFilter(FrameSize frameSize, Option<TimeSpan> maybeStart, Option<TimeSpan> maybeDuration) : BaseFilter
{
public override string Filter
{
get
{
foreach (TimeSpan duration in maybeDuration)
{
TimeSpan start = maybeStart.IfNone(TimeSpan.Zero);
TimeSpan finish = start + duration;
double width = frameSize.Width * 0.9;
double height = frameSize.Height * 0.025;
double seconds = duration.TotalSeconds;
double alreadyPlayed = start.TotalSeconds / finish.TotalSeconds;
double scale = 1 - alreadyPlayed;
var generateWhiteBar = $"color=c=white:s={width}x{height}";
var scaleToFullWidth = $"scale=iw*{alreadyPlayed}+iw*(t/{seconds})*{scale}:ih:eval=frame";
var overlayBar = "overlay=W*0.05:H-h-H*0.05:shortest=1:enable='gt(t,0.1)'";
return $"loop=-1:1[si],{generateWhiteBar},format=rgba,colorchannelmixer=aa=0.9,{scaleToFullWidth}[sbar];[si][sbar]{overlayBar}";
}
return string.Empty;
}
}
public override FrameState NextState(FrameState currentState) => currentState;
}

2
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -170,7 +170,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -170,7 +170,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
currentState = SetScale(videoInputFile, videoStream, context, ffmpegState, desiredState, currentState);
currentState = SetPad(videoInputFile, videoStream, desiredState, currentState);
currentState = SetCrop(videoInputFile, desiredState, currentState);
SetStillImageLoop(videoInputFile, videoStream, desiredState, pipelineSteps);
SetStillImageLoop(videoInputFile, videoStream, ffmpegState, desiredState, pipelineSteps);
if (currentState.BitDepth == 8 && context.HasSubtitleOverlay || context.HasWatermark)
{

11
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -733,12 +733,21 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -733,12 +733,21 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
protected static void SetStillImageLoop(
VideoInputFile videoInputFile,
VideoStream videoStream,
FFmpegState ffmpegState,
FrameState desiredState,
ICollection<IPipelineStep> pipelineSteps)
{
if (videoStream.StillImage)
{
videoInputFile.FilterSteps.Add(new LoopFilter());
if (ffmpegState.IsSongWithProgress)
{
videoInputFile.FilterSteps.Add(new SongProgressFilter(videoStream.FrameSize, ffmpegState.Start, ffmpegState.Finish));
}
else
{
videoInputFile.FilterSteps.Add(new LoopFilter());
}
if (desiredState.Realtime)
{
videoInputFile.FilterSteps.Add(new RealtimeFilter());

2
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -175,7 +175,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder @@ -175,7 +175,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
currentState = SetPad(videoInputFile, videoStream, desiredState, currentState);
// _logger.LogDebug("After pad: {PixelFormat}", currentState.PixelFormat);
currentState = SetCrop(videoInputFile, desiredState, currentState);
SetStillImageLoop(videoInputFile, videoStream, desiredState, pipelineSteps);
SetStillImageLoop(videoInputFile, videoStream, ffmpegState, desiredState, pipelineSteps);
// need to download for any sort of overlay
if (currentState.FrameDataLocation == FrameDataLocation.Hardware &&

2
ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs

@ -107,7 +107,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase @@ -107,7 +107,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase
currentState = SetScale(videoInputFile, videoStream, desiredState, currentState);
currentState = SetPad(videoInputFile, videoStream, desiredState, currentState);
currentState = SetCrop(videoInputFile, desiredState, currentState);
SetStillImageLoop(videoInputFile, videoStream, desiredState, pipelineSteps);
SetStillImageLoop(videoInputFile, videoStream, ffmpegState, desiredState, pipelineSteps);
SetSubtitle(
videoInputFile,
subtitleInputFile,

2
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -179,7 +179,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder @@ -179,7 +179,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
currentState = SetCrop(videoInputFile, desiredState, currentState);
SetStillImageLoop(videoInputFile, videoStream, desiredState, pipelineSteps);
SetStillImageLoop(videoInputFile, videoStream, ffmpegState, desiredState, pipelineSteps);
// need to upload for hardware overlay
bool forceSoftwareOverlay = context is { HasSubtitleOverlay: true, HasWatermark: true }

5872
ErsatzTV.Infrastructure.MySql/Migrations/20241120195710_Add_Channel_SongVideoMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20241120195710_Add_Channel_SongVideoMode.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_Channel_SongVideoMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "SongVideoMode",
table: "Channel",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SongVideoMode",
table: "Channel");
}
}
}

5
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -17,7 +17,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -17,7 +17,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.7")
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
@ -280,6 +280,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -280,6 +280,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("ProgressMode")
.HasColumnType("int");
b.Property<int>("SongVideoMode")
.HasColumnType("int");
b.Property<int>("StreamingMode")
.HasColumnType("int");

5711
ErsatzTV.Infrastructure.Sqlite/Migrations/20241120195625_Add_Channel_SongVideoMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20241120195625_Add_Channel_SongVideoMode.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_Channel_SongVideoMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "SongVideoMode",
table: "Channel",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SongVideoMode",
table: "Channel");
}
}
}

5
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
modelBuilder.HasAnnotation("ProductVersion", "8.0.10");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{
@ -267,6 +267,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -267,6 +267,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("ProgressMode")
.HasColumnType("INTEGER");
b.Property<int>("SongVideoMode")
.HasColumnType("INTEGER");
b.Property<int>("StreamingMode")
.HasColumnType("INTEGER");

1
ErsatzTV/ErsatzTV.csproj

@ -71,6 +71,7 @@ @@ -71,6 +71,7 @@
<EmbeddedResource Include="Resources\Scripts\_threePartEpisodes.js" />
<EmbeddedResource Include="Resources\Scripts\_episode.js" />
<EmbeddedResource Include="Resources\Scripts\_movie.js" />
<EmbeddedResource Include="Resources\song_progress_overlay.png" />
<EmbeddedResource Include="Resources\song_background_1.png" />
<EmbeddedResource Include="Resources\song_background_2.png" />
<EmbeddedResource Include="Resources\song_background_3.png" />

5
ErsatzTV/Pages/ChannelEditor.razor

@ -92,6 +92,10 @@ @@ -92,6 +92,10 @@
<MudSelectItem T="string" Value="@template">@template</MudSelectItem>
}
</MudSelect>
<MudSelect Class="mt-3" Label="Song Video Mode" @bind-Value="_model.SongVideoMode" For="@(() => _model.SongVideoMode)">
<MudSelectItem Value="@(ChannelSongVideoMode.Default)">Default</MudSelectItem>
<MudSelectItem Value="@(ChannelSongVideoMode.WithProgress)">With Progress</MudSelectItem>
</MudSelect>
<MudGrid Class="mt-3" Style="align-items: center" Justify="Justify.Center">
<MudItem xs="6">
<InputFile id="fileInput" OnChange="UploadLogo" hidden/>
@ -194,6 +198,7 @@ @@ -194,6 +198,7 @@
_model.SubtitleMode = channelViewModel.SubtitleMode;
_model.MusicVideoCreditsMode = channelViewModel.MusicVideoCreditsMode;
_model.MusicVideoCreditsTemplate = channelViewModel.MusicVideoCreditsTemplate;
_model.SongVideoMode = channelViewModel.SongVideoMode;
},
() => NavigationManager.NavigateTo("404"));
}

BIN
ErsatzTV/Resources/song_progress_overlay.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

1
ErsatzTV/Services/RunOnce/ResourceExtractorService.cs

@ -17,6 +17,7 @@ public class ResourceExtractorService : BackgroundService @@ -17,6 +17,7 @@ public class ResourceExtractorService : BackgroundService
Assembly assembly = typeof(ResourceExtractorService).GetTypeInfo().Assembly;
await ExtractResource(assembly, "background.png", stoppingToken);
await ExtractResource(assembly, "song_progress_overlay.png", stoppingToken);
await ExtractResource(assembly, "song_background_1.png", stoppingToken);
await ExtractResource(assembly, "song_background_2.png", stoppingToken);
await ExtractResource(assembly, "song_background_3.png", stoppingToken);

8
ErsatzTV/ViewModels/ChannelEditViewModel.cs

@ -22,7 +22,6 @@ public class ChannelEditViewModel @@ -22,7 +22,6 @@ public class ChannelEditViewModel
public string PreferredSubtitleLanguageCode { get; set; }
public ChannelSubtitleMode SubtitleMode { get; set; }
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }
public string MusicVideoCreditsTemplate
{
get => MusicVideoCreditsMode == ChannelMusicVideoCreditsMode.GenerateSubtitles
@ -30,6 +29,7 @@ public class ChannelEditViewModel @@ -30,6 +29,7 @@ public class ChannelEditViewModel
: null;
set => _musicVideoCreditsTemplate = value;
}
public ChannelSongVideoMode SongVideoMode { get; set; }
public UpdateChannel ToUpdate() =>
new(
@ -49,7 +49,8 @@ public class ChannelEditViewModel @@ -49,7 +49,8 @@ public class ChannelEditViewModel
PreferredSubtitleLanguageCode,
SubtitleMode,
MusicVideoCreditsMode,
MusicVideoCreditsTemplate);
MusicVideoCreditsTemplate,
SongVideoMode);
public CreateChannel ToCreate() =>
new(
@ -68,5 +69,6 @@ public class ChannelEditViewModel @@ -68,5 +69,6 @@ public class ChannelEditViewModel
PreferredSubtitleLanguageCode,
SubtitleMode,
MusicVideoCreditsMode,
MusicVideoCreditsTemplate);
MusicVideoCreditsTemplate,
SongVideoMode);
}

Loading…
Cancel
Save