Browse Source

Support individual chapters as filler (#2208)

* Use chapters in duration filler

* add new option, migrations, and update filler preset editor

* Revert "Use chapters in duration filler"

This reverts commit d87a8a240a78c1cbca7b311125f8d3a84645d296.

* scaffold splitting filler by chapter

* implement chapters as filler

* update changelog

* re-add migrations

* Add duration for ChapterMediaItem

---------

Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
pull/2248/head
Chris Simpson 10 months ago committed by GitHub
parent
commit
48f93b8af8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/Filler/Commands/CreateFillerPreset.cs
  3. 3
      ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs
  4. 3
      ErsatzTV.Application/Filler/Commands/UpdateFillerPreset.cs
  5. 1
      ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs
  6. 3
      ErsatzTV.Application/Filler/FillerPresetViewModel.cs
  7. 3
      ErsatzTV.Application/Filler/Mapper.cs
  8. 1
      ErsatzTV.Core/Domain/Filler/FillerPreset.cs
  9. 14
      ErsatzTV.Core/Domain/MediaItem/ChapterMediaItem.cs
  10. 14
      ErsatzTV.Core/Domain/MediaItem/ChapterMediaVersion.cs
  11. 2
      ErsatzTV.Core/Extensions/MediaItemExtensions.cs
  12. 105
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  13. 52
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  14. 6163
      ErsatzTV.Infrastructure.MySql/Migrations/20250803211857_FillerPreset_UseChaptersAsMediaItems.Designer.cs
  15. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20250803211857_FillerPreset_UseChaptersAsMediaItems.cs
  16. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  17. 6000
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250803211925_FillerPreset_UseChaptersAsMediaItems.Designer.cs
  18. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250803211925_FillerPreset_UseChaptersAsMediaItems.cs
  19. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  20. 12
      ErsatzTV/Pages/FillerPresetEditor.razor
  21. 3
      ErsatzTV/Validators/FillerPresetEditViewModelValidator.cs
  22. 13
      ErsatzTV/ViewModels/FillerPresetEditViewModel.cs

3
CHANGELOG.md

@ -40,6 +40,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -40,6 +40,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Detect supported VideoToolbox hardware decoders and encoders
- Software decoders/encoders will automatically be used when hardware versions are unavailable
- Add VideoToolbox Capabilities to Troubleshooting page
- Add `Use Chapters As Media Items` option to filler preset
- This option allows scheduling individual chapters as filler
- The chapters are shuffled or otherwise sorted together just like normal filler would be
### Fixed
- Fix app startup with MySql/MariaDB

3
ErsatzTV.Application/Filler/Commands/CreateFillerPreset.cs

@ -18,5 +18,6 @@ public record CreateFillerPreset( @@ -18,5 +18,6 @@ public record CreateFillerPreset(
int? MultiCollectionId,
int? SmartCollectionId,
int? PlaylistId,
string Expression
string Expression,
bool UseChaptersAsMediaItems
) : IRequest<Either<BaseError, Unit>>;

3
ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs

@ -38,7 +38,8 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit @@ -38,7 +38,8 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit
MultiCollectionId = request.MultiCollectionId,
SmartCollectionId = request.SmartCollectionId,
PlaylistId = request.PlaylistId,
Expression = request.FillerKind is FillerKind.MidRoll ? request.Expression : null
Expression = request.FillerKind is FillerKind.MidRoll ? request.Expression : null,
UseChaptersAsMediaItems = request.FillerKind is not FillerKind.Fallback && request.UseChaptersAsMediaItems
};
await dbContext.FillerPresets.AddAsync(fillerPreset, cancellationToken);

3
ErsatzTV.Application/Filler/Commands/UpdateFillerPreset.cs

@ -19,5 +19,6 @@ public record UpdateFillerPreset( @@ -19,5 +19,6 @@ public record UpdateFillerPreset(
int? MultiCollectionId,
int? SmartCollectionId,
int? PlaylistId,
string Expression
string Expression,
bool UseChaptersAsMediaItems
) : IRequest<Either<BaseError, Unit>>;

1
ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs

@ -39,6 +39,7 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit @@ -39,6 +39,7 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit
existing.SmartCollectionId = request.SmartCollectionId;
existing.PlaylistId = request.PlaylistId;
existing.Expression = request.FillerKind is FillerKind.MidRoll ? request.Expression : null;
existing.UseChaptersAsMediaItems = request.FillerKind is not FillerKind.Fallback && request.UseChaptersAsMediaItems;
await dbContext.SaveChangesAsync();

3
ErsatzTV.Application/Filler/FillerPresetViewModel.cs

@ -19,4 +19,5 @@ public record FillerPresetViewModel( @@ -19,4 +19,5 @@ public record FillerPresetViewModel(
int? MultiCollectionId,
int? SmartCollectionId,
PlaylistViewModel Playlist,
string Expression);
string Expression,
bool UseChaptersAsMediaItems);

3
ErsatzTV.Application/Filler/Mapper.cs

@ -22,5 +22,6 @@ internal static class Mapper @@ -22,5 +22,6 @@ internal static class Mapper
fillerPreset.Playlist is not null
? MediaCollections.Mapper.ProjectToViewModel(fillerPreset.Playlist)
: null,
fillerPreset.Expression);
fillerPreset.Expression,
fillerPreset.UseChaptersAsMediaItems);
}

1
ErsatzTV.Core/Domain/Filler/FillerPreset.cs

@ -22,4 +22,5 @@ public class FillerPreset @@ -22,4 +22,5 @@ public class FillerPreset
public int? PlaylistId { get; set; }
public Playlist Playlist { get; set; }
public string Expression { get; set; }
public bool UseChaptersAsMediaItems { get; set; }
}

14
ErsatzTV.Core/Domain/MediaItem/ChapterMediaItem.cs

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
namespace ErsatzTV.Core.Domain;
public class ChapterMediaItem : MediaItem
{
public ChapterMediaItem(int id, MediaItem mediaItem, ChapterMediaVersion chapterMediaVersion)
{
Id = id;
MediaItemId = mediaItem.Id;
MediaVersion = chapterMediaVersion;
}
public int MediaItemId { get; }
public ChapterMediaVersion MediaVersion { get; }
}

14
ErsatzTV.Core/Domain/MediaItem/ChapterMediaVersion.cs

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
namespace ErsatzTV.Core.Domain;
public class ChapterMediaVersion : MediaVersion
{
public ChapterMediaVersion(MediaChapter chapter)
{
InPoint = chapter.StartTime;
Duration = chapter.EndTime - chapter.StartTime;
Title = chapter.Title;
}
public TimeSpan InPoint { get; }
public string Title { get; }
}

2
ErsatzTV.Core/Extensions/MediaItemExtensions.cs

@ -16,6 +16,7 @@ public static class MediaItemExtensions @@ -16,6 +16,7 @@ public static class MediaItemExtensions
MusicVideo mv => mv.MediaVersions.HeadOrNone().Map(v => v.Duration),
OtherVideo ov => ov.MediaVersions.HeadOrNone().Map(v => v.Duration),
Song s => s.MediaVersions.HeadOrNone().Map(v => v.Duration),
ChapterMediaItem c => c.MediaVersion.Duration,
_ => None
};
@ -33,6 +34,7 @@ public static class MediaItemExtensions @@ -33,6 +34,7 @@ public static class MediaItemExtensions
Song s => s.MediaVersions.Head(),
Image i => i.MediaVersions.Head(),
RemoteStream rs => rs.MediaVersions.Head(),
ChapterMediaItem c => c.MediaVersion,
_ => throw new ArgumentOutOfRangeException(nameof(mediaItem))
};

105
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
using System.Reflection;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
@ -854,23 +855,68 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -854,23 +855,68 @@ public class PlayoutBuilder : IPlayoutBuilder
private async Task<Map<CollectionKey, List<MediaItem>>> GetCollectionMediaItems(Playout playout)
{
var collectionKeys = playout.ProgramSchedule.Items
IEnumerable<KeyValuePair<CollectionKey, Option<FillerPreset>>> collectionKeys = GetAllCollectionKeys(playout);
IEnumerable<Task<KeyValuePair<CollectionKey, List<MediaItem>>>> tasks = collectionKeys.Select(async key =>
{
List<MediaItem> mediaItems = await FetchMediaItemsForKeyAsync(key.Key, key.Value);
return new KeyValuePair<CollectionKey, List<MediaItem>>(key.Key, mediaItems);
});
return Map.createRange(await Task.WhenAll(tasks));
}
private static IEnumerable<KeyValuePair<CollectionKey, Option<FillerPreset>>> GetAllCollectionKeys(Playout playout)
{
return playout.ProgramSchedule.Items
.Append(playout.ProgramScheduleAlternates.Bind(psa => psa.ProgramSchedule.Items))
.DistinctBy(i => i.Id)
.DistinctBy(item => item.Id)
.SelectMany(CollectionKeysForItem)
.Distinct()
.ToList();
.DistinctBy(kvp => kvp.Key);
}
IEnumerable<Tuple<CollectionKey, List<MediaItem>>> tuples = await collectionKeys.Map(async collectionKey =>
Tuple(
collectionKey,
await MediaItemsForCollection.Collect(
_mediaCollectionRepository,
_televisionRepository,
_artistRepository,
collectionKey))).SequenceParallel();
private async Task<List<MediaItem>> FetchMediaItemsForKeyAsync(
CollectionKey collectionKey,
Option<FillerPreset> fillerPreset)
{
List<MediaItem> result = await MediaItemsForCollection.Collect(
_mediaCollectionRepository,
_televisionRepository,
_artistRepository,
collectionKey);
foreach (FillerPreset _ in fillerPreset.Where(p => p.UseChaptersAsMediaItems))
{
var fakeResults = new List<MediaItem>();
var uniqueId = 1;
return Map.createRange(tuples);
foreach (MediaItem mediaItem in result)
{
MediaVersion version = mediaItem.GetHeadVersion();
var allChapters = Optional(version.Chapters).Flatten().OrderBy(c => c.StartTime).ToList();
if (allChapters.Count > 0)
{
foreach (MediaChapter chapter in allChapters)
{
var chapterVersion = new ChapterMediaVersion(chapter);
var chapterItem = new ChapterMediaItem(uniqueId++, mediaItem, chapterVersion);
fakeResults.Add(chapterItem);
}
}
else
{
// still use a fake item here so we don't have id conflicts
var chapterVersion = new ChapterMediaVersion(
new MediaChapter { StartTime = TimeSpan.Zero, EndTime = version.Duration });
var chapterItem = new ChapterMediaItem(uniqueId++, mediaItem, chapterVersion);
fakeResults.Add(chapterItem);
}
}
return fakeResults;
}
return result;
}
private async Task<Option<CollectionKey>> CheckForEmptyCollections(
@ -900,6 +946,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -900,6 +946,7 @@ public class PlayoutBuilder : IPlayoutBuilder
RemoteStream rs => await rs.MediaVersions.Map(v => v.Duration).HeadOrNone()
.IfNoneAsync(TimeSpan.Zero) == TimeSpan.Zero
&& (!rs.Duration.HasValue || rs.Duration.Value == TimeSpan.Zero),
ChapterMediaItem c => c.MediaVersion.Duration == TimeSpan.Zero,
_ => true
};
@ -1244,36 +1291,52 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1244,36 +1291,52 @@ public class PlayoutBuilder : IPlayoutBuilder
}
}
private static List<CollectionKey> CollectionKeysForItem(ProgramScheduleItem item)
private static List<KeyValuePair<CollectionKey, Option<FillerPreset>>> CollectionKeysForItem(
ProgramScheduleItem item)
{
var result = new List<CollectionKey>
var result = new List<KeyValuePair<CollectionKey, Option<FillerPreset>>>
{
CollectionKey.ForScheduleItem(item)
new(CollectionKey.ForScheduleItem(item), Option<FillerPreset>.None)
};
if (item.PreRollFiller != null)
{
result.Add(CollectionKey.ForFillerPreset(item.PreRollFiller));
result.Add(
new KeyValuePair<CollectionKey, Option<FillerPreset>>(
CollectionKey.ForFillerPreset(item.PreRollFiller),
item.PreRollFiller));
}
if (item.MidRollFiller != null)
{
result.Add(CollectionKey.ForFillerPreset(item.MidRollFiller));
result.Add(
new KeyValuePair<CollectionKey, Option<FillerPreset>>(
CollectionKey.ForFillerPreset(item.MidRollFiller),
item.MidRollFiller));
}
if (item.PostRollFiller != null)
{
result.Add(CollectionKey.ForFillerPreset(item.PostRollFiller));
result.Add(
new KeyValuePair<CollectionKey, Option<FillerPreset>>(
CollectionKey.ForFillerPreset(item.PostRollFiller),
item.PostRollFiller));
}
if (item.TailFiller != null)
{
result.Add(CollectionKey.ForFillerPreset(item.TailFiller));
result.Add(
new KeyValuePair<CollectionKey, Option<FillerPreset>>(
CollectionKey.ForFillerPreset(item.TailFiller),
item.TailFiller));
}
if (item.FallbackFiller != null)
{
result.Add(CollectionKey.ForFillerPreset(item.FallbackFiller));
result.Add(
new KeyValuePair<CollectionKey, Option<FillerPreset>>(
CollectionKey.ForFillerPreset(item.FallbackFiller),
item.FallbackFiller));
}
return result;

52
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -118,6 +118,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -118,6 +118,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
MediaItem mediaItem = enumerator.Current.ValueUnsafe();
TimeSpan itemDuration = DurationForMediaItem(mediaItem);
TimeSpan inPoint = InPointForMediaItem(mediaItem);
if (nextState.CurrentTime + itemDuration > nextItemStart)
{
@ -131,14 +132,15 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -131,14 +132,15 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
var playoutItem = new PlayoutItem
{
MediaItemId = mediaItem.Id,
MediaItemId = IdForMediaItem(mediaItem),
Start = nextState.CurrentTime.UtcDateTime,
Finish = nextState.CurrentTime.UtcDateTime + itemDuration,
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
InPoint = inPoint,
OutPoint = inPoint + itemDuration,
FillerKind = FillerKind.Tail,
GuideGroup = nextState.NextGuideGroup,
DisableWatermarks = !scheduleItem.TailFiller.AllowWatermarks
DisableWatermarks = !scheduleItem.TailFiller.AllowWatermarks,
ChapterTitle = ChapterTitleForMediaItem(mediaItem)
};
newItems.Add(playoutItem);
@ -218,6 +220,27 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -218,6 +220,27 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
return version.Duration;
}
private static TimeSpan InPointForMediaItem(MediaItem mediaItem) =>
mediaItem switch
{
ChapterMediaItem c => c.MediaVersion.InPoint,
_ => TimeSpan.Zero
};
private static int IdForMediaItem(MediaItem mediaItem) =>
mediaItem switch
{
ChapterMediaItem c => c.MediaItemId,
_ => mediaItem.Id
};
private static string ChapterTitleForMediaItem(MediaItem mediaItem) =>
mediaItem switch
{
ChapterMediaItem c => c.MediaVersion.Title,
_ => null
};
protected static List<MediaChapter> ChaptersForMediaItem(MediaItem mediaItem)
{
MediaVersion version = mediaItem.GetHeadVersion();
@ -726,17 +749,19 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -726,17 +749,19 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
foreach (MediaItem mediaItem in enumerator.Current)
{
TimeSpan itemDuration = DurationForMediaItem(mediaItem);
TimeSpan inPoint = InPointForMediaItem(mediaItem);
var playoutItem = new PlayoutItem
{
MediaItemId = mediaItem.Id,
MediaItemId = IdForMediaItem(mediaItem),
Start = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc),
Finish = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) + itemDuration,
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
InPoint = inPoint,
OutPoint = inPoint + itemDuration,
GuideGroup = playoutBuilderState.NextGuideGroup,
FillerKind = fillerKind,
DisableWatermarks = !allowWatermarks
DisableWatermarks = !allowWatermarks,
ChapterTitle = ChapterTitleForMediaItem(mediaItem)
};
result.Add(playoutItem);
@ -765,19 +790,22 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -765,19 +790,22 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
foreach (MediaItem mediaItem in enumerator.Current)
{
TimeSpan itemDuration = DurationForMediaItem(mediaItem);
TimeSpan inPoint = InPointForMediaItem(mediaItem);
if (remainingToFill - itemDuration >= TimeSpan.Zero)
{
var playoutItem = new PlayoutItem
{
MediaItemId = mediaItem.Id,
MediaItemId = IdForMediaItem(mediaItem),
Start = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc),
Finish = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) + itemDuration,
InPoint = TimeSpan.Zero,
OutPoint = itemDuration,
InPoint = inPoint,
OutPoint = inPoint + itemDuration,
GuideGroup = playoutBuilderState.NextGuideGroup,
FillerKind = fillerKind,
DisableWatermarks = !allowWatermarks
DisableWatermarks = !allowWatermarks,
ChapterTitle = ChapterTitleForMediaItem(mediaItem)
};
remainingToFill -= itemDuration;

6163
ErsatzTV.Infrastructure.MySql/Migrations/20250803211857_FillerPreset_UseChaptersAsMediaItems.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20250803211857_FillerPreset_UseChaptersAsMediaItems.cs

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

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

@ -746,6 +746,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -746,6 +746,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int?>("SmartCollectionId")
.HasColumnType("int");
b.Property<bool>("UseChaptersAsMediaItems")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("CollectionId");

6000
ErsatzTV.Infrastructure.Sqlite/Migrations/20250803211925_FillerPreset_UseChaptersAsMediaItems.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20250803211925_FillerPreset_UseChaptersAsMediaItems.cs

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

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

@ -713,6 +713,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -713,6 +713,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int?>("SmartCollectionId")
.HasColumnType("INTEGER");
b.Property<bool>("UseChaptersAsMediaItems")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CollectionId");

12
ErsatzTV/Pages/FillerPresetEditor.razor

@ -209,6 +209,17 @@ @@ -209,6 +209,17 @@
</MudSelect>
</MudStack>
}
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Use Chapters As Media Items</MudText>
</div>
<MudCheckBox @bind-Value="@_model.UseChaptersAsMediaItems"
For="@(() => _model.UseChaptersAsMediaItems)"
Dense="true"
Disabled="@(_model.FillerKind is FillerKind.Fallback)">
<MudText Typo="Typo.caption" Style="font-weight: normal">Schedule individual chapters instead of entire files</MudText>
</MudCheckBox>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Expression</MudText>
@ -313,6 +324,7 @@ @@ -313,6 +324,7 @@
? _televisionShows.Append(_televisionSeasons).Append(_artists).ToList().Find(vm => vm.MediaItemId == fillerPreset.MediaItemId.Value)
: null;
_model.Expression = fillerPreset.Expression;
_model.UseChaptersAsMediaItems = fillerPreset.UseChaptersAsMediaItems;
_model.Playlist = fillerPreset.Playlist;
});
}

3
ErsatzTV/Validators/FillerPresetEditViewModelValidator.cs

@ -25,6 +25,9 @@ public class FillerPresetEditViewModelValidator : AbstractValidator<FillerPreset @@ -25,6 +25,9 @@ public class FillerPresetEditViewModelValidator : AbstractValidator<FillerPreset
When(
fp => fp.FillerMode == FillerMode.Pad,
() => RuleFor(fp => fp.PadToNearestMinute).NotNull());
When(
fp => fp.FillerKind is FillerKind.Fallback,
() => RuleFor(fp => fp.UseChaptersAsMediaItems).NotEqual(true));
When(
fp => fp.CollectionType == ProgramScheduleItemCollectionType.Collection,

13
ErsatzTV/ViewModels/FillerPresetEditViewModel.cs

@ -35,6 +35,11 @@ public class FillerPresetEditViewModel @@ -35,6 +35,11 @@ public class FillerPresetEditViewModel
{
Expression = string.Empty;
}
if (_fillerKind is FillerKind.Fallback)
{
UseChaptersAsMediaItems = false;
}
}
}
@ -93,6 +98,8 @@ public class FillerPresetEditViewModel @@ -93,6 +98,8 @@ public class FillerPresetEditViewModel
public string Expression { get; set; }
public bool UseChaptersAsMediaItems { get; set; }
public IRequest<Either<BaseError, Unit>> ToEdit() =>
new UpdateFillerPreset(
Id,
@ -109,7 +116,8 @@ public class FillerPresetEditViewModel @@ -109,7 +116,8 @@ public class FillerPresetEditViewModel
MultiCollection?.Id,
SmartCollection?.Id,
Playlist?.Id,
Expression);
Expression,
UseChaptersAsMediaItems);
public IRequest<Either<BaseError, Unit>> ToUpdate() =>
new CreateFillerPreset(
@ -126,7 +134,8 @@ public class FillerPresetEditViewModel @@ -126,7 +134,8 @@ public class FillerPresetEditViewModel
MultiCollection?.Id,
SmartCollection?.Id,
Playlist?.Id,
Expression);
Expression,
UseChaptersAsMediaItems);
private static TimeSpan FixDuration(TimeSpan duration) =>
duration > TimeSpan.FromDays(1) ? duration.Subtract(TimeSpan.FromDays(1)) : duration;

Loading…
Cancel
Save