mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-07-07 15:41:22 +08:00
feat(extension+backend): 插件直接在浏览器里抓 B 站字幕,跳过后端 download_subtitles
之前 B 站字幕优先逻辑放在后端的 BilibiliSubtitleFetcher,需要后端通过 CookieConfigManager
管理 SESSDATA cookie 才能拿 AI 字幕。这次改为:插件在用户浏览器里直接抓字幕,
天然带着用户当前登录态的 cookie;后端只负责把传过来的字幕当作转写缓存。
extension:
- 新增 logic/bilibili-subtitle.ts,调 /x/web-interface/view → /x/player/wbi/v2 → 字幕 URL JSON
· service worker fetch 走 credentials:'include',借 manifest host_permissions:'*://*/*'
自动带 .bilibili.com 域 cookie,并绕过 CORS
· 优先级:人工中文 > AI 中文 > 任意非空
- popup start() 与 background startTask() 在 platform === 'bilibili' 时先调一次抓取,
结果作为 prefetched_transcript 字段塞到 /api/generate_note payload
- types.ts GenerateRequest 增加 prefetched_transcript 字段
backend:
- VideoRequest 增加可选 prefetched_transcript: dict
- generate_note endpoint 收到时调 _persist_prefetched_transcript() 写到
NOTE_OUTPUT_DIR/<task_id>_transcript.json;NoteGenerator 的 cache-hit 逻辑天然命中,
跳过 downloader.download_subtitles 和音频转写,直接走 GPT 总结
- 字幕清洗:去掉空 segment、必要时合成 full_text、language 默认 'zh'
效果:B 站登录用户的视频,从用户点击到 GPT 拿到全文,省掉一次后端 → B 站 API 的来回,
也彻底告别了 backend 那侧的 cookie 配置心智负担。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,10 @@ class VideoRequest(BaseModel):
|
||||
video_understanding: Optional[bool] = False
|
||||
video_interval: Optional[int] = 0
|
||||
grid_size: Optional[list] = []
|
||||
# 客户端(如浏览器插件)已经在用户浏览器里抓到字幕,直接传给后端复用,
|
||||
# 跳过 download_subtitles 和音频转写。形如:
|
||||
# {"language": "zh", "full_text": "...", "segments": [{"start","end","text"}, ...]}
|
||||
prefetched_transcript: Optional[dict] = None
|
||||
|
||||
@field_validator("video_url")
|
||||
def validate_supported_url(cls, v):
|
||||
@@ -74,6 +78,40 @@ def save_note_to_file(task_id: str, note):
|
||||
json.dump(asdict(note), f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def _persist_prefetched_transcript(task_id: str, transcript: dict) -> None:
|
||||
"""把客户端预取的字幕写到 NoteGenerator 期望的转写缓存文件里。
|
||||
|
||||
NoteGenerator.generate 会优先读 <task_id>_transcript.json,命中即跳过 download_subtitles
|
||||
与音频转写流程。要求字段:language(可空)/full_text/segments[{start,end,text}]
|
||||
"""
|
||||
segments = transcript.get("segments") or []
|
||||
cleaned_segments = []
|
||||
for s in segments:
|
||||
text = (s.get("text") or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
cleaned_segments.append({
|
||||
"start": float(s.get("start", 0)),
|
||||
"end": float(s.get("end", 0)),
|
||||
"text": text,
|
||||
})
|
||||
if not cleaned_segments:
|
||||
raise ValueError("prefetched_transcript 没有可用的 segments")
|
||||
|
||||
full_text = transcript.get("full_text") or " ".join(s["text"] for s in cleaned_segments)
|
||||
payload = {
|
||||
"language": transcript.get("language") or "zh",
|
||||
"full_text": full_text,
|
||||
"segments": cleaned_segments,
|
||||
}
|
||||
|
||||
os.makedirs(NOTE_OUTPUT_DIR, exist_ok=True)
|
||||
target = os.path.join(NOTE_OUTPUT_DIR, f"{task_id}_transcript.json")
|
||||
with open(target, "w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, ensure_ascii=False, indent=2)
|
||||
logger.info(f"已写入客户端预取字幕缓存: {target} ({len(cleaned_segments)} 段)")
|
||||
|
||||
|
||||
def run_note_task(task_id: str, video_url: str, platform: str, quality: DownloadQuality,
|
||||
link: bool = False, screenshot: bool = False, model_name: str = None, provider_id: str = None,
|
||||
_format: list = None, style: str = None, extras: str = None, video_understanding: bool = False,
|
||||
@@ -163,6 +201,13 @@ def generate_note(data: VideoRequest, background_tasks: BackgroundTasks):
|
||||
# 统一先写入 PENDING,表示已进入队列等待串行执行
|
||||
NoteGenerator()._update_status(task_id, TaskStatus.PENDING)
|
||||
|
||||
# 客户端已经抓好字幕的话,写到转写缓存文件,NoteGenerator 的 cache-hit 逻辑会直接用上
|
||||
if data.prefetched_transcript:
|
||||
try:
|
||||
_persist_prefetched_transcript(task_id, data.prefetched_transcript)
|
||||
except Exception as e:
|
||||
logger.warning(f"写入预取字幕失败 (task_id={task_id}): {e}")
|
||||
|
||||
background_tasks.add_task(run_note_task, task_id, data.video_url, data.platform, data.quality, data.link,
|
||||
data.screenshot, data.model_name, data.provider_id, data.format, data.style,
|
||||
data.extras, data.video_understanding, data.video_interval, data.grid_size)
|
||||
|
||||
Reference in New Issue
Block a user