fix(analyze): extract text from Whisper JSON format
- Handle Whisper transcription format (array of segments with text) - Extract full text from transcription array before passing to DeepAnalyzer - Fix offsets parsing to support both dict and list formats Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,15 @@ class AnalyzeProcessor(BaseProcessor):
|
|||||||
metadata = self._load_json(input_data.metadata_file)
|
metadata = self._load_json(input_data.metadata_file)
|
||||||
content = self._load_json(input_data.content_file)
|
content = self._load_json(input_data.content_file)
|
||||||
|
|
||||||
|
# 处理 Whisper 字幕格式 - 提取完整文本
|
||||||
|
if content and 'transcription' in content:
|
||||||
|
# Whisper JSON 格式,提取文本
|
||||||
|
text_parts = []
|
||||||
|
for item in content.get('transcription', []):
|
||||||
|
text_parts.append(item.get('text', ''))
|
||||||
|
content = {'text': ' '.join(text_parts)}
|
||||||
|
logger.info(f"Extracted {len(content['text'])} chars from Whisper transcription")
|
||||||
|
|
||||||
# 对于文章类型,内容可能是纯文本文件
|
# 对于文章类型,内容可能是纯文本文件
|
||||||
if not content and input_data.content_type in ("article", "answer", "zhuanlan"):
|
if not content and input_data.content_type in ("article", "answer", "zhuanlan"):
|
||||||
content_path = Path(input_data.content_file)
|
content_path = Path(input_data.content_file)
|
||||||
|
|||||||
@@ -167,6 +167,92 @@ class ASRProcessor:
|
|||||||
self.state_manager.save(state)
|
self.state_manager.save(state)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def _check_existing_subtitle_files(self, input_data: ASRInput) -> Optional[ASROutput]:
|
||||||
|
"""检查是否已存在字幕文件(幂等性检查)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_data: ASR 输入数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
如果找到现有字幕文件返回 ASROutput,否则返回 None
|
||||||
|
"""
|
||||||
|
output_dir = Path(input_data.content_dir)
|
||||||
|
|
||||||
|
# 尝试两种命名格式:新格式(带 _subtitle 后缀)和旧格式
|
||||||
|
base_names = [
|
||||||
|
f"{input_data.platform}_{input_data.content_id}_subtitle",
|
||||||
|
f"{input_data.platform}_{input_data.content_id}",
|
||||||
|
]
|
||||||
|
|
||||||
|
for base_name in base_names:
|
||||||
|
json_path = output_dir / f"{base_name}.json"
|
||||||
|
srt_path = output_dir / f"{base_name}.srt"
|
||||||
|
txt_path = output_dir / f"{base_name}.txt"
|
||||||
|
|
||||||
|
if json_path.exists():
|
||||||
|
# 读取现有字幕数据
|
||||||
|
segments = []
|
||||||
|
text = ""
|
||||||
|
duration = 0.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(json_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# 解析字幕格式
|
||||||
|
if 'transcription' in data:
|
||||||
|
# Whisper 格式
|
||||||
|
for i, item in enumerate(data['transcription']):
|
||||||
|
offsets = item.get('offsets', {})
|
||||||
|
if isinstance(offsets, dict):
|
||||||
|
# 新格式:{"from": 0, "to": 5320}
|
||||||
|
start = offsets.get('from', 0) / 1000.0
|
||||||
|
end = offsets.get('to', 0) / 1000.0
|
||||||
|
else:
|
||||||
|
# 旧格式:[0, 5320]
|
||||||
|
start = offsets[0] / 1000.0 if len(offsets) > 0 else 0
|
||||||
|
end = offsets[1] / 1000.0 if len(offsets) > 1 else 0
|
||||||
|
segments.append({
|
||||||
|
'id': i,
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
'text': item.get('text', '')
|
||||||
|
})
|
||||||
|
elif 'body' in data:
|
||||||
|
# B站官方字幕格式
|
||||||
|
for i, item in enumerate(data['body']):
|
||||||
|
segments.append({
|
||||||
|
'id': i,
|
||||||
|
'start': item.get('from', 0),
|
||||||
|
'end': item.get('to', 0),
|
||||||
|
'text': item.get('content', '')
|
||||||
|
})
|
||||||
|
|
||||||
|
text = ' '.join(s['text'] for s in segments)
|
||||||
|
if segments:
|
||||||
|
duration = segments[-1]['end']
|
||||||
|
|
||||||
|
output_files = {'json': str(json_path)}
|
||||||
|
if srt_path.exists():
|
||||||
|
output_files['srt'] = str(srt_path)
|
||||||
|
if txt_path.exists():
|
||||||
|
output_files['txt'] = str(txt_path)
|
||||||
|
|
||||||
|
return ASROutput(
|
||||||
|
success=True,
|
||||||
|
tool="existing",
|
||||||
|
has_subtitle=True,
|
||||||
|
output_files=output_files,
|
||||||
|
segments=segments,
|
||||||
|
text=text,
|
||||||
|
duration_seconds=duration
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to read existing subtitle file: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
async def _try_official_subtitle(self, input_data: ASRInput) -> Optional[ASROutput]:
|
async def _try_official_subtitle(self, input_data: ASRInput) -> Optional[ASROutput]:
|
||||||
"""尝试下载 B站官方字幕
|
"""尝试下载 B站官方字幕
|
||||||
|
|
||||||
@@ -433,10 +519,19 @@ class ASRProcessor:
|
|||||||
# Whisper.cpp JSON 格式
|
# Whisper.cpp JSON 格式
|
||||||
if 'transcription' in data:
|
if 'transcription' in data:
|
||||||
for i, item in enumerate(data['transcription']):
|
for i, item in enumerate(data['transcription']):
|
||||||
|
offsets = item.get('offsets', {})
|
||||||
|
if isinstance(offsets, dict):
|
||||||
|
# 新格式:{"from": 0, "to": 5320}
|
||||||
|
start = offsets.get('from', 0) / 1000.0
|
||||||
|
end = offsets.get('to', 0) / 1000.0
|
||||||
|
else:
|
||||||
|
# 旧格式:[0, 5320]
|
||||||
|
start = offsets[0] / 1000.0 if len(offsets) > 0 else 0
|
||||||
|
end = offsets[1] / 1000.0 if len(offsets) > 1 else 0
|
||||||
segments.append({
|
segments.append({
|
||||||
'id': i,
|
'id': i,
|
||||||
'start': item.get('offsets', [0, 0])[0] / 1000.0,
|
'start': start,
|
||||||
'end': item.get('offsets', [0, 0])[1] / 1000.0,
|
'end': end,
|
||||||
'text': item.get('text', '')
|
'text': item.get('text', '')
|
||||||
})
|
})
|
||||||
text = ' '.join(s['text'] for s in segments)
|
text = ' '.join(s['text'] for s in segments)
|
||||||
|
|||||||
Reference in New Issue
Block a user