Auto-Cut - 經實測的靜音與冗詞修剪
原則
- 在建立場景/字幕前先裁切。 事後裁切會打亂所有時間戳(字幕、縮放、音效)。此步驟的輸出:
assets/<source>.cut.mp4+assets/transcript.cut.json- 後續所有步驟皆使用裁切後的版本。 - 當任務簡報啟用 autoCut 時,裁切是強制性的 - 而非建議。若無任何內容可裁切,你必須在報告中說明具體原因(例如影片已相當精簡)。
- 門檻值存在於伺服器中,而非你的腦中。 請勿自行執行
silencedetect,也勿憑感覺選擇 dB 門檻。兩個經實測的事實已淘汰此工作流程:- 正確的門檻是檔案的屬性,而非常數。在某個真實檔案中,-40dB 找到 0 個靜音,-30dB 找到 13 個,-25dB 找到 21 個。硬編碼的數字不是無用就是會吃掉語音。
- 單靠音量無法區分「停頓」與「小聲說話」。在測量的來源檔案中,
silencedetect在 -30dB 下報告了 48.6 秒的「靜音」,但其中僅 18.2 秒確實位於詞與詞之間的真實間隙;其餘 30.4 秒則落在詞語內部(音節間斷、越南語無聲韻尾 c/t/p/ch)。僅憑音量裁切會吞噬語音。 伺服器負責測量(autoTrim.ts)與候選生成(deadWeight.ts)。你負責審查。 這個分工正是此工具的關鍵。
- 兩類冗詞會被裁切:靜音(機器測量,受字幕保護)與內容冗詞(填充語、結巴、重新陳述的片段 - 由伺服器提出,你批准;加上重複論點,這僅有你才能找到 - 見步驟 3)。
步驟 0 - 前提:具備含詞時間戳的字幕
整個保護機制依賴於此。伺服器會先尋找 assets/transcript.raw.json,再尋找 assets/transcript.json。若無字幕,分析仍會執行,但回傳 guarded: false,冗詞清單為空,且數值在兩個方向上都是猜測(它可能放過錯誤的裁切,也可能阻擋正確的裁切)。請先進行轉錄 - 切勿在無保護的情況下修剪越南語說話人影片。
步驟 1 - 分析(免費、可重複執行、無需編碼)
POST http://localhost:6869/api/projects/<id>/auto-trim/analyze
body: {} # 或 { "source": "assets/face.mp4", "level": "tight" }
level 可為 natural | default | tight,預設值為 brief.autoCutLevel。回應:
silence-durationSec、選定的thresholdDb與thresholdNote(該門檻勝出的原因)、測量的noiseFloorDb、將被裁切的silences、keepRanges、removedSec、完整的sweep表格,以及wordGuard(字幕否決的秒數)。deadWeight-candidates[],包含kind(filler | stutter | repeat-take | hesitation)、start/end、text、confidence、reason、context;以及totalSec和byKind。guarded- 僅在找到字幕時為true。若為false,請先修正此問題再進行裁切。
此步驟不進行編碼,因此你可隨意呼叫(一個 217 秒的來源檔案約在一秒內完成分析)。
步驟 2 - 審查每個候選項(你的工作,而非機器的)
此清單是確定性的:相同的字幕會產生相同的候選項與相同的信心度。機器無法理解的是意義。請逐一檢查,僅保留你願意為其辯護的項目:
- 低信心度意味著「先閱讀上下文」,而非「可能沒問題」。 越南語填充詞幾乎總是與真實詞語衝突。
đó、ấy、thế、mà、là既可能是填充詞,也可能是真實的指示詞/連接詞。 - 連接詞片語(
hoặc là、tức là、bởi vì là、với lại là)是陷阱。 在一個真實字幕中測量:「…có thể là ok ứng dụng nó / Hoặc là / Tham khảo để tìm cách…」是一個真正的替代選項 - 裁切它會破壞句子的一個分支。但「Còn trường hợp mà mọi người có thể nghe / Hoặc là / Còn trường hợp mà người AI không ứng dụng được…」則是一個被放棄的句子,應該被移除。兩者的表層形式相同;唯有意義能區分它們。這正是它們以低基礎信心度回傳,且原因標示為「請閱讀上下文」的原因。 repeat-take候選項:在批准前,請確認後續的錄製版本確實更完整。hesitation候選項是詞與詞之間純粹的靜音 - 通常安全,但請確認你沒有移除一個在笑點前刻意安排的停頓。
請自由拒絕。一個留下的填充詞約耗時 0.4 秒;但一個被裁切的真實詞語會讓句子變得無意義,觀眾會立即察覺。
步驟 3 - 重複論點(僅你能找到這些 - 強制性)
沒有任何偵測器能捕捉到這點,而這正是使用者最常抱怨的冗詞。請將字幕視為一段口語來閱讀,並進行語意分析:
- 將表達相同論點的句子分組 - 詞語不必相同,僅需內容一致。說話者經常將一個論點重複 2-3 次:先簡短/結巴,後更完整。也請考慮不相鄰的句子(先提出論點 A,離題,再回歸論點 A 並更詳細說明)。
- 每組中僅保留一個版本 - 最完整的那個:
- 前段簡短版本,後段更完整的重新陳述 -> 保留後者,裁切前者。
- 後段句子僅為短暫回聲(「đúng vậy, như tôi nói…」) -> 保留前者,裁撤回聲。
- 兩個等同的版本 -> 保留表達更流暢的那個(結巴較少、無填充詞)。
- 裁切後檢查整體流暢度:從頭到尾重新閱讀保留的文字。若某個保留的句子提及(「như vừa nói」)一個你已裁切的句子,請兩者都保留,或選擇無回指的那個版本。
- 將每個決策轉換為
{start, end}區間,並與已批准的候選項一同在步驟 4 中發送。 - 在裁切前建立一個表格(將其放入報告中):
時間戳 | 被裁切的句子 | 原因 | 保留的句子- 以便使用者審查每個決策。
步驟 4 - 應用(透過渲染佇列執行)
POST http://localhost:6869/api/projects/<id>/auto-trim/apply
body: { "cutCandidates": [{ "start": 51.81, "end": 52.25 }, …] } # 僅限你批准的項目
-> 202 { job }
若你未批准任何項目,請發送空清單 - 經測量的靜音仍會被裁切。接著輪詢 GET /api/jobs/<jobId> 直至完成。此工作將:
- 以字幕保護重新分析,將你批准的區間與靜音區間合併(重疊部分合併,因此不會重複計算)。
- 丟棄任何會切到詞語中點的已批准區間 - 這是針對你審查的最後一道安全網,並會記錄所有被拒絕的區間。記錄中被拒絕的區間意味著你批准了位於真實語音上的項目;請回去重新閱讀該處。
- 在單次 ffmpeg 過程中進行裁切(
trim/atrim + setpts/asetpts + concat),產出assets/<stem>.cut.mp4。 - 將所有時間戳重新對映到裁切後時間軸 ->
assets/transcript.cut.json。 - 使用重新對映後的詞語驗證輸出,並產出
assets/auto-trim-report.json。
若無任何值得裁切的內容,該工作將刻意不產出 .cut.mp4(重新編碼的相同副本只會降低畫質) - 報告中的 output 為 null,你將繼續使用原始來源。
步驟 5 - 閱讀報告(在工作完成前你都不算完成)
assets/auto-trim-report.json 包含:裁切前/後時長、removed.silenceSec 與 removed.approvedSec、所選門檻及其原因、被拒絕的候選項,以及 verification。
verdict: "pass"- 結果符合該等級的設定檔。verdict: "fail"- 工作仍完成且檔案可用,但未符合設定檔。日誌會明確說明。請批准更多冗詞(步驟 2/3)並重新執行應用,或於最終報告中說明你接受此結果的原因。切勿在忽略「失敗」判決的情況下將裁切視為完成。
通過標準不再是舊有的「無靜音 > 0.8 秒」規則 - 測量顯示該規則過於寬鬆。一個真實檔案通過該規則,同時卻攜帶 13 個殘留靜音(0.45-0.67 秒),總計 6.7 秒(佔運行時間的 4.2%),聽起來明顯拖沓。現在的設定檔同時限制了最長單一靜音與總比例(default:0.5 秒 / 3%)。
步驟 6 - 在後續所有環節使用裁切後版本
從此刻起,字幕、關鍵布局、音效時序與縮放皆讀取 assets/transcript.cut.json 與 .cut.mp4。品質檢查 dead-air 會重新測量組合後的影片與字幕的對應關係,並在 brief.autoCut 啟用且空白音仍超過設定檔時判定為失敗 - 這是最後一道防線,而非主要計畫。
最終報告必須包含:步驟 3 的裁切表格、以秒為單位區分的靜音與已批准冗詞,以及驗證判決。請從 auto-trim-report.json 中取得數值。
已知問題
- 在裁切後使用舊版字幕 -> 字幕會朝結尾處逐漸偏移。請務必切換為
transcript.cut.json。 - 僅裁切靜音而忽略重複論點 - 使用者所謂的「冗詞」大部分來自步驟 3。
- 裁切已渲染的草稿而非原始來源 -> 二次編碼導致畫質下降。請務必從來源裁切(伺服器拒絕覆寫來源,且在選取預設值時會略過已命名為
*.cut.*的檔案)。 - 在唯一字幕為原始版本時分析
.cut.mp4-> 保護機制對應到錯誤的時間軸。請確保來源檔案與其描述的字幕相匹配。 - SFX/旁白檔案自帶前導靜音 -> 使用
data-media-start在 HyperFrames 內部進行修剪(參閱 noti-tiktok-vn 技能中的「SFX 響度與前導靜音」部分),無需為了 0.3 秒的前導靜音重新編碼音檔。 - 連接裁切後的片段時未添加淡入淡出 -> 每個連接點都會產生點擊聲。已在
autoTrim.ts中修正;下方為相關規則,適用於任何會裁切或連接音訊的新程式碼。
音訊:在每個裁切邊緣進行 30 毫秒淡入淡出(嚴格規則)
裁切發生在波形中間。一個片段的最後一個樣本與下一個片段的第一個樣本毫無關聯,因此連接點形成一個垂直階梯,而階梯即為點擊聲。其原因非編碼器也非來源 - 是連接本身所致,因此任何下游的重新編碼都無法消除。
其影響程度取決於裁切位置,且差異巨大。 在一個真實的 278 秒說話人影片上進行無損測量,比較有無淡入淡出下的相同裁切(連接點處最大的樣本間階梯;任何高於 ~0.02 的值即可聽見):
| 連接類型 | 之前 | 之後 |
|---|---|---|
靜音修剪,40 個連接點(balanced) | 中位數 0.0040,最差 0.0113 - 無一可聽見 | 中位數 0.0000 |
| 語音中段,4 個連接點(冗詞 / 重複論點) | 0.1195, 0.0744, 0.0232, 0.0064 - 4 個中有 3 個可聽見 | 全部 ≤ 0.0008 |
作為參考,這些檔案中正常語音內任何地方的最大階梯值為 0.06-0.13。因此,未加淡入淡出的語音中段連接點可能產生比內容本身任何階梯都要大的跳躍,而靜音連接點則遠不及可聽閾值。
請勿因第一行數據而省略淡入淡出。純靜音修剪是剛好無關緊要的情況;步驟 3(重複論點)與片段提取會直接切過語音,而這些連接點正是會發出點擊聲的部分。
修復方式:對每個片段的兩個邊緣進行 30 毫秒淡入淡出。時間長到足以消除階梯,也短到無法被感知為淡入淡出效果。請使用 apps/server/src/util.ts 中的 audioCutFade();切勿自行撰寫濾鏡。
[0:a]atrim=start=S:end=E,asetpts=PTS-STARTPTS,afade=t=in:st=0:d=0.030,afade=t=out:st=D-0.030:d=0.030[aN]
三個會讓此效果無聲無息失效的情況:
- 順序。
afade必須放在asetpts=PTS-STARTPTS之後。afade從串流自身的時鐘讀取st;若使用原始時間戳,st=0已成過去,淡入效果永遠不會觸發。 - 長度。
D是片段時長,因此淡出開始時間為D - 0.030。請將淡出長度限制在D/2內,否則在短片段上兩個淡入淡出會重疊並吞噬整個片段。 - 提取片段,而非僅連接。 從較長影片中裁切出的片段,即使無需連接,其兩個原始邊緣仍然存在 -
reframe.ts透過-af對其進行淡入淡出。
關於自行測量的一個備註:請勿比較兩個 AAC 編碼。重新編碼會擾亂各處的樣本,這會將 40 個真實連接點埋藏在 294 個假性差異區域中,並在兩個檔案之間引入 54 毫秒的偏移。請改為將兩個變體直接渲染為 pcm_s16le - 這樣兩個檔案會樣本對齊,且連接點會精確位於累計的片段長度處。