add merging sentences action

This commit is contained in:
2026-09-04 09:09:34 +08:00
parent a38679d4a0
commit ff1341e191
6 changed files with 287 additions and 4 deletions

View File

@@ -548,6 +548,115 @@ class VideoRepository:
for row in result_rows
]
def merge_with_next_sentence(
self,
video_hash: str,
sentence_index: int,
) -> Optional[SentenceBoundary]:
"""Merge sentence_index with the immediately following sentence."""
normalized_hash = normalize_video_hash(video_hash)
with self._connect() as connection:
current = connection.execute(
"""
SELECT * FROM sentences
WHERE video_hash = ? AND sentence_index = ?
""",
(normalized_hash, sentence_index),
).fetchone()
if current is None:
return None
nxt = connection.execute(
"""
SELECT * FROM sentences
WHERE video_hash = ? AND sentence_index = ?
""",
(normalized_hash, sentence_index + 1),
).fetchone()
if nxt is None:
raise ValueError("No next sentence to merge with.")
merged_text = _join_adjacent_sentence_text(
current["text"] or "",
nxt["text"] or "",
current["language"] or nxt["language"],
)
merged_language = current["language"] or nxt["language"]
merged_speech_duration = max(
1,
int(current["reference_speech_duration_ms"] or 0)
+ int(nxt["reference_speech_duration_ms"] or 0),
)
if nxt["end_ms"] <= current["start_ms"]:
raise ValueError("Merged sentence times must increase.")
connection.execute(
"""
UPDATE sentences
SET end_ms = ?, text = ?, language = ?,
reference_speech_duration_ms = ?
WHERE video_hash = ? AND sentence_index = ?
""",
(
nxt["end_ms"],
merged_text,
merged_language,
merged_speech_duration,
normalized_hash,
sentence_index,
),
)
connection.execute(
"""
DELETE FROM sentences
WHERE video_hash = ? AND sentence_index = ?
""",
(normalized_hash, sentence_index + 1),
)
# Compact following indexes upward so sentence_index stays contiguous.
following = [
row["sentence_index"]
for row in connection.execute(
"""
SELECT sentence_index FROM sentences
WHERE video_hash = ? AND sentence_index > ?
ORDER BY sentence_index ASC
""",
(normalized_hash, sentence_index + 1),
).fetchall()
]
for old_index in following:
connection.execute(
"""
UPDATE sentences SET sentence_index = ?
WHERE video_hash = ? AND sentence_index = ?
""",
(old_index - 1, normalized_hash, old_index),
)
connection.execute(
"UPDATE videos SET updated_at = ? WHERE video_hash = ?",
(utc_now(), normalized_hash),
)
row = connection.execute(
"""
SELECT * FROM sentences
WHERE video_hash = ? AND sentence_index = ?
""",
(normalized_hash, sentence_index),
).fetchone()
return SentenceBoundary(
index=row["sentence_index"],
start_ms=row["start_ms"],
end_ms=row["end_ms"],
text=row["text"],
language=row["language"],
reference_speech_duration_ms=row["reference_speech_duration_ms"],
)
def adjust_sentence_boundary(
self,
video_hash: str,
@@ -1019,3 +1128,40 @@ class VideoRepository:
(normalize_video_hash(video_hash),),
)
return video
def _join_adjacent_sentence_text(
left: str,
right: str,
language: Optional[str],
) -> str:
"""Join two adjacent sentence texts with language-aware spacing."""
left_text = left.strip()
right_text = right.strip()
if not left_text:
return right_text
if not right_text:
return left_text
lang = (language or "").strip().lower()
if lang.startswith(("zh", "ja", "ko")):
return f"{left_text}{right_text}"
left_end = left_text[-1]
right_start = right_text[0]
if _is_cjk_char(left_end) or _is_cjk_char(right_start):
return f"{left_text}{right_text}"
if left_end.isspace() or right_start in ",.;:!?)]}、,。!?;:”’》〉】":
return f"{left_text}{right_text}"
return f"{left_text} {right_text}"
def _is_cjk_char(char: str) -> bool:
code = ord(char)
return (
0x3400 <= code <= 0x4DBF
or 0x4E00 <= code <= 0x9FFF
or 0xF900 <= code <= 0xFAFF
or 0x20000 <= code <= 0x2CEAF
)