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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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