字幕キューの重なりと空白を、整数ミリ秒と区間の到達点で点検する小さなPython実装です。実装と14件の合成テストを実行して確認しました。音声認識や歌詞の正しさを保証するものではありません。AI LRC Generator関係者によるAI支援の記事で、独立した製品評価ではありません。
A small interval review problem
A subtitle cue can be represented as a start, an end, and some text. That representation makes a useful offline question possible: which display windows overlap, and where does the covered timeline contain a gap? The answer is not a judgment about the recording. Simultaneous lines may be intentional, and a silent interval may be correct. The goal is to expose geometry for a reviewer without changing the source or pretending to infer meaning from timestamps.
The function below accepts structured cue objects, not an audio file or a subtitle format directly. In particular, ordinary LRC commonly supplies line starts rather than explicit ends. This code does not parse LRC, manufacture end times, transcribe speech, or translate text. An upstream parser or deliberate project rule must supply the windows before this review is meaningful. Keeping that boundary explicit prevents a convenient helper from acquiring capabilities it does not have.
Why integer milliseconds
Use one time unit throughout the function. Integer milliseconds avoid repeatedly comparing binary floating-point approximations of decimal seconds. They also make the boundary convention easy to state: a window ends exactly where an adjacent window starts without an overlap or gap. This is an engineering convention for this helper, not a guarantee that every player uses identical rendering behavior at a boundary.
The type check deliberately rejects booleans. In Python, a boolean is also an instance of the integer type, so a broad instance check would accept it as a timestamp. The implementation instead requires the exact integer type. It also rejects negative starts, zero or negative durations, non-string text, and unsupported container shapes. Additional object keys are ignored; this is a narrow input contract, not a complete application schema or a validation framework.
Keep original row identities
A reviewer needs to find the original cue after a report has been generated. Each row therefore receives its original zero-based index before sorting. The function notes local backward movement in the input order, then sorts a separate list of timing tuples. It never sorts the caller's list in place or rewrites text. A report can identify an observation while leaving the decision about editing to the application or person using it.
Equal starts have a deterministic tie break: end time and original index. This means that an overlap report is reproducible for the same input, but the chosen reference row is not a semantic priority. A duet, stacked caption, or deliberate alternate-language line may overlap legitimately. The report should be presented as an inspection aid rather than a red failure badge that forces somebody to remove intentional content.
The nested-window trap
Comparing only each cue with the immediately previous cue is insufficient. Consider a long window that covers two shorter windows inside it. The second short window may start after the first short window ends, yet it still overlaps the long one. A naive previous-end comparison would incorrectly describe a gap. The useful state is the furthest end reached by any window encountered so far, together with the row that supplied that end.
For each sorted cue, compare its start with this coverage frontier. A start before the frontier produces an overlap against the frontier's owner. A start after it produces a gap in the covered timeline. Then extend the frontier only if the current end reaches further. This reports one frontier overlap per affected row, not every overlapping pair. That limitation is intentional and must remain visible to downstream callers.
Implementation
Save this exact file as cue_review.py. It uses no third-party package and makes no network requests. The text field is inspected only for a whitespace-only note; the original content is not normalized, translated, returned, or modified. Python's whitespace classification is not a language-quality check and does not identify every invisible character. A production system may need additional project-specific rules, but those should be separate from this timing calculation.
"""Offline review of integer-millisecond cue windows; no audio inference."""
def review_cues(cues):
if type(cues) is not list:
raise ValueError("cues must be a list")
rows = []
out_of_order = []
empty_text_rows = []
previous_start = None
for index, cue in enumerate(cues):
if type(cue) is not dict:
raise ValueError("each cue must be an object")
start, end, text = (cue.get(k) for k in ("start_ms", "end_ms", "text"))
if type(start) is not int or type(end) is not int:
raise ValueError("timestamps must be integers, not booleans or floats")
if start < 0 or end <= start:
raise ValueError("require 0 <= start_ms < end_ms")
if type(text) is not str:
raise ValueError("text must be a string")
if previous_start is not None and start < previous_start:
out_of_order.append(index)
previous_start = start
if not text.strip():
empty_text_rows.append(index)
rows.append((start, end, index))
overlaps, gaps = [], []
frontier, owner = None, None
for start, end, index in sorted(rows):
if frontier is not None:
if start < frontier:
overlaps.append({
"row": index, "against": owner,
"overlap_ms": min(end, frontier) - start,
})
elif start > frontier:
gaps.append({"after_ms": frontier, "before_ms": start})
if frontier is None or end > frontier:
frontier, owner = end, index
return {
"cue_count": len(rows),
"out_of_order": out_of_order,
"empty_text_rows": empty_text_rows,
"overlaps": overlaps,
"gaps": gaps,
}
Synthetic tests, not product benchmarks
Save the following file beside the implementation. I ran these fourteen unittest methods locally, including the nested-frontier example, equal-start ordering, invalid types, blank text, and an input-preservation check. All fourteen passed. Those results apply to these synthetic fixtures and this small function only. They do not measure transcription accuracy, establish player compatibility, verify any website, or prove that all possible inputs have been covered.
The nested case is particularly important because a plausible-looking short implementation can pass ordinary adjacent-window examples while still reporting a false gap. The boolean case catches a different class of mistake: a value can satisfy a familiar language-level type relationship while violating the intended data contract. Tests should capture those assumptions explicitly instead of relying on what a variable name appears to mean.
import copy
import unittest
from cue_review import review_cues
def cue(start, end, text="invented practice line"):
return {"start_ms": start, "end_ms": end, "text": text}
class CueReviewTests(unittest.TestCase):
def test_empty(self):
self.assertEqual(review_cues([])["cue_count"], 0)
def test_adjacent(self):
r = review_cues([cue(0, 100), cue(100, 200)])
self.assertEqual((r["gaps"], r["overlaps"]), ([], []))
def test_gap(self):
self.assertEqual(review_cues([cue(0, 100), cue(150, 200)])["gaps"],
[{"after_ms": 100, "before_ms": 150}])
def test_overlap(self):
self.assertEqual(review_cues([cue(0, 200), cue(150, 250)])["overlaps"],
[{"row": 1, "against": 0, "overlap_ms": 50}])
def test_nested_frontier(self):
r = review_cues([cue(0, 1000), cue(100, 200), cue(300, 400)])
self.assertEqual([x["against"] for x in r["overlaps"]], [0, 0])
self.assertEqual(r["gaps"], [])
def test_out_of_order_keeps_original_index(self):
r = review_cues([cue(200, 300), cue(0, 100)])
self.assertEqual(r["out_of_order"], [1])
self.assertEqual(r["gaps"], [{"after_ms": 100, "before_ms": 200}])
def test_equal_starts(self):
r = review_cues([cue(10, 30), cue(10, 20)])
self.assertEqual(r["overlaps"], [{"row": 0, "against": 1, "overlap_ms": 10}])
def test_boolean_rejected(self):
with self.assertRaises(ValueError):
review_cues([cue(True, 100)])
def test_float_rejected(self):
with self.assertRaises(ValueError):
review_cues([cue(0.5, 100)])
def test_negative_rejected(self):
with self.assertRaises(ValueError):
review_cues([cue(-1, 100)])
def test_zero_duration_rejected(self):
with self.assertRaises(ValueError):
review_cues([cue(100, 100)])
def test_container_and_text_types(self):
for bad in ({}, [None], [cue(0, 100, None)]):
with self.subTest(bad=bad), self.assertRaises(ValueError):
review_cues(bad)
def test_blank_text(self):
self.assertEqual(review_cues([cue(0, 100, " ")])["empty_text_rows"], [0])
def test_unicode_and_no_mutation(self):
rows = [cue(0, 100, "café — invented")]
before = copy.deepcopy(rows)
self.assertEqual(review_cues(rows)["empty_text_rows"], [])
self.assertEqual(rows, before)
if __name__ == "__main__":
unittest.main()
Run the tests from the directory containing both files. No recording, account, API token, browser session, or private lyric file is needed. The Unicode fixture is invented text and checks that the caller's data remains unchanged; it is not a multilingual recognition benchmark. Keeping the examples synthetic makes the technical behavior easier to reproduce without asking somebody to publish material they are not entitled to share.
python3 -m unittest -v test_cue_review.py
# Ran 14 tests; OK on the synthetic fixtures.
Reading the report
The cue count describes the accepted input rows. The out-of-order list identifies positions whose starts move backward relative to the preceding original row. Blank-text rows are another observation, not an instruction to delete them. Overlap entries preserve both original row numbers and an intersection length against the frontier owner. Gap entries describe uncovered time between the current frontier and the next sorted start.
Do not sum the reported overlap lengths as though they represented a unique total amount of duplicated coverage. Nested windows and reference selection make that interpretation wrong. Similarly, a gap is not proof of missing words: a recording may contain silence or an instrumental section. The helper intentionally returns descriptive structures rather than a single pass/fail score. An interface can explain each observation and let a reviewer keep an intentional condition.
Cost and limits
Sorting dominates the running time, so the straightforward implementation takes order n log n time and linear additional space for its working tuples and reports. It is not a streaming parser. A caller handling untrusted uploads should separately limit input size and decoding resources before constructing a large list. This example does not implement file ingestion, a web server, persistence, or an exhaustive interval-pair enumerator.
The helper also lacks audio duration and cannot decide whether a cue extends beyond a recording. It cannot detect a wrong recording version, a missing verse, an incorrect translation, or an unreadable display. Those are valuable checks, but attaching their names to this report would be misleading. Add them as separately specified operations with their own inputs and tests rather than quietly broadening what the interval loop claims to establish.
Where a browser editor fits
For the editing stage, the project-associated lrc generator at AI LRC Generator supports recognized or supplied lyrics, editable words and line timing, offset adjustments, previews, and lyric or subtitle exports. Editing and exports are free; automatic audio processing requires a verified account and credits. The offline function here is a separate educational example, not an integration claim or a statement about the service's internal implementation.
The useful workflow is to identify a specific timing observation, inspect it against the chosen recording, make a deliberate edit when appropriate, and test the actual export in its destination. Preserve uncertainty and permission boundaries along the way. This AI-assisted, project-associated article offers reproducible code with bounded claims; it does not promise perfect synchronization or replace a human review of the words and playback.
