fix: clip metadata intervals to playable duration

This commit is contained in:
2026-10-22 13:17:47 +02:00
parent fe34ce88f9
commit 7b53b00b15
4 changed files with 25 additions and 11 deletions
+7 -4
View File
@@ -4,8 +4,8 @@ import CoreFoundation
struct MiruroInterval: Sendable, Equatable {
enum Kind: Sendable { case intro, outro }
let kind: Kind
let start: Double
let end: Double
var start: Double
var end: Double
let episode: Int
var rawKind: String = ""
var provider: String? = nil
@@ -20,13 +20,16 @@ struct MiruroMetadata: Sendable {
func visibleIntervals(episode: Int, duration: Double) -> [MiruroInterval] {
guard duration.isFinite, duration > 0 else { return [] }
var intro: MiruroInterval?, outro: MiruroInterval?
for row in intervals {
for var row in intervals {
guard row.episode == episode, row.start.isFinite, row.end.isFinite,
row.start >= 0, row.end > row.start, row.end <= duration,
row.end > row.start,
!(row.provider?.range(of: "crunchyroll", options: .caseInsensitive) != nil),
(row.votes ?? 0).isFinite, (row.votes ?? 0) >= 0 else { continue }
if let length = row.referenceLength, length.isFinite, length > 0,
abs(length - duration) / duration > 0.05 { continue }
row.start = max(0, row.start)
row.end = min(duration, row.end)
guard row.end > row.start else { continue }
// Strictly greater preserves input order on ties, equivalent to stable descending votes.
switch row.kind {
case .intro:
+4 -2
View File
@@ -1614,8 +1614,10 @@ final class PlayerModel: ObservableObject {
precondition(lifecycle.accept(metadata, identity: a, generation: first), "Current metadata is accepted")
precondition(lifecycle.visibleIntervals(duration: 0).isEmpty, "Unknown duration hides bands")
precondition(lifecycle.visibleIntervals(duration: 100).count == 2, "Actual duration permits exact-end bands")
precondition(lifecycle.visibleIntervals(duration: 95).count == 1, "Duration shrink revalidates raw bands")
precondition(lifecycle.visibleIntervals(duration: 100).count == 2, "Duration growth restores raw bands")
precondition(lifecycle.visibleIntervals(duration: 95) == [
metadata.intervals[0], MiruroInterval(kind: .outro, start: 90, end: 95, episode: 1)
], "Duration shrink clips raw bands")
precondition(lifecycle.visibleIntervals(duration: 100) == metadata.intervals, "Duration growth restores raw bands")
_ = lifecycle.replace(identity: b, episode: 2)
precondition(lifecycle.metadata == nil, "Replacement clears title and intervals")
precondition(!lifecycle.accept(metadata, identity: a, generation: first), "Different-identity result is stale")