Browse Source

add collection, smart collection, multi collection, playlist content sources to yaml playouts (#1841)

* add collection content to yaml playout

* add smart_collection content

* add multi_collection content

* add playlist content
pull/1842/head
Jason Dove 1 year ago committed by GitHub
parent
commit
912f79097d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs
  2. 4
      ErsatzTV.Core/Interfaces/Repositories/IMediaCollectionRepository.cs
  3. 37
      ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs
  4. 6
      ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentCollectionItem.cs
  5. 9
      ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentMultiCollectionItem.cs
  6. 11
      ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentPlaylistItem.cs
  7. 9
      ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentSmartCollectionItem.cs
  8. 8
      ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs
  9. 64
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs

6
ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs

@ -13,6 +13,9 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository
public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId) => public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId) =>
throw new NotSupportedException(); throw new NotSupportedException();
public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(string groupName, string name) =>
throw new NotSupportedException();
public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist) => public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist) =>
throw new NotSupportedException(); throw new NotSupportedException();
@ -20,8 +23,11 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository
throw new NotSupportedException(); throw new NotSupportedException();
public Task<List<MediaItem>> GetItems(int id) => _data[id].ToList().AsTask(); public Task<List<MediaItem>> GetItems(int id) => _data[id].ToList().AsTask();
public Task<List<MediaItem>> GetCollectionItemsByName(string name) => throw new NotSupportedException();
public Task<List<MediaItem>> GetMultiCollectionItems(int id) => throw new NotSupportedException(); public Task<List<MediaItem>> GetMultiCollectionItems(int id) => throw new NotSupportedException();
public Task<List<MediaItem>> GetMultiCollectionItemsByName(string name) => throw new NotSupportedException();
public Task<List<MediaItem>> GetSmartCollectionItems(int id) => _data[id].ToList().AsTask(); public Task<List<MediaItem>> GetSmartCollectionItems(int id) => _data[id].ToList().AsTask();
public Task<List<MediaItem>> GetSmartCollectionItemsByName(string name) => throw new NotSupportedException();
public Task<List<MediaItem>> GetSmartCollectionItems(string query) => throw new NotSupportedException(); public Task<List<MediaItem>> GetSmartCollectionItems(string query) => throw new NotSupportedException();
public Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids) => throw new NotSupportedException(); public Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids) => throw new NotSupportedException();
public Task<List<MediaItem>> GetPlaylistItems(int id) => throw new NotSupportedException(); public Task<List<MediaItem>> GetPlaylistItems(int id) => throw new NotSupportedException();

4
ErsatzTV.Core/Interfaces/Repositories/IMediaCollectionRepository.cs

@ -6,11 +6,15 @@ namespace ErsatzTV.Core.Interfaces.Repositories;
public interface IMediaCollectionRepository public interface IMediaCollectionRepository
{ {
Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId); Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId);
Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(string groupName, string name);
Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist); Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist);
Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id); Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id);
Task<List<MediaItem>> GetItems(int id); Task<List<MediaItem>> GetItems(int id);
Task<List<MediaItem>> GetCollectionItemsByName(string name);
Task<List<MediaItem>> GetMultiCollectionItems(int id); Task<List<MediaItem>> GetMultiCollectionItems(int id);
Task<List<MediaItem>> GetMultiCollectionItemsByName(string name);
Task<List<MediaItem>> GetSmartCollectionItems(int id); Task<List<MediaItem>> GetSmartCollectionItems(int id);
Task<List<MediaItem>> GetSmartCollectionItemsByName(string name);
Task<List<MediaItem>> GetSmartCollectionItems(string query); Task<List<MediaItem>> GetSmartCollectionItems(string query);
Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids); Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids);
Task<List<MediaItem>> GetPlaylistItems(int id); Task<List<MediaItem>> GetPlaylistItems(int id);

37
ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs

@ -3,10 +3,11 @@ using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling.BlockScheduling; using ErsatzTV.Core.Scheduling.BlockScheduling;
using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using ErsatzTV.Core.Scheduling.YamlScheduling.Models;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Core.Scheduling.YamlScheduling; namespace ErsatzTV.Core.Scheduling.YamlScheduling;
public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepository) public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepository, ILogger logger)
{ {
private readonly Dictionary<string, List<MediaItem>> _mediaItems = new(); private readonly Dictionary<string, List<MediaItem>> _mediaItems = new();
private readonly Dictionary<string, IMediaCollectionEnumerator> _enumerators = new(); private readonly Dictionary<string, IMediaCollectionEnumerator> _enumerators = new();
@ -49,7 +50,7 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor
private async Task<Option<IMediaCollectionEnumerator>> GetEnumeratorForContent( private async Task<Option<IMediaCollectionEnumerator>> GetEnumeratorForContent(
YamlPlayoutContext context, YamlPlayoutContext context,
string contentKey, string contentKey,
CancellationToken _) CancellationToken cancellationToken)
{ {
int index = context.Definition.Content.FindIndex(c => c.Key == contentKey); int index = context.Definition.Content.FindIndex(c => c.Key == contentKey);
if (index < 0) if (index < 0)
@ -69,11 +70,43 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor
items = await mediaCollectionRepository.GetShowItemsByShowGuids( items = await mediaCollectionRepository.GetShowItemsByShowGuids(
show.Guids.Map(g => $"{g.Source}://{g.Value}").ToList()); show.Guids.Map(g => $"{g.Source}://{g.Value}").ToList());
break; break;
case YamlPlayoutContentCollectionItem collection:
items = await mediaCollectionRepository.GetCollectionItemsByName(collection.Collection);
break;
case YamlPlayoutContentSmartCollectionItem smartCollection:
items = await mediaCollectionRepository.GetSmartCollectionItemsByName(smartCollection.SmartCollection);
break;
case YamlPlayoutContentMultiCollectionItem multiCollection:
items = await mediaCollectionRepository.GetMultiCollectionItemsByName(multiCollection.MultiCollection);
break;
// playlist is handled later
} }
_mediaItems[content.Key] = items; _mediaItems[content.Key] = items;
var state = new CollectionEnumeratorState { Seed = context.Playout.Seed + index, Index = 0 }; var state = new CollectionEnumeratorState { Seed = context.Playout.Seed + index, Index = 0 };
// playlist is a special case that needs to be handled on its own
if (content is YamlPlayoutContentPlaylistItem playlist)
{
if (!string.IsNullOrWhiteSpace(playlist.Order))
{
logger.LogWarning(
"Ignoring playback order {Order} for playlist {Playlist}",
playlist.Order,
playlist.Playlist);
}
Dictionary<PlaylistItem, List<MediaItem>> itemMap =
await mediaCollectionRepository.GetPlaylistItemMap(playlist.PlaylistGroup, playlist.Playlist);
return await PlaylistEnumerator.Create(
mediaCollectionRepository,
itemMap,
state,
cancellationToken);
}
switch (Enum.Parse<PlaybackOrder>(content.Order, true)) switch (Enum.Parse<PlaybackOrder>(content.Order, true))
{ {
case PlaybackOrder.Chronological: case PlaybackOrder.Chronological:

6
ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentCollectionItem.cs

@ -0,0 +1,6 @@
namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models;
public class YamlPlayoutContentCollectionItem : YamlPlayoutContentItem
{
public string Collection { get; set; }
}

9
ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentMultiCollectionItem.cs

@ -0,0 +1,9 @@
using YamlDotNet.Serialization;
namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models;
public class YamlPlayoutContentMultiCollectionItem : YamlPlayoutContentItem
{
[YamlMember(Alias = "multi_collection", ApplyNamingConventions = false)]
public string MultiCollection { get; set; }
}

11
ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentPlaylistItem.cs

@ -0,0 +1,11 @@
using YamlDotNet.Serialization;
namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models;
public class YamlPlayoutContentPlaylistItem : YamlPlayoutContentItem
{
public string Playlist { get; set; }
[YamlMember(Alias = "playlist_group", ApplyNamingConventions = false)]
public string PlaylistGroup { get; set; }
}

9
ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutContentSmartCollectionItem.cs

@ -0,0 +1,9 @@
using YamlDotNet.Serialization;
namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models;
public class YamlPlayoutContentSmartCollectionItem : YamlPlayoutContentItem
{
[YamlMember(Alias = "smart_collection", ApplyNamingConventions = false)]
public string SmartCollection { get; set; }
}

8
ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs

@ -35,7 +35,7 @@ public class YamlPlayoutBuilder(
DateTimeOffset finish = start.AddDays(daysToBuild); DateTimeOffset finish = start.AddDays(daysToBuild);
Dictionary<YamlPlayoutInstruction, IYamlPlayoutHandler> handlers = new(); Dictionary<YamlPlayoutInstruction, IYamlPlayoutHandler> handlers = new();
var enumeratorCache = new EnumeratorCache(mediaCollectionRepository); var enumeratorCache = new EnumeratorCache(mediaCollectionRepository, logger);
var context = new YamlPlayoutContext(playout, playoutDefinition, guideGroup: 1) var context = new YamlPlayoutContext(playout, playoutDefinition, guideGroup: 1)
{ {
@ -280,8 +280,12 @@ public class YamlPlayoutBuilder(
{ {
var contentKeyMappings = new Dictionary<string, Type> var contentKeyMappings = new Dictionary<string, Type>
{ {
{ "collection", typeof(YamlPlayoutContentCollectionItem) },
{ "multi_collection", typeof(YamlPlayoutContentMultiCollectionItem) },
{ "playlist", typeof(YamlPlayoutContentPlaylistItem) },
{ "search", typeof(YamlPlayoutContentSearchItem) }, { "search", typeof(YamlPlayoutContentSearchItem) },
{ "show", typeof(YamlPlayoutContentShowItem) } { "show", typeof(YamlPlayoutContentShowItem) },
{ "smart_collection", typeof(YamlPlayoutContentSmartCollectionItem) }
}; };
o.AddUniqueKeyTypeDiscriminator<YamlPlayoutContentItem>(contentKeyMappings); o.AddUniqueKeyTypeDiscriminator<YamlPlayoutContentItem>(contentKeyMappings);

64
ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs

@ -155,6 +155,21 @@ public class MediaCollectionRepository : IMediaCollectionRepository
return result; return result;
} }
public async Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(string groupName, string name)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
Option<Playlist> maybePlaylist = await dbContext.Playlists
.SelectOneAsync(p => p.Name, p => EF.Functions.Collate(p.Name, TvContext.CaseInsensitiveCollation) == name);
foreach (Playlist playlist in maybePlaylist)
{
return await GetPlaylistItemMap(playlist.Id);
}
return [];
}
public async Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist) public async Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist)
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -307,6 +322,21 @@ public class MediaCollectionRepository : IMediaCollectionRepository
return result.Distinct().ToList(); return result.Distinct().ToList();
} }
public async Task<List<MediaItem>> GetCollectionItemsByName(string name)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
Option<Collection> maybeCollection = await dbContext.Collections
.SelectOneAsync(c => c.Name, c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation) == name);
foreach (Collection collection in maybeCollection)
{
return await GetItems(collection.Id);
}
return [];
}
public async Task<List<MediaItem>> GetMultiCollectionItems(int id) public async Task<List<MediaItem>> GetMultiCollectionItems(int id)
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -342,6 +372,23 @@ public class MediaCollectionRepository : IMediaCollectionRepository
return result.DistinctBy(x => x.Id).ToList(); return result.DistinctBy(x => x.Id).ToList();
} }
public async Task<List<MediaItem>> GetMultiCollectionItemsByName(string name)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
Option<MultiCollection> maybeCollection = await dbContext.MultiCollections
.SelectOneAsync(
mc => mc.Name,
mc => EF.Functions.Collate(mc.Name, TvContext.CaseInsensitiveCollation) == name);
foreach (MultiCollection collection in maybeCollection)
{
return await GetMultiCollectionItems(collection.Id);
}
return [];
}
public async Task<List<MediaItem>> GetSmartCollectionItems(int id) public async Task<List<MediaItem>> GetSmartCollectionItems(int id)
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
@ -357,6 +404,23 @@ public class MediaCollectionRepository : IMediaCollectionRepository
return []; return [];
} }
public async Task<List<MediaItem>> GetSmartCollectionItemsByName(string name)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
Option<SmartCollection> maybeCollection = await dbContext.SmartCollections
.SelectOneAsync(
sc => sc.Name,
sc => EF.Functions.Collate(sc.Name, TvContext.CaseInsensitiveCollation) == name);
foreach (SmartCollection collection in maybeCollection)
{
return await GetSmartCollectionItems(collection.Query);
}
return [];
}
public async Task<List<MediaItem>> GetSmartCollectionItems(string query) public async Task<List<MediaItem>> GetSmartCollectionItems(string query)
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

Loading…
Cancel
Save