Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
793817ce42 | ||
|
|
f3125370d3 |
+2
-10
@@ -84,19 +84,11 @@ def build_notes(rows: list[dict], times, f0, conf, language: str | None,
|
||||
) -> tuple[list[TimedNote], list[float], list[str]]:
|
||||
notes: list[TimedNote] = []
|
||||
breaks: list[float] = []
|
||||
prev_line, prev_end, last_seen_line = None, 0.0, None
|
||||
prev_line, prev_end = None, 0.0
|
||||
for row in rows:
|
||||
# last_seen_line tracks every row (skipped or not) so a run of
|
||||
# skipped rows can't trigger a duplicate break on the next row of
|
||||
# the same still-unseen line; prev_line/prev_end only advance on
|
||||
# rows that actually emit notes, so a skipped row can't corrupt
|
||||
# line_initial or break timing for its own line's later words.
|
||||
if last_seen_line is not None and row["line_index"] > last_seen_line:
|
||||
if prev_line is not None and row["line_index"] > prev_line:
|
||||
breaks.append((prev_end + row["start"]) / 2)
|
||||
last_seen_line = row["line_index"]
|
||||
line_initial = row["line_index"] != prev_line
|
||||
if any(a <= row["start"] < b for a, b in missing_spans):
|
||||
continue
|
||||
prev_line, prev_end = row["line_index"], row["end"]
|
||||
segs = segment_span(times, f0, conf, row["start"], row["end"])
|
||||
if not segs:
|
||||
|
||||
@@ -47,9 +47,8 @@ def classify_lyrics_match(segments, ref: RefLyrics, pad: float = 1.0
|
||||
dropped_lines: reference line indices covered only by 'insert' opcodes
|
||||
(present in the lyrics, never heard). missing_spans: the padded, merged
|
||||
bounds of the heard segments behind 'delete' opcodes (heard, but matching
|
||||
no reference word) — scoped tightly to the unmatched audio itself, not
|
||||
the whole vocal section it falls in, so matched lines sharing a section
|
||||
with an ad-lib keep their notes.
|
||||
no reference word). A segment can also hold matched lines, so a span
|
||||
marks where to look, not words to discard.
|
||||
"""
|
||||
matcher, seg_of, line_of = match_lyrics(segments, ref)
|
||||
covered = [False] * len(line_of)
|
||||
|
||||
+9
-22
@@ -297,15 +297,19 @@ def test_notes_stage_registered():
|
||||
assert "notes" in [s.name for s in all_stages()]
|
||||
|
||||
|
||||
def test_build_notes_skips_notes_within_missing_span():
|
||||
def test_build_notes_keeps_matched_words_within_missing_span():
|
||||
# A missing span covers whole Whisper segments, so it can hold matched
|
||||
# lines alongside the unmatched singing; those lines must keep their notes.
|
||||
times, f0, conf = flat_pitch()
|
||||
rows = [
|
||||
{"word": "keep", "start": 1.0, "end": 1.4, "score": 0.9, "line_index": 0},
|
||||
{"word": "skip", "start": 5.0, "end": 5.4, "score": 0.9, "line_index": 1},
|
||||
{"word": "held", "start": 5.0, "end": 5.4, "score": 0.9, "line_index": 1},
|
||||
]
|
||||
notes, _, _ = build_notes(rows, times, f0, conf, "English",
|
||||
missing_spans=[(4.5, 6.0)])
|
||||
assert [n.text for n in notes] == ["keep"]
|
||||
notes, breaks, warnings = build_notes(rows, times, f0, conf, "English",
|
||||
missing_spans=[(4.5, 6.0)])
|
||||
assert [n.text for n in notes] == ["keep", "held"]
|
||||
assert breaks == [(1.4 + 5.0) / 2]
|
||||
assert warnings == ["missing lyrics near 0:04–0:06"]
|
||||
|
||||
|
||||
def test_warnings_report_dropped_lines():
|
||||
@@ -325,23 +329,6 @@ def test_warnings_report_missing_spans():
|
||||
assert warnings == ["missing lyrics near 1:21–1:29"]
|
||||
|
||||
|
||||
def test_build_notes_skipped_row_mid_line_does_not_corrupt_spacing_or_breaks():
|
||||
# "gone" falls inside a missing span and is skipped; "away" is the same
|
||||
# line's next word and must still get its leading space, and the break
|
||||
# before the next line must use the last *emitted* word's end (3.4, from
|
||||
# "away"), not the skipped row's end (2.4).
|
||||
times, f0, conf = flat_pitch()
|
||||
rows = [
|
||||
{"word": "keep", "start": 1.0, "end": 1.4, "score": 0.9, "line_index": 0},
|
||||
{"word": "gone", "start": 2.0, "end": 2.4, "score": 0.9, "line_index": 0},
|
||||
{"word": "away", "start": 3.0, "end": 3.4, "score": 0.9, "line_index": 0},
|
||||
{"word": "next", "start": 5.0, "end": 5.4, "score": 0.9, "line_index": 1},
|
||||
]
|
||||
notes, breaks, _ = build_notes(rows, times, f0, conf, "English",
|
||||
missing_spans=[(1.9, 2.5)])
|
||||
assert [n.text for n in notes] == ["keep", " away", "next"]
|
||||
assert breaks == [(3.4 + 5.0) / 2]
|
||||
|
||||
def test_notes_carry_line_index():
|
||||
times, f0, conf = flat_pitch()
|
||||
rows = [{"word": "hey", "start": 1.0, "end": 1.4, "score": 0.9, "line_index": 3}]
|
||||
|
||||
@@ -53,7 +53,7 @@ def test_classify_lyrics_match_keeps_partially_matched_lines():
|
||||
def test_classify_lyrics_match_scopes_missing_span_to_adlib_not_whole_section():
|
||||
# matched line and an unmatched ad-lib close enough together that
|
||||
# vocal_sections would merge them into one section; missing_spans must
|
||||
# stay tight around the ad-lib so the matched line keeps its notes.
|
||||
# stay tight around the ad-lib so the review marks the right place.
|
||||
ref = normalize_lyrics("it's one two three\nfour five six seven eight")
|
||||
segs = [seg(1, 4, "it's one two three four five six seven eight"),
|
||||
seg(6, 8, "completely unrelated ad lib")]
|
||||
|
||||
Reference in New Issue
Block a user