Browse Source

add marathon group by director (#2800)

pull/2801/head
Jason Dove 6 months ago committed by GitHub
parent
commit
e80f687612
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 3
      ErsatzTV.Core/Domain/MarathonGroupBy.cs
  3. 42
      ErsatzTV.Core/Scheduling/Engine/MarathonHelper.cs
  4. 12
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs
  5. 1
      ErsatzTV/Pages/ScheduleItemsEditor.razor

3
CHANGELOG.md

@ -21,6 +21,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add health check to warn about incompatible ffmpeg due to missing filters - Add health check to warn about incompatible ffmpeg due to missing filters
- This is directly applicable to homebrew `ffmpeg` on MacOS, which is no longer compatible with ErsatzTV - This is directly applicable to homebrew `ffmpeg` on MacOS, which is no longer compatible with ErsatzTV
- `ffmpeg@7` or `ffmpeg-full` should be used instead - `ffmpeg@7` or `ffmpeg-full` should be used instead
- Add `Marathon Group By` option `Director`
- This groups the *first* director on Movies, Episodes, Music Videos and Other Videos
- This is supported in classic schedules and sequential schedules
### Changed ### Changed
- Disable automatic artwork database cleanup - Disable automatic artwork database cleanup

3
ErsatzTV.Core/Domain/MarathonGroupBy.cs

@ -6,5 +6,6 @@ public enum MarathonGroupBy
Show = 1, Show = 1,
Season = 2, Season = 2,
Artist = 3, Artist = 3,
Album = 4 Album = 4,
Director = 5
} }

42
ErsatzTV.Core/Scheduling/Engine/MarathonHelper.cs

@ -39,6 +39,10 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository
groups.AddRange(mediaItems.GroupBy(MediaItemKeyByAlbum)); groups.AddRange(mediaItems.GroupBy(MediaItemKeyByAlbum));
itemPlaybackOrder = marathonShuffleItems ? PlaybackOrder.Shuffle : PlaybackOrder.Chronological; itemPlaybackOrder = marathonShuffleItems ? PlaybackOrder.Shuffle : PlaybackOrder.Chronological;
break; break;
case MarathonGroupBy.Director:
groups.AddRange(mediaItems.GroupBy(MediaItemKeyByDirector));
itemPlaybackOrder = marathonShuffleItems ? PlaybackOrder.Shuffle : PlaybackOrder.Chronological;
break;
default: default:
return Option<PlaylistEnumerator>.None; return Option<PlaylistEnumerator>.None;
} }
@ -111,6 +115,11 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository
{ {
groups.AddRange(allMediaItems.GroupBy(MediaItemKeyByAlbum)); groups.AddRange(allMediaItems.GroupBy(MediaItemKeyByAlbum));
} }
// group by (first) director
else if (string.Equals(groupBy, "director", StringComparison.OrdinalIgnoreCase))
{
groups.AddRange(allMediaItems.GroupBy(MediaItemKeyByDirector));
}
Dictionary<PlaylistItem, List<MediaItem>> itemMap = []; Dictionary<PlaylistItem, List<MediaItem>> itemMap = [];
@ -189,6 +198,39 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository
_ => new GroupKey(CollectionType.Collection, 0, null, null, null) _ => new GroupKey(CollectionType.Collection, 0, null, null, null)
}; };
private static GroupKey MediaItemKeyByDirector(MediaItem mediaItem) =>
mediaItem switch
{
Movie m => new GroupKey(
CollectionType.Collection,
m.MovieMetadata.HeadOrNone().Map(mm => FirstDirectorHashCode(mm.Directors)).IfNone(0),
null,
null,
null),
Episode e => new GroupKey(
CollectionType.Collection,
e.EpisodeMetadata.HeadOrNone().Map(em => FirstDirectorHashCode(em.Directors)).IfNone(0),
null,
null,
null),
MusicVideo mv => new GroupKey(
CollectionType.Collection,
mv.MusicVideoMetadata.HeadOrNone().Map(mvm => FirstDirectorHashCode(mvm.Directors)).IfNone(0),
null,
null,
null),
OtherVideo ov => new GroupKey(
CollectionType.Collection,
ov.OtherVideoMetadata.HeadOrNone().Map(ovm => FirstDirectorHashCode(ovm.Directors)).IfNone(0),
null,
null,
null),
_ => new GroupKey(CollectionType.Collection, 0, null, null, null)
};
private static int FirstDirectorHashCode(List<Director> directors) =>
directors.HeadOrNone().Select(director => director.Name.GetStableHashCode()).FirstOrDefault();
private static PlaylistItem GroupToPlaylistItem( private static PlaylistItem GroupToPlaylistItem(
int index, int index,
bool playAllItems, bool playAllItems,

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

@ -1229,9 +1229,12 @@ public class MediaCollectionRepository : IMediaCollectionRepository
private static Task<List<Movie>> GetMovieItems(TvContext dbContext, IEnumerable<int> movieIds) => private static Task<List<Movie>> GetMovieItems(TvContext dbContext, IEnumerable<int> movieIds) =>
dbContext.Movies dbContext.Movies
.TagWithCallSite()
.AsNoTracking() .AsNoTracking()
.Include(m => m.MovieMetadata) .Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Subtitles) .ThenInclude(mm => mm.Subtitles)
.Include(m => m.MovieMetadata)
.ThenInclude(mm => mm.Directors)
.Include(m => m.MediaVersions) .Include(m => m.MediaVersions)
.ThenInclude(mv => mv.Chapters) .ThenInclude(mv => mv.Chapters)
.Include(m => m.MediaVersions) .Include(m => m.MediaVersions)
@ -1291,6 +1294,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
private static Task<List<MusicVideo>> GetMusicVideoItems(TvContext dbContext, IEnumerable<int> musicVideoIds) => private static Task<List<MusicVideo>> GetMusicVideoItems(TvContext dbContext, IEnumerable<int> musicVideoIds) =>
dbContext.MusicVideos dbContext.MusicVideos
.TagWithCallSite()
.AsNoTracking() .AsNoTracking()
.Include(m => m.Artist) .Include(m => m.Artist)
.ThenInclude(a => a.ArtistMetadata) .ThenInclude(a => a.ArtistMetadata)
@ -1298,6 +1302,8 @@ public class MediaCollectionRepository : IMediaCollectionRepository
.ThenInclude(mvm => mvm.Subtitles) .ThenInclude(mvm => mvm.Subtitles)
.Include(m => m.MusicVideoMetadata) .Include(m => m.MusicVideoMetadata)
.ThenInclude(mvm => mvm.Artists) .ThenInclude(mvm => mvm.Artists)
.Include(m => m.MusicVideoMetadata)
.ThenInclude(mvm => mvm.Directors)
.Include(m => m.MediaVersions) .Include(m => m.MediaVersions)
.ThenInclude(mv => mv.Chapters) .ThenInclude(mv => mv.Chapters)
.Include(m => m.MediaVersions) .Include(m => m.MediaVersions)
@ -1318,9 +1324,12 @@ public class MediaCollectionRepository : IMediaCollectionRepository
private static Task<List<OtherVideo>> GetOtherVideoItems(TvContext dbContext, IEnumerable<int> otherVideoIds) => private static Task<List<OtherVideo>> GetOtherVideoItems(TvContext dbContext, IEnumerable<int> otherVideoIds) =>
dbContext.OtherVideos dbContext.OtherVideos
.TagWithCallSite()
.AsNoTracking() .AsNoTracking()
.Include(m => m.OtherVideoMetadata) .Include(m => m.OtherVideoMetadata)
.ThenInclude(ovm => ovm.Subtitles) .ThenInclude(ovm => ovm.Subtitles)
.Include(m => m.OtherVideoMetadata)
.ThenInclude(ovm => ovm.Directors)
.Include(m => m.MediaVersions) .Include(m => m.MediaVersions)
.ThenInclude(mv => mv.Chapters) .ThenInclude(mv => mv.Chapters)
.Include(m => m.MediaVersions) .Include(m => m.MediaVersions)
@ -1492,9 +1501,12 @@ public class MediaCollectionRepository : IMediaCollectionRepository
private static Task<List<Episode>> GetEpisodeItems(TvContext dbContext, IEnumerable<int> episodeIds) => private static Task<List<Episode>> GetEpisodeItems(TvContext dbContext, IEnumerable<int> episodeIds) =>
dbContext.Episodes dbContext.Episodes
.TagWithCallSite()
.AsNoTracking() .AsNoTracking()
.Include(e => e.EpisodeMetadata) .Include(e => e.EpisodeMetadata)
.ThenInclude(em => em.Subtitles) .ThenInclude(em => em.Subtitles)
.Include(e => e.EpisodeMetadata)
.ThenInclude(em => em.Directors)
.Include(e => e.MediaVersions) .Include(e => e.MediaVersions)
.ThenInclude(mv => mv.Chapters) .ThenInclude(mv => mv.Chapters)
.Include(m => m.MediaVersions) .Include(m => m.MediaVersions)

1
ErsatzTV/Pages/ScheduleItemsEditor.razor

@ -450,6 +450,7 @@
<MudSelectItem Value="MarathonGroupBy.Artist">Artist</MudSelectItem> <MudSelectItem Value="MarathonGroupBy.Artist">Artist</MudSelectItem>
<MudSelectItem Value="MarathonGroupBy.Season">Season</MudSelectItem> <MudSelectItem Value="MarathonGroupBy.Season">Season</MudSelectItem>
<MudSelectItem Value="MarathonGroupBy.Show">Show</MudSelectItem> <MudSelectItem Value="MarathonGroupBy.Show">Show</MudSelectItem>
<MudSelectItem Value="MarathonGroupBy.Director">Director</MudSelectItem>
</MudSelect> </MudSelect>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">

Loading…
Cancel
Save