add merging sentences action
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user