方案)
語音識別生成字幕后最常見的問題不是“有幾個錯別字”而是人名、產(chǎn)品名、斷句和口頭語影響閱讀。很多團隊會把整份 SRT 當成普通文本交給 AI 改寫結(jié)果模型順手改了序號、時間軸甚至合并或刪除片段。字幕看起來更通順了但視頻播放時會提前、延后或錯行人工也無法說清某一句是怎樣被修改的。本文先處理字幕生成的交接點再處理校正的工程邊界語音識別只生成待驗收的 SRT 初稿系統(tǒng)校驗片段結(jié)構(gòu)后保存原始版本校正環(huán)節(jié)保留時間軸讓每一處文字修改可比較、可回退、可人工確認最后才導出可播放的 SRT。上游可以是任何語音識別服務或人工初稿本文不綁定某個模型下游也可以是視頻壓制、內(nèi)容審核、翻譯或發(fā)布不假設只為某一種平臺服務。固定案例為一條 93 秒視頻的原始字幕。第 12 段將“DataPartner”識別成“data partner”第 18 段將“資產(chǎn)編號”斷成兩行。我們希望校正文字但第 12、18 段的開始和結(jié)束時間不變?nèi)绻詣有U豢煽烤庉嬋藛T能只確認或退回有疑問的片段。示例環(huán)境為 Python 3 負責 SRT 解析、規(guī)則校驗和可選 AI 校正Java 17/Spring Boot 風格服務層負責版本、人工確認和權限MySQL 8.x 保存版本與片段事實。本文不討論模型提示詞的優(yōu)劣而討論模型輸出進入生產(chǎn)系統(tǒng)前必須通過的結(jié)構(gòu)校驗。目錄字幕生成先產(chǎn)出可驗收的原稿先看一次文字變好但字幕不能播放的故障字幕生成和校正應該交付什么結(jié)果整體方案生成、解析、提議、校驗和確認SRT數(shù)據(jù)模型時間軸不可變文字版本可演進Python實現(xiàn)把模型輸出限制在可校正范圍內(nèi)Java實現(xiàn)版本對比和人工確認預期輸出和自動測試SQL驗證怎樣發(fā)現(xiàn)錯位和未確認版本上線邊界和驗收清單小結(jié)和延伸閱讀一、字幕生成先產(chǎn)出可驗收的原稿字幕不是從一段文本憑空開始的。固定案例中的AUDIO_INPUT是第 03 篇已驗收的 93 秒 WAV 音頻資產(chǎn)語音識別服務讀取它后先返回包含片段起止時間和識別文字的 SRT 初稿。這個初稿有價值但還不是可交付版本產(chǎn)品名可能識別錯誤斷句可能不自然甚至服務返回的時間軸也可能不連續(xù)。因此生成服務的職責是把識別結(jié)果變成可追蹤的RAW-V1而不是直接把文件交給視頻壓制。適配器隔離具體供應商系統(tǒng)只接收標準化的片段集合defcreate_raw_subtitle(audio_asset,recognizer,version_repository):ifaudio_asset.status!AVAILABLE:raiseValueError(音頻資產(chǎn)尚不可用)resultrecognizer.transcribe(audio_asset.storage_key,languagezh,response_formatsrt,)segmentsparse_srt(result.srt_text)validate_raw_segments(segments)# 編號唯一、時間遞增、文本非空returnversion_repository.create_raw(subtitle_noSUB-20260930-004,source_audio_idaudio_asset.id,recognizer_versionresult.model_version,segmentssegments,)validate_raw_segments失敗時系統(tǒng)記錄識別任務失敗和原始響應摘要不創(chuàng)建RAW-V1成功后才保存原始文件摘要、識別器版本、語言參數(shù)和片段集合。這樣后續(xù)發(fā)現(xiàn)“第 12 段把 DataPartner 識別錯了”時能區(qū)分是識別初稿的問題還是校正環(huán)節(jié)引入的問題。二、先看一次文字變好但字幕不能播放的故障原始 SRT 中第 12 段是12 00:00:41,200 -- 00:00:44,600 我們用 data partner 管理資產(chǎn)編號自動校正返回的文本把產(chǎn)品名改對了卻把片段拆成兩段并將結(jié)束時間寫成00:00:45,600。導出后41 秒到 45 秒的字幕與原視頻口型不再匹配。另一次校正中模型省略了第 18 段后續(xù)所有編號發(fā)生偏移人工無法從“第 18 段”定位原句。這些都不是語言模型是否聰明的問題而是系統(tǒng)把結(jié)構(gòu)數(shù)據(jù)交給了自由文本輸出。時間軸、片段編號和原文必須作為受保護字段自動工具只可以對指定片段提出文字建議不能默默改變播放邊界。固定輸入如下字幕資產(chǎn)編號SUB-20260930-004 原始版本RAW-V1 來源任意語音識別或人工初稿 校正規(guī)則產(chǎn)品名 DataPartner保留所有時間軸不合并片段 自動校正版本PROPOSAL-V2 人工確認版本CONFIRMED-V3圖1時間軸變化、片段丟失和編號漂移都會讓校正后的字幕無法安全交付。三、字幕生成和校正應該交付什么結(jié)果字幕生成和校正的交付物不是一份“看起來更通順”的文本而是一份可播放、可比較、可確認的字幕版本目標具體要求驗收方式時間軸穩(wěn)定自動校正不得修改片段開始、結(jié)束時間新舊版本按片段 ID 比較時間值完全一致片段完整不允許靜默刪除、合并或新增片段段數(shù)與片段 ID 集合一致修改可追溯每段保存原文、建議文本、修改原因和版本可展示差異并回退到原版人工可控有疑問的片段可單獨確認、駁回或改寫確認動作記錄操作者與時間導出可驗證確認版本能重新生成合法 SRT解析、排序和時間范圍校驗通過對于固定案例第 12 段的輸出應為我們用 DataPartner 管理資產(chǎn)編號但時間仍是00:00:41,200 -- 00:00:44,600。第 18 段可以保持原文并標為“需人工確認”系統(tǒng)不能為了提高自動通過率而把它刪除。四、整體方案生成、解析、提議、校驗和確認一份安全的字幕生成與校正流程分為五步步驟處理動作關鍵約束識別生成從可用音頻生成 SRT 初稿保存識別器版本和原始響應摘要原稿驗收解析為帶穩(wěn)定segment_id的片段集合編號唯一、時間遞增、文本非空生成校正提議逐段或小批量生成建議文本與原因輸入中攜帶片段 ID不允許返回自由 SRT結(jié)構(gòu)校驗與版本保存比較片段集合、時間軸、文本長度并保存差異任何時間軸變更直接拒絕不覆蓋 RAW-V1人工確認按片段確認、駁回或編輯再導出確認版只有確認版本可供視頻壓制使用這里的核心取舍是模型可以提出“第 12 段文字應該怎樣改”但不能決定“第 12 段從什么時候開始到什么時候結(jié)束”。如果業(yè)務確實需要調(diào)整時間軸應進入獨立的字幕時間軸編輯功能并要求人工操作和額外校驗不能混在文字校正里。圖2語音識別先生成原稿校正工具只負責文字提議結(jié)構(gòu)校驗、版本保存和人工確認共同保證字幕可播放。五、SRT數(shù)據(jù)模型時間軸不可變文字版本可演進用“整份 SRT 文件路徑”保存全部狀態(tài)無法支持逐段對比和確認。下面給出最小表結(jié)構(gòu)CREATETABLEsubtitle_version(idBIGINTPRIMARYKEYAUTO_INCREMENT,subtitle_noVARCHAR(64)NOTNULL,parent_version_idBIGINTNULL,version_typeVARCHAR(32)NOTNULL,version_statusVARCHAR(32)NOTNULL,source_file_keyVARCHAR(500)NOTNULL,segment_countINTNOTNULL,content_sha256CHAR(64)NOTNULL,rule_versionVARCHAR(64)NULL,created_byBIGINTNULL,create_timeDATETIMENOTNULL,confirmed_byBIGINTNULL,confirmed_timeDATETIMENULL,UNIQUEKEYuk_subtitle_version(subtitle_no,id),KEYidx_subtitle_status(subtitle_no,version_status),CHECK(version_typeIN(RAW,PROPOSAL,CONFIRMED)),CHECK(version_statusIN(DRAFT,WAITING_CONFIRM,CONFIRMED,REJECTED)));CREATETABLEsubtitle_segment(idBIGINTPRIMARYKEYAUTO_INCREMENT,version_idBIGINTNOTNULL,segment_noINTNOTNULL,start_msBIGINTNOTNULL,end_msBIGINTNOTNULL,original_textTEXTNOTNULL,corrected_textTEXTNULL,review_statusVARCHAR(32)NOTNULL,change_reasonVARCHAR(255)NULL,reviewer_idBIGINTNULL,review_timeDATETIMENULL,UNIQUEKEYuk_version_segment(version_id,segment_no),CHECK(end_msstart_ms),CHECK(review_statusIN(UNCHANGED,PROPOSED,CONFIRMED,REJECTED,MANUAL_EDITED)));RAW版本只保存原始片段和時間軸PROPOSAL繼承父版本的segment_no/start_ms/end_ms只填寫corrected_textCONFIRMED是一份可導出的確定版本。這樣每次自動校正都只是新增版本歷史不會被覆蓋人工也能只處理PROPOSED片段。圖3版本表保存來源、狀態(tài)和父子關系片段表保存時間軸、原文、建議和審核結(jié)果。六、Python實現(xiàn)把模型輸出限制在可校正范圍內(nèi)Python 側(cè)負責 SRT 解析和結(jié)構(gòu)校驗。校正服務的輸入不傳整份自由文本而是傳入固定片段 ID、原文和允許修改的字段返回結(jié)果也必須是segment_no corrected_text的 JSON 數(shù)組。fromdataclassesimportdataclassimportjsondataclass(frozenTrue)classSegment:no:intstart_ms:intend_ms:inttext:strdefvalidate_proposal(source:list[Segment],proposal_json:str)-dict[int,str]:proposalsjson.loads(proposal_json)ifnotisinstance(proposals,list):raiseValueError(校正結(jié)果必須是數(shù)組)source_ids{segment.noforsegmentinsource}proposal_ids{item.get(segment_no)foriteminproposals}ifproposal_ids!source_ids:raiseValueError(校正結(jié)果缺少片段或包含未知片段)corrected{}foriteminproposals:ifset(item)-{segment_no,corrected_text,reason}:raiseValueError(校正結(jié)果包含不允許的字段)textitem.get(corrected_text,).strip()ifnottext:raiseValueError(f片段{item[segment_no]}的校正文本為空)corrected[item[segment_no]]textreturncorrecteddefbuild_proposal(source:list[Segment],proposal_json:str)-list[dict]:correctedvalidate_proposal(source,proposal_json)return[{segment_no:item.no,start_ms:item.start_ms,end_ms:item.end_ms,original_text:item.text,corrected_text:corrected[item.no]}foriteminsource]這段代碼沒有把時間軸交給校正服務。無論模型返回多么像 SRT 的內(nèi)容系統(tǒng)都不接收它的時間字段時間軸只來自已解析的原始版本。導出 SRT 時也由系統(tǒng)根據(jù)start_ms/end_ms重建而不是直接保存模型原文。圖4模型輸出被限制為片段編號、建議文字和原因時間軸由系統(tǒng)保留。七、Java實現(xiàn)版本對比和人工確認Java 服務層創(chuàng)建提議版本、展示逐段差異并在人工確認后生成CONFIRMED版本。確認接口應鎖定版本和片段避免兩位編輯人員互相覆蓋Transactional(rollbackForException.class)publicSubtitleVersionconfirm(ConfirmSubtitleCommandcommand){SubtitleVersionproposalversionRepository.lock(command.versionId()).orElseThrow(()-newBizException(字幕版本不存在));if(!WAITING_CONFIRM.equals(proposal.status())){thrownewBizException(當前版本不需要確認);}ListSubtitleSegmentsegmentssegmentRepository.lockByVersion(proposal.id());if(segments.stream().anyMatch(item-PROPOSED.equals(item.reviewStatus())!command.reviewedSegmentNos().contains(item.segmentNo()))){thrownewBizException(仍有未處理的校正片段);}segmentRepository.applyReviews(proposal.id(),command.reviews(),command.operatorId());SubtitleVersionconfirmedversionRepository.copyAsConfirmed(proposal.id(),command.operatorId());versionRepository.markConfirmed(proposal.id(),command.operatorId());returnconfirmed;}確認時可以接受某段建議、恢復原文或提交人工改寫文本但不接受改變start_ms/end_ms。導出前再次檢查確認版本的片段編號連續(xù)、時間軸遞增、文本非空檢查不通過就拒絕導出不生成半成品 SRT。圖5人工逐段處理校正建議后才生成確認版本上線 SQL 持續(xù)檢查狀態(tài)和時間軸是否一致。八、預期輸出和自動測試固定案例的預期結(jié)果如下RAW-V1第12段 text 我們用 data partner 管理資產(chǎn)編號 PROPOSAL-V2第12段 corrected_text 我們用 DataPartner 管理資產(chǎn)編號 PROPOSAL-V2第12段 start_ms 41200end_ms 44600 CONFIRMED-V3第12段 review_status CONFIRMED 第18段可以保留原文并標記 REJECTED不影響其他片段編號測試至少覆蓋時間軸保護、片段完整和確認邊界deftest_proposal_must_cover_every_source_segment():source[Segment(12,41200,44600,我們用 data partner 管理資產(chǎn)編號)]withpytest.raises(ValueError,match缺少片段):validate_proposal(source,[])deftest_build_proposal_keeps_source_timing():source[Segment(12,41200,44600,我們用 data partner 管理資產(chǎn)編號)]resultbuild_proposal(source,[{segment_no:12,corrected_text:我們用 DataPartner 管理資產(chǎn)編號}])assertresult[0][start_ms]41200assertresult[0][end_ms]44600TestvoidshouldRejectConfirmationWhenProposedSegmentIsNotReviewed(){fixture.waitingProposalWithSegments(12,18);BizExceptionerrorassertThrows(BizException.class,()-service.confirm(newConfirmSubtitleCommand(2004L,Set.of(12),1L)));assertTrue(error.getMessage().contains(未處理的校正片段));}九、SQL驗證怎樣發(fā)現(xiàn)錯位和未確認版本查提議版本中時間軸與父版本不一致的片段SELECTp.version_id,p.segment_no,p.start_ms,r.start_msASraw_start_ms,p.end_ms,r.end_msASraw_end_msFROMsubtitle_segment pJOINsubtitle_version pvONpv.idp.version_idJOINsubtitle_segment rONr.version_idpv.parent_version_idANDr.segment_nop.segment_noWHEREpv.version_typePROPOSALAND(p.start_msr.start_msORp.end_msr.end_ms);查等待確認但已超過一天的版本SELECTsubtitle_no,id,create_timeFROMsubtitle_versionWHEREversion_statusWAITING_CONFIRMANDcreate_timeDATE_SUB(NOW(),INTERVAL1DAY);查確認版本中仍有未處理提議片段SELECTv.subtitle_no,s.segment_noFROMsubtitle_version vJOINsubtitle_segment sONs.version_idv.idWHEREv.version_statusCONFIRMEDANDs.review_statusPROPOSED;查片段時間軸倒置或重疊SELECTversion_id,segment_no,start_ms,end_msFROMsubtitle_segmentWHEREend_msstart_ms;十、上線邊界和驗收清單文字校正和時間軸編輯要明確分開。本文的規(guī)則適用于“保持時間軸只修正文案”如果需要自動斷句、合并片段、調(diào)整延遲應進入另一條需要人工審核的時間軸編輯流程。不同語言、雙語字幕、說話人標簽和富文本樣式也應作為獨立字段或版本能力不能悄悄塞進corrected_text。上線前按下面清單驗收1. 原始 SRT 可解析為連續(xù)片段時間軸和原文生成 RAW 版本。 2. 校正提議缺少片段、包含未知片段或返回空文本時被拒絕。 3. PROPOSAL 版本的時間軸與父版本逐段完全一致。 4. 每處修改可展示原文、建議文本、原因和版本來源。 5. 編輯人員可單段確認、駁回或手工改寫。 6. 未處理的 PROPOSED 片段不能生成 CONFIRMED 版本。 7. 導出 SRT 前檢查編號、時間軸和文本完整性。 8. SQL 能查出時間軸差異、長期未確認版本和確認狀態(tài)異常。十一、小結(jié)和延伸閱讀字幕校正不是讓模型重寫一份 SRT而是讓模型在受保護的時間軸上提出文字修改建議。原始版本保留事實提議版本保留差異人工確認版本才成為可交付產(chǎn)物。這樣自動化可以提高效率卻不會替人悄悄改變播放結(jié)構(gòu)。延伸閱讀SRT format overviewPython dataclassesPython jsonSpring FrameworkTransaction ManagementMySQL 8.0 Reference ManualCREATE TABLE