mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* add setting to keep multi-part episodes together * keep multi-part episodes together when shufflingpull/189/head
23 changed files with 2754 additions and 111 deletions
@ -0,0 +1,86 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Scheduling |
||||||
|
{ |
||||||
|
internal class ChronologicalMediaComparer : IComparer<MediaItem> |
||||||
|
{ |
||||||
|
public int Compare(MediaItem x, MediaItem y) |
||||||
|
{ |
||||||
|
if (x == null || y == null) |
||||||
|
{ |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
DateTime date1 = x switch |
||||||
|
{ |
||||||
|
Episode e => e.EpisodeMetadata.HeadOrNone().Match( |
||||||
|
em => em.ReleaseDate ?? DateTime.MaxValue, |
||||||
|
() => DateTime.MaxValue), |
||||||
|
Movie m => m.MovieMetadata.HeadOrNone().Match( |
||||||
|
mm => mm.ReleaseDate ?? DateTime.MaxValue, |
||||||
|
() => DateTime.MaxValue), |
||||||
|
MusicVideo mv => mv.MusicVideoMetadata.HeadOrNone().Match( |
||||||
|
mvm => mvm.ReleaseDate ?? DateTime.MaxValue, |
||||||
|
() => DateTime.MaxValue), |
||||||
|
_ => DateTime.MaxValue |
||||||
|
}; |
||||||
|
|
||||||
|
DateTime date2 = y switch |
||||||
|
{ |
||||||
|
Episode e => e.EpisodeMetadata.HeadOrNone().Match( |
||||||
|
em => em.ReleaseDate ?? DateTime.MaxValue, |
||||||
|
() => DateTime.MaxValue), |
||||||
|
Movie m => m.MovieMetadata.HeadOrNone().Match( |
||||||
|
mm => mm.ReleaseDate ?? DateTime.MaxValue, |
||||||
|
() => DateTime.MaxValue), |
||||||
|
MusicVideo mv => mv.MusicVideoMetadata.HeadOrNone().Match( |
||||||
|
mvm => mvm.ReleaseDate ?? DateTime.MaxValue, |
||||||
|
() => DateTime.MaxValue), |
||||||
|
_ => DateTime.MaxValue |
||||||
|
}; |
||||||
|
|
||||||
|
if (date1 != date2) |
||||||
|
{ |
||||||
|
return date1.CompareTo(date2); |
||||||
|
} |
||||||
|
|
||||||
|
int season1 = x switch |
||||||
|
{ |
||||||
|
Episode e => e.Season?.SeasonNumber ?? int.MaxValue, |
||||||
|
_ => int.MaxValue |
||||||
|
}; |
||||||
|
|
||||||
|
int season2 = y switch |
||||||
|
{ |
||||||
|
Episode e => e.Season?.SeasonNumber ?? int.MaxValue, |
||||||
|
_ => int.MaxValue |
||||||
|
}; |
||||||
|
|
||||||
|
if (season1 != season2) |
||||||
|
{ |
||||||
|
return season1.CompareTo(season2); |
||||||
|
} |
||||||
|
|
||||||
|
int episode1 = x switch |
||||||
|
{ |
||||||
|
Episode e => e.EpisodeNumber, |
||||||
|
_ => int.MaxValue |
||||||
|
}; |
||||||
|
|
||||||
|
int episode2 = y switch |
||||||
|
{ |
||||||
|
Episode e => e.EpisodeNumber, |
||||||
|
_ => int.MaxValue |
||||||
|
}; |
||||||
|
|
||||||
|
if (episode1 != episode2) |
||||||
|
{ |
||||||
|
return episode1.CompareTo(episode2); |
||||||
|
} |
||||||
|
|
||||||
|
return x.Id.CompareTo(y.Id); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Scheduling |
||||||
|
{ |
||||||
|
public record GroupedMediaItem(MediaItem First, List<MediaItem> Additional); |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using System.Text.RegularExpressions; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using static LanguageExt.Prelude; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Scheduling |
||||||
|
{ |
||||||
|
public static class MultiPartEpisodeGrouper |
||||||
|
{ |
||||||
|
public static List<GroupedMediaItem> GroupMediaItems(IList<MediaItem> mediaItems) |
||||||
|
{ |
||||||
|
var sortedMediaItems = mediaItems.OrderBy(identity, new ChronologicalMediaComparer()).ToList(); |
||||||
|
var groups = new List<GroupedMediaItem>(); |
||||||
|
GroupedMediaItem group = null; |
||||||
|
var lastNumber = 0; |
||||||
|
foreach (MediaItem item in sortedMediaItems) |
||||||
|
{ |
||||||
|
if (item is Episode e) |
||||||
|
{ |
||||||
|
const string PATTERN = @"^.*\((\d+)\)$"; |
||||||
|
Match match = Regex.Match(e.EpisodeMetadata.Head().Title, PATTERN); |
||||||
|
if (match.Success) |
||||||
|
{ |
||||||
|
var number = int.Parse(match.Groups[1].Value); |
||||||
|
if (number == lastNumber + 1) |
||||||
|
{ |
||||||
|
if (lastNumber == 0) |
||||||
|
{ |
||||||
|
// start a new group
|
||||||
|
group = new GroupedMediaItem(item, null); |
||||||
|
} |
||||||
|
else if (group != null) |
||||||
|
{ |
||||||
|
// add to current group
|
||||||
|
List<MediaItem> additional = group.Additional ?? new List<MediaItem>(); |
||||||
|
additional.Add(item); |
||||||
|
group = group with { Additional = additional }; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// this should never happen
|
||||||
|
throw new InvalidOperationException("Bad shuffle state"); |
||||||
|
} |
||||||
|
|
||||||
|
lastNumber = number; |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
if (group != null && lastNumber != 0) |
||||||
|
{ |
||||||
|
groups.Add(group); |
||||||
|
group = null; |
||||||
|
lastNumber = 0; |
||||||
|
} |
||||||
|
|
||||||
|
groups.Add(new GroupedMediaItem(item, null)); |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
groups.Add(new GroupedMediaItem(item, null)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return groups; |
||||||
|
} |
||||||
|
|
||||||
|
public static IList<MediaItem> FlattenGroups(GroupedMediaItem[] copy, int mediaItemCount) |
||||||
|
{ |
||||||
|
var result = new MediaItem[mediaItemCount]; |
||||||
|
var i = 0; |
||||||
|
foreach (GroupedMediaItem group in copy) |
||||||
|
{ |
||||||
|
result[i++] = group.First; |
||||||
|
foreach (MediaItem additional in Optional(group.Additional).Flatten()) |
||||||
|
{ |
||||||
|
result[i++] = additional; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@ |
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations |
||||||
|
{ |
||||||
|
public partial class Add_ProgramSchedule_KeepMultiPartEpisodesTogether : Migration |
||||||
|
{ |
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) => |
||||||
|
migrationBuilder.AddColumn<bool>( |
||||||
|
"KeepMultiPartEpisodesTogether", |
||||||
|
"ProgramSchedule", |
||||||
|
"INTEGER", |
||||||
|
nullable: false, |
||||||
|
defaultValue: false); |
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) => |
||||||
|
migrationBuilder.DropColumn( |
||||||
|
"KeepMultiPartEpisodesTogether", |
||||||
|
"ProgramSchedule"); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue