Compare commits

...
Author SHA1 Message Date
Jason Dove 59a1a4a8dc update changelog for release v0.3.3-alpha [no ci] 2021-12-12 23:53:12 -06:00
Jason Dove 85a9afb51c update dependencies (#538) 2021-12-12 23:51:57 -06:00
Jason Dove 246b4d7591 properly sort channels in m3u (#537) 2021-12-10 20:22:52 -06:00
Jason Dove ae2c6350e1 sync virtual shows and season from jellyfin (#536) 2021-12-10 14:41:47 -06:00
Jason Dove ce228604e8 use select controls instead of autocomplete (#532)
* use select instead of autocomplete for playout editor

* use select instead of autocomplete for filler preset editor

* reset selected collection when changing collection type

* use select instead of autocomplete for multi collection editor

* more select

* more select controls
2021-12-06 12:49:48 -06:00
Jason Dove 3656e932d3 more song fixes (#529)
* use blurhash for default etv song backgrounds

* fix saving artwork blurhash

* fix song detail alignment

* rename song background files

* watermark path is always none here
2021-12-04 13:30:25 -06:00
24 changed files with 4161 additions and 222 deletions
+14 -1
View File
@@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [0.3.3-alpha] - 2021-12-12
### Fixed
- Fix bug with saving multiple blurhash versions for cover art; all cover art will be automatically rescanned
- Fix song detail margin when no cover art exists and no watermark exists
- Fix synchronizing virtual shows and seasons from Jellyfin
- Properly sort channels in M3U
### Changed
- Use blurhash of ErsatzTV colors instead of solid colors for default song backgrounds
- Use select control instead of autocomplete control in many places
- The autocomplete control is not intuitive to use and has focus bugs
## [0.3.2-alpha] - 2021-12-03
### Fixed
- Fix artwork upload on Windows
@@ -850,7 +862,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Initial release to facilitate testing outside of Docker.
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.2-alpha...HEAD
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.3-alpha...HEAD
[0.3.3-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.2-alpha...v0.3.3-alpha
[0.3.2-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.1-alpha...v0.3.2-alpha
[0.3.1-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.3.0-alpha...v0.3.1-alpha
[0.3.0-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.2.5-alpha...v0.3.0-alpha
@@ -11,5 +11,6 @@ namespace ErsatzTV.Application.Channels
string PreferredLanguageCode,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId);
int? FallbackFillerId,
int PlayoutCount);
}
+2 -1
View File
@@ -16,7 +16,8 @@ namespace ErsatzTV.Application.Channels
channel.PreferredLanguageCode,
channel.StreamingMode,
channel.WatermarkId,
channel.FallbackFillerId);
channel.FallbackFillerId,
channel.Playouts?.Count ?? 0);
private static string GetLogo(Channel channel) =>
Optional(channel.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Logo))
+14 -12
View File
@@ -54,10 +54,9 @@ namespace ErsatzTV.Core.FFmpeg
string[] backgrounds =
{
"background_blank.png",
"background_e.png",
"background_t.png",
"background_v.png"
"song_background_1.png",
"song_background_2.png",
"song_background_3.png"
};
// use random ETV color by default
@@ -117,15 +116,18 @@ namespace ErsatzTV.Core.FFmpeg
int leftMarginPercent = HORIZONTAL_MARGIN_PERCENT;
int rightMarginPercent = HORIZONTAL_MARGIN_PERCENT;
switch (watermarkLocation)
if (metadata.Artwork.Any(a => a.ArtworkKind == ArtworkKind.Thumbnail))
{
case ChannelWatermarkLocation.BottomLeft:
leftMarginPercent += WATERMARK_WIDTH_PERCENT + HORIZONTAL_MARGIN_PERCENT;
break;
case ChannelWatermarkLocation.BottomRight:
leftMarginPercent = rightMarginPercent = HORIZONTAL_MARGIN_PERCENT;
rightMarginPercent += WATERMARK_WIDTH_PERCENT + HORIZONTAL_MARGIN_PERCENT;
break;
switch (watermarkLocation)
{
case ChannelWatermarkLocation.BottomLeft:
leftMarginPercent += WATERMARK_WIDTH_PERCENT + HORIZONTAL_MARGIN_PERCENT;
break;
case ChannelWatermarkLocation.BottomRight:
leftMarginPercent = rightMarginPercent = HORIZONTAL_MARGIN_PERCENT;
rightMarginPercent += WATERMARK_WIDTH_PERCENT + HORIZONTAL_MARGIN_PERCENT;
break;
}
}
var leftMargin = (int)Math.Round(leftMarginPercent / 100.0 * channel.FFmpegProfile.Resolution.Width);
+1 -1
View File
@@ -26,7 +26,7 @@ namespace ErsatzTV.Core.Iptv
var xmltv = $"{_scheme}://{_host}/iptv/xmltv.xml";
sb.AppendLine($"#EXTM3U url-tvg=\"{xmltv}\" x-tvg-url=\"{xmltv}\"");
foreach (Channel channel in _channels.OrderBy(c => c.Number))
foreach (Channel channel in _channels.OrderBy(c => decimal.Parse(c.Number)))
{
string logo = Optional(channel.Artwork).Flatten()
.Filter(a => a.ArtworkKind == ArtworkKind.Logo)
@@ -7,6 +7,7 @@ using LanguageExt;
using Microsoft.EntityFrameworkCore;
using static LanguageExt.Prelude;
namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class ChannelRepository : IChannelRepository
@@ -42,10 +43,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task<List<Channel>> GetAll()
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Channels
.Include(c => c.FFmpegProfile)
.Include(c => c.Artwork)
.Include(c => c.Playouts)
.ToListAsync();
}
@@ -209,7 +209,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public Task<Unit> UpdateArtworkPath(Artwork artwork) =>
_dbConnection.ExecuteAsync(
"UPDATE Artwork SET Path = @Path, SourcePath = @SourcePath, DateUpdated = @DateUpdated, BlurHash43 = @BlurHash43, BlurHash43 = @BlurHash54, BlurHash43 = @BlurHash64 WHERE Id = @Id",
"UPDATE Artwork SET Path = @Path, SourcePath = @SourcePath, DateUpdated = @DateUpdated, BlurHash43 = @BlurHash43, BlurHash54 = @BlurHash54, BlurHash64 = @BlurHash64 WHERE Id = @Id",
new { artwork.Path, artwork.SourcePath, artwork.DateUpdated, artwork.BlurHash43, artwork.BlurHash54, artwork.BlurHash64, artwork.Id }).ToUnit();
public Task<Unit> AddArtwork(Metadata metadata, Artwork artwork)
@@ -383,11 +383,6 @@ namespace ErsatzTV.Infrastructure.Jellyfin
{
try
{
if (item.LocationType != "FileSystem")
{
return None;
}
ShowMetadata metadata = ProjectToShowMetadata(item);
var show = new JellyfinShow
@@ -481,11 +476,6 @@ namespace ErsatzTV.Infrastructure.Jellyfin
{
try
{
if (item.LocationType != "FileSystem")
{
return None;
}
DateTime dateAdded = item.DateCreated.UtcDateTime;
// DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Reset_SongMetadataBlurHashProper : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"UPDATE LibraryPath SET LastScan = '0001-01-01 00:00:00' WHERE Id IN
(SELECT LP.Id FROM LibraryPath LP INNER JOIN Library L on L.Id = LP.LibraryId WHERE MediaKind = 5)");
migrationBuilder.Sql(
@"UPDATE Library SET LastScan = '0001-01-01 00:00:00' WHERE MediaKind = 5");
migrationBuilder.Sql(
@"UPDATE Artwork SET DateUpdated = '0001-01-01 00:00:00' WHERE SongMetadataId IS NOT NULL");
migrationBuilder.Sql(
@"UPDATE LibraryFolder SET Etag = NULL WHERE Id IN
(SELECT LF.Id FROM LibraryFolder LF INNER JOIN LibraryPath LP on LF.LibraryPathId = LP.Id INNER JOIN Library L on LP.LibraryId = L.Id WHERE MediaKind = 5)");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
+5 -6
View File
@@ -19,7 +19,7 @@
<PackageReference Include="HtmlSanitizer" Version="6.0.453" />
<PackageReference Include="LanguageExt.Core" Version="4.0.3" />
<PackageReference Include="Markdig" Version="0.26.0" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="3.0.1" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="4.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
@@ -31,7 +31,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MudBlazor" Version="6.0.2" />
<PackageReference Include="NaturalSort.Extension" Version="3.1.0" />
<PackageReference Include="NaturalSort.Extension" Version="3.2.0" />
<PackageReference Include="PPioli.FluentValidation.Blazor" Version="5.0.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="6.1.15" />
<PackageReference Include="Serilog" Version="2.10.0" />
@@ -51,10 +51,9 @@
<ItemGroup>
<EmbeddedResource Include="Resources\background.png" />
<EmbeddedResource Include="Resources\background_blank.png" />
<EmbeddedResource Include="Resources\background_e.png" />
<EmbeddedResource Include="Resources\background_t.png" />
<EmbeddedResource Include="Resources\background_v.png" />
<EmbeddedResource Include="Resources\song_background_1.png" />
<EmbeddedResource Include="Resources\song_background_2.png" />
<EmbeddedResource Include="Resources\song_background_3.png" />
<EmbeddedResource Include="Resources\ErsatzTV.png" />
<EmbeddedResource Include="Resources\Roboto-Regular.ttf" />
<EmbeddedResource Include="Resources\OPTIKabel-Heavy.otf" />
+54 -54
View File
@@ -52,57 +52,75 @@
</MudSelect>
@if (_model.CollectionType == ProgramScheduleItemCollectionType.Collection)
{
<MudAutocomplete Class="mt-3"
T="MediaCollectionViewModel"
Label="Collection"
@bind-value="_model.Collection"
SearchFunc="@SearchMediaCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect Class="mt-3"
T="MediaCollectionViewModel"
Label="Collection"
@bind-value="_model.Collection">
@foreach (MediaCollectionViewModel collection in _mediaCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_model.CollectionType == ProgramScheduleItemCollectionType.MultiCollection)
{
<MudAutocomplete Class="mt-3"
T="MultiCollectionViewModel"
Label="Multi Collection"
@bind-value="_model.MultiCollection"
SearchFunc="@SearchMultiCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect Class="mt-3"
T="MultiCollectionViewModel"
Label="Multi Collection"
@bind-value="_model.MultiCollection">
@foreach (MultiCollectionViewModel collection in _multiCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_model.CollectionType == ProgramScheduleItemCollectionType.SmartCollection)
{
<MudAutocomplete Class="mt-3"
T="SmartCollectionViewModel"
Label="Smart Collection"
@bind-value="_model.SmartCollection"
SearchFunc="@SearchSmartCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect Class="mt-3"
T="SmartCollectionViewModel"
Label="Smart Collection"
@bind-value="_model.SmartCollection">
@foreach (SmartCollectionViewModel collection in _smartCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_model.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
{
<MudAutocomplete Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Show"
@bind-value="_model.MediaItem"
SearchFunc="@SearchTelevisionShows"
ToStringFunc="@(s => s?.Name)"/>
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Show"
@bind-value="_model.MediaItem">
@foreach (NamedMediaItemViewModel show in _televisionShows)
{
<MudSelectItem Value="@show">@show.Name</MudSelectItem>
}
</MudSelect>
}
@if (_model.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
{
<MudAutocomplete Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Season"
@bind-value="_model.MediaItem"
SearchFunc="@SearchTelevisionSeasons"
ToStringFunc="@(s => s?.Name)"/>
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Season"
@bind-value="_model.MediaItem">
@foreach (NamedMediaItemViewModel season in _televisionSeasons)
{
<MudSelectItem Value="@season">@season.Name</MudSelectItem>
}
</MudSelect>
}
@if (_model.CollectionType == ProgramScheduleItemCollectionType.Artist)
{
<MudAutocomplete Class="mt-3"
T="NamedMediaItemViewModel"
Label="Artist"
@bind-value="_model.MediaItem"
SearchFunc="@SearchArtists"
ToStringFunc="@(s => s?.Name)"/>
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Label="Artist"
@bind-value="_model.MediaItem">
@foreach (NamedMediaItemViewModel artist in _artists)
{
<MudSelectItem Value="@artist">@artist.Name</MudSelectItem>
}
</MudSelect>
}
</MudCardContent>
<MudCardActions>
@@ -206,22 +224,4 @@
() => _navigationManager.NavigateTo("/media/filler/presets"));
}
}
private Task<IEnumerable<MediaCollectionViewModel>> SearchMediaCollections(string value) =>
_mediaCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value) =>
_multiCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) =>
_smartCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value) =>
_televisionShows.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value) =>
_televisionSeasons.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value) =>
_artists.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
}
+32 -25
View File
@@ -17,23 +17,37 @@
<MudCard>
<MudCardContent>
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
<MudAutocomplete @ref="_collectionAutocomplete"
Class="mt-4"
T="MediaCollectionViewModel"
Label="Collection"
@bind-value="_selectedCollection"
SearchFunc="@SearchCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect @ref="_collectionSelect"
Class="mt-4"
T="MediaCollectionViewModel"
Label="Collection"
@bind-value="_selectedCollection"
HelperText="Disabled collections are already present in this multi collection">
@foreach (MediaCollectionViewModel collection in _collections)
{
<MudSelectItem Disabled="@(_model.Items.Any(i => i.Collection.Id == collection.Id))"
Value="@collection">
@collection.Name
</MudSelectItem>
}
</MudSelect>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => AddCollection())" Class="mt-4 mr-auto">
Add Collection
</MudButton>
<MudAutocomplete @ref="_smartCollectionAutocomplete"
Class="mt-4"
T="SmartCollectionViewModel"
Label="Smart Collection"
@bind-value="_selectedSmartCollection"
SearchFunc="@SearchSmartCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect @ref="_smartCollectionSelect"
Class="mt-4"
T="SmartCollectionViewModel"
Label="Smart Collection"
@bind-value="_selectedSmartCollection"
HelperText="Disabled collections are already present in this multi collection">
@foreach (SmartCollectionViewModel collection in _smartCollections)
{
<MudSelectItem Disabled="@(_model.Items.Any(i => i.Collection.Id == collection.Id))"
Value="@collection">
@collection.Name
</MudSelectItem>
}
</MudSelect>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => AddSmartCollection())" Class="mt-4 mr-auto">
Add Smart Collection
</MudButton>
@@ -100,8 +114,8 @@
private List<SmartCollectionViewModel> _smartCollections;
private MediaCollectionViewModel _selectedCollection;
private SmartCollectionViewModel _selectedSmartCollection;
private MudAutocomplete<MediaCollectionViewModel> _collectionAutocomplete;
private MudAutocomplete<SmartCollectionViewModel> _smartCollectionAutocomplete;
private MudSelect<MediaCollectionViewModel> _collectionSelect;
private MudSelect<SmartCollectionViewModel> _smartCollectionSelect;
protected override async Task OnParametersSetAsync()
{
@@ -211,7 +225,7 @@
});
_selectedCollection = null;
_collectionAutocomplete.Reset();
_collectionSelect.Reset();
}
}
@@ -226,14 +240,7 @@
});
_selectedSmartCollection = null;
_smartCollectionAutocomplete.Reset();
_smartCollectionSelect.Reset();
}
}
private Task<IEnumerable<MediaCollectionViewModel>> SearchCollections(string value) =>
_collections.Filter(c => _model.Items.All(i => i.Collection != c) && c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) =>
_smartCollections.Filter(c => _model.Items.OfType<MultiCollectionSmartItemEditViewModel>().All(i => i.SmartCollection != c) && c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
}
+20 -9
View File
@@ -16,8 +16,26 @@
<FluentValidator/>
<MudCard>
<MudCardContent>
<MudAutocomplete T="ChannelViewModel" Label="Channel" @bind-value="_model.Channel" SearchFunc="@SearchChannels" ToStringFunc="@(c => c is null ? null : $"{c.Number} - {c.Name}")"/>
<MudAutocomplete Class="mt-3" T="ProgramScheduleViewModel" Label="Schedule" @bind-value="_model.ProgramSchedule" SearchFunc="@SearchProgramSchedules" ToStringFunc="@(s => s?.Name)"/>
<MudSelect T="ChannelViewModel"
Label="Channel"
@bind-value="_model.Channel"
HelperText="Disabled channels already have a playout">
@foreach (ChannelViewModel channel in _channels)
{
<MudSelectItem Disabled="@(channel.PlayoutCount > 0)" Value="@channel">
@($"{channel.Number} - {channel.Name}")
</MudSelectItem>
}
</MudSelect>
<MudSelect Class="mt-3"
T="ProgramScheduleViewModel"
Label="Schedule"
@bind-value="_model.ProgramSchedule">
@foreach (ProgramScheduleViewModel schedule in _programSchedules)
{
<MudSelectItem Value="@schedule">@schedule.Name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
<MudCardActions>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
@@ -52,13 +70,6 @@
_messageStore = new ValidationMessageStore(_editContext);
}
private Task<IEnumerable<ChannelViewModel>> SearchChannels(string value) =>
_channels.Filter(c => $"{c.Number} - {c.Name}".Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<ProgramScheduleViewModel>> SearchProgramSchedules(string value) =>
_programSchedules.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private async Task HandleSubmitAsync()
{
_messageStore.Clear();
+105 -95
View File
@@ -105,57 +105,75 @@
</MudSelect>
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection)
{
<MudAutocomplete Class="mt-3"
T="MediaCollectionViewModel"
Label="Collection"
@bind-value="_selectedItem.Collection"
SearchFunc="@SearchMediaCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect Class="mt-3"
T="MediaCollectionViewModel"
Label="Collection"
@bind-value="_selectedItem.Collection">
@foreach (MediaCollectionViewModel collection in _mediaCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.MultiCollection)
{
<MudAutocomplete Class="mt-3"
T="MultiCollectionViewModel"
Label="Multi Collection"
@bind-value="_selectedItem.MultiCollection"
SearchFunc="@SearchMultiCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect Class="mt-3"
T="MultiCollectionViewModel"
Label="Multi Collection"
@bind-value="_selectedItem.MultiCollection">
@foreach (MultiCollectionViewModel collection in _multiCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.SmartCollection)
{
<MudAutocomplete Class="mt-3"
T="SmartCollectionViewModel"
Label="Smart Collection"
@bind-value="_selectedItem.SmartCollection"
SearchFunc="@SearchSmartCollections"
ToStringFunc="@(c => c?.Name)"/>
<MudSelect Class="mt-3"
T="SmartCollectionViewModel"
Label="Smart Collection"
@bind-value="_selectedItem.SmartCollection">
@foreach (SmartCollectionViewModel collection in _smartCollections)
{
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem>
}
</MudSelect>
}
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
{
<MudAutocomplete Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Show"
@bind-value="_selectedItem.MediaItem"
SearchFunc="@SearchTelevisionShows"
ToStringFunc="@(s => s?.Name)"/>
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Show"
@bind-value="_selectedItem.MediaItem">
@foreach (NamedMediaItemViewModel show in _televisionShows)
{
<MudSelectItem Value="@show">@show.Name</MudSelectItem>
}
</MudSelect>
}
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
{
<MudAutocomplete Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Season"
@bind-value="_selectedItem.MediaItem"
SearchFunc="@SearchTelevisionSeasons"
ToStringFunc="@(s => s?.Name)"/>
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Label="Television Season"
@bind-value="_selectedItem.MediaItem">
@foreach (NamedMediaItemViewModel season in _televisionSeasons)
{
<MudSelectItem Value="@season">@season.Name</MudSelectItem>
}
</MudSelect>
}
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Artist)
{
<MudAutocomplete Class="mt-3"
T="NamedMediaItemViewModel"
Label="Artist"
@bind-value="_selectedItem.MediaItem"
SearchFunc="@SearchArtists"
ToStringFunc="@(s => s?.Name)"/>
<MudSelect Class="mt-3"
T="NamedMediaItemViewModel"
Label="Artist"
@bind-value="_selectedItem.MediaItem">
@foreach (NamedMediaItemViewModel artist in _artists)
{
<MudSelectItem Value="@artist">@artist.Name</MudSelectItem>
}
</MudSelect>
}
<MudSelect Class="mt-3" Label="Playback Order" @bind-Value="@_selectedItem.PlaybackOrder" For="@(() => _selectedItem.PlaybackOrder)">
@switch (_selectedItem.CollectionType)
@@ -202,47 +220,57 @@
<div style="flex-grow: 1; max-width: 400px;">
<MudCard>
<MudCardContent>
<MudAutocomplete Class="mt-3"
T="FillerPresetViewModel"
Label="Pre-Roll Filler"
@bind-value="_selectedItem.PreRollFiller"
SearchFunc="@(arg => SearchFillerPresets(FillerKind.PreRoll, arg))"
ToStringFunc="@(c => c?.Name)"
ResetValueOnEmptyText="true"
Clearable="true"/>
<MudAutocomplete Class="mt-3"
T="FillerPresetViewModel"
Label="Mid-Roll Filler"
@bind-value="_selectedItem.MidRollFiller"
SearchFunc="@(arg => SearchFillerPresets(FillerKind.MidRoll, arg))"
ToStringFunc="@(c => c?.Name)"
ResetValueOnEmptyText="true"
Clearable="true"/>
<MudAutocomplete Class="mt-3"
T="FillerPresetViewModel"
Label="Post-Roll Filler"
@bind-value="_selectedItem.PostRollFiller"
SearchFunc="@(arg => SearchFillerPresets(FillerKind.PostRoll, arg))"
ToStringFunc="@(c => c?.Name)"
ResetValueOnEmptyText="true"
Clearable="true"/>
<MudAutocomplete Class="mt-3"
T="FillerPresetViewModel"
Label="Tail Filler"
@bind-value="_selectedItem.TailFiller"
SearchFunc="@(arg => SearchFillerPresets(FillerKind.Tail, arg))"
ToStringFunc="@(c => c?.Name)"
ResetValueOnEmptyText="true"
Clearable="true"/>
<MudAutocomplete Class="mt-3"
T="FillerPresetViewModel"
Label="Fallback Filler"
@bind-value="_selectedItem.FallbackFiller"
SearchFunc="@(arg => SearchFillerPresets(FillerKind.Fallback, arg))"
ToStringFunc="@(c => c?.Name)"
ResetValueOnEmptyText="true"
Clearable="true"/>
</MudCardContent>
<MudSelect Class="mt-3"
T="FillerPresetViewModel"
Label="Pre-Roll Filler"
@bind-value="_selectedItem.PreRollFiller"
Clearable="true">
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PreRoll))
{
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
}
</MudSelect>
<MudSelect Class="mt-3"
T="FillerPresetViewModel"
Label="Mid-Roll Filler"
@bind-value="_selectedItem.MidRollFiller"
Clearable="true">
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.MidRoll))
{
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
}
</MudSelect>
<MudSelect Class="mt-3"
T="FillerPresetViewModel"
Label="Post-Roll Filler"
@bind-value="_selectedItem.PostRollFiller"
Clearable="true">
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.PostRoll))
{
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
}
</MudSelect>
<MudSelect Class="mt-3"
T="FillerPresetViewModel"
Label="Tail Filler"
@bind-value="_selectedItem.TailFiller"
Clearable="true">
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Tail))
{
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
}
</MudSelect>
<MudSelect Class="mt-3"
T="FillerPresetViewModel"
Label="Fallback Filler"
@bind-value="_selectedItem.FallbackFiller"
Clearable="true">
@foreach (FillerPresetViewModel filler in _fillerPresets.Where(f => f.FillerKind == FillerKind.Fallback))
{
<MudSelectItem Value="@filler">@filler.Name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudCard>
</div>
</div>
@@ -379,24 +407,6 @@
(toSwap.Index, item.Index) = (item.Index, toSwap.Index);
}
private Task<IEnumerable<MediaCollectionViewModel>> SearchMediaCollections(string value) =>
_mediaCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<MultiCollectionViewModel>> SearchMultiCollections(string value) =>
_multiCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<SmartCollectionViewModel>> SearchSmartCollections(string value) =>
_smartCollections.Filter(c => c.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionShows(string value) =>
_televisionShows.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<NamedMediaItemViewModel>> SearchTelevisionSeasons(string value) =>
_televisionSeasons.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<NamedMediaItemViewModel>> SearchArtists(string value) =>
_artists.Filter(s => s.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
private Task<IEnumerable<FillerPresetViewModel>> SearchFillerPresets(FillerKind fillerKind, string value) =>
_fillerPresets.Filter(p => p.FillerKind == fillerKind && p.Name.Contains(value ?? string.Empty, StringComparison.OrdinalIgnoreCase)).AsTask();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

@@ -19,10 +19,9 @@ namespace ErsatzTV.Services.RunOnce
Assembly assembly = typeof(ResourceExtractorService).GetTypeInfo().Assembly;
await ExtractResource(assembly, "background.png", cancellationToken);
await ExtractResource(assembly, "background_blank.png", cancellationToken);
await ExtractResource(assembly, "background_e.png", cancellationToken);
await ExtractResource(assembly, "background_t.png", cancellationToken);
await ExtractResource(assembly, "background_v.png", cancellationToken);
await ExtractResource(assembly, "song_background_1.png", cancellationToken);
await ExtractResource(assembly, "song_background_2.png", cancellationToken);
await ExtractResource(assembly, "song_background_3.png", cancellationToken);
await ExtractResource(assembly, "ErsatzTV.png", cancellationToken);
await ExtractResource(assembly, "Roboto-Regular.ttf", cancellationToken);
await ExtractResource(assembly, "OPTIKabel-Heavy.otf", cancellationToken);
@@ -17,6 +17,7 @@ namespace ErsatzTV.ViewModels
private TimeSpan? _duration;
private FillerMode _fillerMode;
private int? _count;
private ProgramScheduleItemCollectionType _collectionType;
public int Id { get; set; }
public string Name { get; set; }
@@ -53,7 +54,24 @@ namespace ErsatzTV.ViewModels
}
public int? PadToNearestMinute { get; set; }
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public ProgramScheduleItemCollectionType CollectionType
{
get => _collectionType;
set
{
if (_collectionType != value)
{
Collection = null;
MediaItem = null;
MultiCollection = null;
SmartCollection = null;
}
_collectionType = value;
}
}
public MediaCollectionViewModel Collection { get; set; }
public NamedMediaItemViewModel MediaItem { get; set; }
public MultiCollectionViewModel MultiCollection { get; set; }