修復 SRT 字幕編碼且不遺失原始文字
診斷 SRT 亂碼問題、區分解碼錯誤與字元遺失,並透過安全的 Python 工具與可重現的範例,另存一份獨立的 UTF-8 副本。

如果您的 SRT 字幕顯示 café 而非 café,請勿立即將檔案直接儲存為 UTF-8。首先請保留一份未經修改的原始位元組副本,找出能產生正確文字的解碼方式,並將驗證無誤的文字寫入另一個獨立檔案。直接用新編碼儲存錯誤的字元,只會讓錯誤字元被永久保留。
關鍵的區別在於 「有效編碼」與「正確文字」的不同。在下方包含兩段字幕的原始實驗中,其中一個損毀的檔案通過了嚴格的 UTF-8 檢查,且完全不含替換字元,但其法文文字依然是錯誤的。另一個檔案同樣通過了相同的檢查,但原本的六個字元卻早已被替換成 �.
本指南提供了一個 本機 Python 檢測與轉換工具、可下載的來源檔案以及可重現的測試流程。它著重於檔案層級的診斷與明確轉換——而非自動編碼偵測、影片同步,或保證能救回已遺失的字元。
首先找出問題的根源
副檔名 .srt 僅代表一種字幕格式,並不保證特定的字元編碼。SRT 包含字幕序號、時間戳記、文字與分隔符號;美國國會圖書館的 SRT 格式記錄 將其描述為純文字,且沒有統一的字元編碼標準。
請將肉眼看到的異常現象當作深入調查的線索,而非直接作為最終診斷:
| 您所看到的現象 | 應檢查的項目 | 合適的後續步驟 |
|---|---|---|
| 重音符號或特殊符號在某個應用程式中顯示錯誤 | 以已知的來源編碼重新開啟未修改的原始檔案,是否能顯示正確文字? | 如果是,在匯出新副本之前,請明確指定使用該解碼方式。 |
café 依然存在於以 UTF-8 正確讀取的檔案中 | 先前是否曾將 UTF-8 誤讀為其他編碼後儲存? | 請還原回原始位元組,或釐清確切的轉換歷程。單純再次另存為 UTF-8 並不等於修復。 |
� 出現在原本應為字母或符號的位置 | 檔案中是否確實存入了 U+FFFD,還是當前應用程式在顯示無法解碼的位元組時自行插入的? | 檢查未經修改的原始檔案。已經存入的替代字元無法反推原本的文字內容。 |
| 播放器中出現方塊(豆腐字),但可信的文字檢視器中卻能顯示正確文字 | 底層碼位(code point)是否正確?播放器載入的是否為同一個檔案? | 在修改檔案之前,請先檢查播放器的解碼方式、字型涵蓋範圍或算圖設定。 |
字元與其顯示出的字形是兩回事。Python 的 Unicode 說明 區分了碼位與字型算圖產生的字形(glyph)。在文字編輯器中顯示正確,意味著問題很可能出在播放器端;但這並不保證安裝某個特定字型就能解決問題。本文並未針對特定字型或播放器進行測試。
備份原始位元組,接著檢查解碼狀況
下載 subtitle_encoding.py 並將其放在您要檢查的 SRT 檔案旁。執行這些指令需要已安裝 Python 3,無需額外安裝套件。本文的本機檢查環境為 macOS 上的 Python 3.14.3。若您的 Windows 環境使用 Python 啟動器,請將 py -3 替換為 python3;本文未在該環境下進行測試。
在該資料夾中開啟終端機。在這些範例中,請將 input.srt 替換為您的實際檔名。若路徑包含空格,請保留引號。
建立未經修改的副本
python3 subtitle_encoding.py backup "input.srt" "original-bytes.srt"
目的路徑不得已存在。該工具會在不進行解碼的情況下複製位元組、回讀副本並回報其 SHA-256 雜湊值。它也會拒絕將來源路徑作為目的地。即使製作出可用的字幕後,也請保留這份備份。
在不儲存任何內容的情況下檢查 UTF-8
python3 subtitle_encoding.py inspect "original-bytes.srt"
報告內容包含 strict_utf8, utf8_bom、所選的解碼方式,以及(若該解碼成功)文字預覽與 replacement_characters 計數。其預設文字檢視採用 utf-8-sig,能夠妥善處理開頭的 UTF-8 簽章(BOM)。
strict_utf8: false表示這些位元組無法通過 UTF-8 檢查。但這 並不能 告訴你哪一種其他編碼才是正確的。strict_utf8: true表示它們可以被解碼為 UTF-8。但這 並不 代表文字內容是正確的。- 非零的替換計數表示所選的解碼方式產生了 U+FFFD 字元。在建立新檔案之前,請先仔細檢查這些字元。
檢查工具會輸出 ASCII 安全的 JSON,因此終端機字型無法隱藏字元的真實身分: \u00e9 代表 é,而 \ufffd 代表替換字元。若要詳細檢查有疑慮的行,請指定其物理行號,而非字幕段落編號:
python3 subtitle_encoding.py inspect "original-bytes.srt" --encoding cp1252 --line 3
這是針對 Windows-1252(即 Python 的 cp1252 編解碼器)進行的唯讀測試。只有在匯出設定或來源紀錄支援此選擇時才使用它,而不是因為此範例剛好是法文。逐行報告會列出每個字元的名稱並顯示其碼位; U+20AC 為歐元符號。
依據確切證據選擇編碼,而非只看第一次預覽成功
向檔案建立者詢問實際的匯出編碼、檢查匯出應用程式的設定,或與可靠的原始逐字稿進行比對。請檢查整份檔案中帶有重音符號的名稱、標點符號、貨幣符號以及具有代表性的行。如果你看不懂該語言,即使解碼成功也無法替你完成這項審核。
「ANSI」一詞不夠精確,無法據此選取來源編解碼器。Windows-1252 與 Latin-1 也無法互換。在提供的實驗中,單一位元組 80 在 € 下會解碼為 cp1252,但在該編碼下卻會解碼為控制字元 U+0080 解碼為 latin-1。這兩種操作都能成功執行。唯有已知的來源背景才能告訴我們哪個才是原本的意思。
其他語言可能會使用不同的傳統編碼,但單憑語言名稱並不足以確定編碼。如果檔案來源背景未知,且候選的解讀結果彼此衝突,請在轉換前先停下來,設法取得更可靠的來源證據。
確認來源文字可正常讀取後再進行轉換
對於已確認為 Windows-1252 的來源檔案,請建立一個獨立的 UTF-8 檔案:
python3 subtitle_encoding.py convert "original-bytes.srt" "repaired-utf8.srt" --from-encoding cp1252
python3 subtitle_encoding.py inspect "repaired-utf8.srt"
轉換工具會嚴格解碼完整來源內容、將該文字編碼為 UTF-8、檢查解碼輸出是否能還原相同的文字,並將寫入的位元組重新讀回驗證。它不會修改字幕序號、時間戳記、對話內容、標籤或換行符號。即使解碼後的文字完全相同,新的位元組序列也可能有所不同。
此工具會在以下情況刻意拒絕執行:
- 覆寫已存在的目標檔案;
- 在嚴格解碼出錯時繼續執行;或
- 寫入包含 U+FFFD 的文字,即使該字元是刻意存在的。
最後一項限制採取保守原則:本工具無法判斷替換字元原本代表什麼意思。為了讓指令通過而強制移除它,只會抹滅證據,而非修復文字。Python 的 錯誤處理常式說明文件 解釋了為什麼 replace 與 ignore 和嚴格解碼有所不同:它們會替換或捨棄有問題的資料。這兩種方式都不應該出現在這個轉換步驟中。
如果發生 I/O 錯誤,請勿使用失敗的輸出結果。磁碟上可能會殘留未完成的 新 檔案;請保留原始檔案,並在稍後重試時使用全新的目標檔名。完整的工具行為與使用說明請參閱 可下載的 README.
帶有 BOM 的 UTF-8 該如何處理?
UTF-8 BOM 是檔案開頭的位元組序列 EF BB BF。Python 的 utf-8-sig 編解碼器 會在解碼時吃掉該簽章,並在編碼時將其加入。
若已確認來源是帶有 BOM 的 UTF-8,請使用 --from-encoding utf-8-sig。本工具通常會輸出不含 BOM 的 UTF-8;只有在目標應用程式明確要求時,才需要加上 --with-bom 。更改 BOM 並無法將 café 變成 café。本實驗並不保證能相容於任何特定的播放器、編輯器或上傳平台。
五個小檔案說明為何僅符合 UTF-8 格式仍然不夠
本實驗使用兩段完全自行編寫的法文字幕段落。這些內容並非取自任何電影、客戶或翻譯產品:
1
00:00:01,000 --> 00:00:04,000
Émile dit : « Le prix est de 12,50 €. »
2
00:00:05,000 --> 00:00:08,000
Le café est fermé.
在 2026 年 9 月 13 日,我們透過 Python 3.14.3 標準函式庫腳本產生了五種位元組表示形式,並將每一種與已知的原始文字進行比對。這些是確定性的位元組與字串檢查,並非翻譯品質評測。請將連結的檔案儲存至硬碟,而不要直接從瀏覽器預覽中複製:不同的位元組內容才是本實驗的關鍵所在。
| 可下載檔案 | 位元組大小 | 是否為嚴格 UTF-8? | 以 UTF-8 解碼時文字是否正確? | 在其 UTF-8 文字中儲存了 U+FFFD |
|---|---|---|---|---|
| 原始 UTF-8 | 132 | 是 | 是 | 0 |
| 原始 Windows-1252 | 125 | 否 | 無法以 UTF-8 解碼;但在下列編碼下解碼正確: cp1252 | 不適用 |
| 解碼器錯誤,重新儲存為 UTF-8 | 147 | 是 | 否 | 0 |
| 包含取代字元,重新儲存為 UTF-8 | 137 | 是 | 否 | 6 |
| 正確轉換的 UTF-8 | 132 | 是 | 是 | 0 |
每個副本都保留了完全相同的兩個時間戳記字串。正確轉換的檔案與 UTF-8 原始檔在逐位元組比對下完全一致。而那兩個受損的 UTF-8 檔案則證明了一個更具體但也很有用的事實:編碼有效或是時間戳記未變,都不能證明字幕文字本身是正確的。時間戳記完全吻合,同樣無法說明這個虛構的軌道是否適用於真實的影片。
若要自行重現這五個檔案,請下載 reproduce.py 並指定一個尚不存在的目錄名稱來執行:
python3 reproduce.py encoding-experiment
該指令碼會寫入測試固件(fixtures)以及一份 results.json ,其中包含完整文字、轉換過程、雜湊值、執行階段版本和各項斷言。您可以將其與 記錄的實驗結果進行比對;兩者的產生時間戳記會有所不同。這些指令碼與原始教學檔案均包含一份 MIT 授權條款.
本機轉換器的檢查機制還測試了拒絕覆寫已存在的輸出檔、拒絕來源路徑、錯誤編解碼器引發失敗、拒絕 U+FFFD、保留 CRLF 以及明確輸出 BOM 等情況。這些檢查旨在驗證此工具的檔案處理行為,並非測試播放器的轉譯效果、實際媒體的時間同步,或是 BookTranslator 的翻譯功能。
如果亂碼字元已經被儲存
已儲存的亂碼:追溯確切的轉換歷程
我們的亂碼副本遵循了一條已知的轉換鏈:原始的 UTF-8 位元組被誤以 Windows-1252 解碼,隨後這些錯誤字元又被儲存為 UTF-8。導致的結果包括 café 以及損壞的歐元符號序列 €.
由於這個特定的轉換過程並未遺失任何位元組,重現指令碼可以將其反向還原:將錯誤文字重新以 cp1252編碼,接著將產生的位元組解碼為 UTF-8。它會拿復原後的文字與原始文字進行驗證比對。
這項證明僅適用於 這條已知的轉換鏈,絕非建議對所有 SRT 檔案一律套用全域的「修復亂碼」操作。檔案可能包含原本就合法的字元、混合了多種編碼歷程,或者文字早已被先前的修復動作改動過。最佳做法始終是優先採用未經改動的原始檔案並走正常的轉換流程。如果手邊只剩下已儲存的亂碼檔案,請先保留該檔案備份,並查明它是如何產生的,然後再在另一個副本上嘗試反向轉換。
已儲存的取代字元:遺失的字母根本沒有編碼在其中
這個造成資料遺失的測試檔案,是將 Windows-1252 位元組以啟用取代字元的方式解碼為 UTF-8,然後儲存結果所產生的。原本不同的字元全變成了相同的 � 標記。已儲存的標記並未保留足夠的資訊,無法辨識原本佔據該位置的是哪一個字元。
請勿將此情況與僅僅是在應用程式中 看到 � 混為一談。如果原始位元組保持未被修改的狀態,正確解碼仍有可能復原文字——正如我們的 Windows-1252 來源檔案一樣。在宣判資料已遺失之前,請務必先檢查該原始檔案。
若唯一留存的副本已確實將遺失文字替換為 U+FFFD 或字面問號,請重新匯出或重新取得乾淨的來源檔案,或對照可靠資料進行人工轉錄校對。一般的問號也可能是正常的標點符號,單憑它的存在並不能證明什麼。AI 模型給出的合理替換字詞只是猜測,並不是復原出來的檔案原始資料。
確認最終來源清晰可讀——而不只是指令執行成功
在接受修復後的檔案之前,請將具代表性的單字與符號與可信來源進行對比,確認字幕序號與時間戳行未遭變更,並在預計使用的應用程式中開啟該 新檔案 。此指令碼的作用是保留解碼後的文字,它並非 SRT 語法驗證工具,也不是播放驗收測試。
如果您現在已經能正常閱讀字幕,且不需要其他語言,那麼工作就已經完成。如果您需要實際進行影片審閱或進一步編輯,這篇 字幕軟體評比 說明了不同工具的角色與用途。
唯有在來源文字正確無誤——且您確實需要其他語言時——再進入 BookTranslator 的字幕翻譯器 與 AI 字幕翻譯工作流程。請將編碼修復、翻譯以及檢視最終軌道視為彼此獨立的工作,任一環節成功都不代表其他環節沒有問題。





