Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59a1a4a8dc | ||
|
|
85a9afb51c | ||
|
|
246b4d7591 | ||
|
|
ae2c6350e1 | ||
|
|
ce228604e8 | ||
|
|
3656e932d3 |
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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,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();
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 369 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
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; }
|
||||
|
||||