diff --git a/sentence_api/DEPLOYMENT.md b/sentence_api/DEPLOYMENT.md
index 83e22ba..75960a6 100644
--- a/sentence_api/DEPLOYMENT.md
+++ b/sentence_api/DEPLOYMENT.md
@@ -158,9 +158,9 @@ curl -X POST http://127.0.0.1:8001/v1/audio/transcriptions \
`sentence_api` 不以 Whisper 返回的 segment 直接作为句子:文本会跨 segment 累积,
只有碰到句号(`.`)、问号(`?`)或逗号前的单词数达到 50 时才算一句结束;
逗号前内容不足 50 个单词时不在此处断句,会继续与后续内容合并。Whisper 在没有
-标点处断开的 segment 会自动合并成同一句。管理后台会先暂存换行拆分、相邻句合并和
-结束时间微调;关闭句子对话框时才一次性提交,后台只启动一轮拆分时间戳/翻译处理。
-拆分点优先使用对原句音频再次转写得到的词级时间戳,转写不可用时按
+标点处断开的 segment 会自动合并成同一句。管理后台的文本保存、换行拆分占位、相邻句
+合并和结束时间微调会立即入库,但不会触发翻译或转写;关闭句子对话框时才统一执行
+一轮拆分时间戳处理和翻译。拆分点优先使用对原句音频再次转写得到的词级时间戳,转写不可用时按
文本长度估算。管理后台的结束时间旁支持微调按钮;调整后下一句的起始时间联动更新,
并保证相邻句子不重叠。若转写服务支持词级时间戳
(`timestamp_granularities[]=word`,Whisper/Speaches 支持),句子的结束时间用
diff --git a/sentence_api/main.py b/sentence_api/main.py
index 8d8a69c..05721e0 100644
--- a/sentence_api/main.py
+++ b/sentence_api/main.py
@@ -812,7 +812,10 @@ def create_app(
raise HTTPException(status_code=404, detail="Course is not available.") from None
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
- application.state.subtitle_translations.refresh(video_hash)
+ if payload.process_timings:
+ application.state.subtitle_translations.refresh(video_hash)
+ else:
+ application.state.subtitle_translations.invalidate(video_hash)
return result
@application.put(
@@ -833,7 +836,7 @@ def create_app(
)
if sentence is None:
raise HTTPException(status_code=404, detail="Sentence was not found.")
- application.state.subtitle_translations.refresh(video_hash)
+ application.state.subtitle_translations.invalidate(video_hash)
return sentence
@application.put(
@@ -891,7 +894,7 @@ def create_app(
raise HTTPException(status_code=400, detail=str(exc)) from exc
if sentence is None:
raise HTTPException(status_code=404, detail="Sentence was not found.")
- application.state.subtitle_translations.refresh(video_hash)
+ application.state.subtitle_translations.invalidate(video_hash)
return sentence
@application.put(
@@ -1261,6 +1264,7 @@ def _replace_admin_sentences(
end_ms=item.end_ms,
text=item.text.strip(),
language=item.language,
+ zh_text=item.zh_text,
reference_speech_duration_ms=item.reference_speech_duration_ms,
)
)
@@ -1279,7 +1283,32 @@ def _replace_admin_sentences(
group = payload.sentences[position:group_end]
source = old_sentences.get(source_key)
if source is None:
- raise ValueError("The original sentence for a queued split no longer exists.")
+ if payload.process_timings:
+ # A split may already have been persisted with provisional
+ # boundaries. Reconstruct its audio range from the saved rows so
+ # timing refinement can be retried after a dialog is reopened.
+ missing = [
+ item
+ for item in group
+ if (item.start_ms, item.end_ms) not in old_sentences
+ ]
+ if missing:
+ raise ValueError("The original sentence for a queued split no longer exists.")
+ source = SentenceBoundary(
+ index=0,
+ start_ms=source_key[0],
+ end_ms=source_key[1],
+ text="\n".join(item.text.strip() for item in group),
+ language=next(
+ (item.language for item in group if item.language is not None),
+ None,
+ ),
+ reference_speech_duration_ms=sum(
+ item.reference_speech_duration_ms for item in group
+ ),
+ )
+ else:
+ source = None
if len(group) == 1:
if "\n" in group[0].text or "\r" in group[0].text:
raise ValueError("Unsplit batch sentences cannot contain line breaks.")
@@ -1290,9 +1319,24 @@ def _replace_admin_sentences(
end_ms=group[0].end_ms,
text=group[0].text.strip(),
language=group[0].language,
+ zh_text=group[0].zh_text,
reference_speech_duration_ms=group[0].reference_speech_duration_ms,
)
)
+ elif not payload.process_timings:
+ for item in group:
+ rows.append(
+ SentenceBoundary(
+ index=index,
+ start_ms=item.start_ms,
+ end_ms=item.end_ms,
+ text=item.text.strip(),
+ language=item.language,
+ zh_text=item.zh_text,
+ reference_speech_duration_ms=item.reference_speech_duration_ms,
+ )
+ )
+ index += 1
else:
split_text = "\n".join(item.text.strip() for item in group if item.text.strip())
replacements = split_sentence_text(
@@ -1319,6 +1363,7 @@ def _replace_admin_sentences(
if item.language is not None
else source.language
),
+ zh_text=None,
reference_speech_duration_ms=replacement.reference_speech_duration_ms,
)
)
diff --git a/sentence_api/models.py b/sentence_api/models.py
index 7d9e43f..bfc0b6d 100644
--- a/sentence_api/models.py
+++ b/sentence_api/models.py
@@ -100,6 +100,7 @@ class AdminSentenceBatchItem(BaseModel):
end_ms: int = Field(gt=0)
text: str = Field(min_length=1, max_length=4000)
language: Optional[str] = Field(default=None, max_length=32)
+ zh_text: Optional[str] = Field(default=None, max_length=4000)
reference_speech_duration_ms: int = Field(gt=0)
source_start_ms: Optional[int] = Field(default=None, ge=0)
source_end_ms: Optional[int] = Field(default=None, gt=0)
@@ -119,6 +120,7 @@ class AdminSentenceBatchUpdate(BaseModel):
model_config = ConfigDict(extra="forbid")
sentences: List[AdminSentenceBatchItem] = Field(min_length=1, max_length=10000)
+ process_timings: bool = Field(default=True)
class CourseTitleUpdate(BaseModel):
diff --git a/sentence_api/repository.py b/sentence_api/repository.py
index 09cc017..149831f 100644
--- a/sentence_api/repository.py
+++ b/sentence_api/repository.py
@@ -602,17 +602,14 @@ class VideoRepository:
with self._connect() as connection:
cursor = connection.execute(
"""
- UPDATE sentences SET text = ?, language = COALESCE(?, language)
+ UPDATE sentences SET text = ?, language = COALESCE(?, language),
+ zh_text = NULL
WHERE video_hash = ? AND sentence_index = ?
""",
(text.strip(), language, normalized_hash, sentence_index),
)
if cursor.rowcount == 0:
return None
- connection.execute(
- "UPDATE sentences SET zh_text = NULL WHERE video_hash = ?",
- (normalized_hash,),
- )
connection.execute(
"UPDATE videos SET updated_at = ? WHERE video_hash = ?",
(utc_now(), normalized_hash),
@@ -817,7 +814,7 @@ class VideoRepository:
"""
UPDATE sentences
SET end_ms = ?, text = ?, language = ?,
- reference_speech_duration_ms = ?
+ reference_speech_duration_ms = ?, zh_text = NULL
WHERE video_hash = ? AND sentence_index = ?
""",
(
@@ -858,10 +855,6 @@ class VideoRepository:
(old_index - 1, normalized_hash, old_index),
)
- connection.execute(
- "UPDATE sentences SET zh_text = NULL WHERE video_hash = ?",
- (normalized_hash,),
- )
connection.execute(
"UPDATE videos SET updated_at = ? WHERE video_hash = ?",
(utc_now(), normalized_hash),
diff --git a/sentence_api/static/admin.html b/sentence_api/static/admin.html
index d878057..fc28a24 100644
--- a/sentence_api/static/admin.html
+++ b/sentence_api/static/admin.html
@@ -139,6 +139,6 @@
-
+