書譯
書譯

Fix SRT Subtitle Encoding Without Losing the Original Text

Diagnose garbled SRT text, distinguish decoding errors from lost characters, and make a separate UTF-8 copy with a safe Python tool and reproducible samples.

BookTranslator

BookTranslator Team

10 min read

If your SRT subtitles show café instead of café, do not immediately save the file as UTF-8. First keep an untouched copy of its bytes, identify a decoding that produces the correct words, and write the verified text to a separate file. Saving the wrong characters in a new encoding preserves the wrong characters.

The important distinction is valid encoding versus correct text. In the original two-cue experiment below, one broken file passes a strict UTF-8 check and contains no replacement characters. Its French text is still wrong. Another passes the same check after six original characters have been replaced with .

This guide provides a local Python inspection and conversion tool, downloadable source files, and a reproducible test. It handles file-level diagnosis and explicit conversion—not automatic encoding detection, video synchronization, or a promise to recover missing letters.

First identify what is actually broken

An .srt extension identifies a subtitle format, not a guaranteed encoding. SRT holds cue numbers, timestamps, text, and separators; the Library of Congress's SRT format record describes it as plain text without one character-encoding standard.

Use the visible symptom as a reason to investigate, not a diagnosis by itself:

What you seeWhat to checkAppropriate next step
Accents or symbols look wrong in one appDoes reopening the untouched file with the known source encoding produce the correct text?If yes, use that explicit decoding before exporting a new copy.
café remains in a file read correctly as UTF-8Was UTF-8 previously misread as another encoding and saved?Return to the original bytes, or investigate the exact conversion history. Another UTF-8 save is not repair.
appears where a letter or symbol belongsIs U+FFFD actually saved in the file, or is the current app inserting it while displaying undecodable bytes?Inspect the untouched source. A saved replacement does not identify the original character.
Boxes appear in the player, but a trusted text view has the intended charactersAre the underlying code points correct? Is the player loading the same file?Investigate the player's decoding, font coverage, or rendering settings before changing the file.

Characters and their displayed shapes are different things. Python's Unicode explanation distinguishes code points from font-rendered glyphs. Correct text in an editor makes a player-side problem plausible; it does not prove that installing a particular font will fix it. No font or player was tested for this article.

Back up the bytes, then inspect a decoding

Download subtitle_encoding.py and put it beside the SRT you want to investigate. These commands require an existing Python 3 installation and no extra packages. The local checks here used Python 3.14.3 on macOS. If your Windows installation uses the Python launcher, substitute py -3 for python3; that environment was not tested here.

Open a terminal in that folder. In these examples, replace input.srt with your actual filename. Keep quotation marks around paths containing spaces.

Make an untouched copy

python3 subtitle_encoding.py backup "input.srt" "original-bytes.srt"

The destination must not already exist. The tool copies bytes without decoding them, reads the copy back, and reports its SHA-256 hash. It also refuses the source path as a destination. Keep this backup even after making a usable subtitle.

Check UTF-8 without saving anything

python3 subtitle_encoding.py inspect "original-bytes.srt"

The report includes strict_utf8, utf8_bom, the selected decoding, and—if that decoding succeeds—a text preview and replacement_characters count. Its default text view uses utf-8-sig, which handles a leading UTF-8 signature.

  • strict_utf8: false means the bytes fail the UTF-8 check. It does not tell you which other encoding is correct.
  • strict_utf8: true means they can be decoded as UTF-8. It does not mean the words are right.
  • A nonzero replacement count means the selected decoding produced U+FFFD characters. Investigate them before creating a new file.

Inspection prints ASCII-safe JSON so a terminal font cannot hide the character identity: \u00e9 means é, and \ufffd means the replacement character. To examine a suspicious line in detail, request its physical line number—not its cue number:

python3 subtitle_encoding.py inspect "original-bytes.srt" --encoding cp1252 --line 3

This is a read-only test of Windows-1252, Python's cp1252 codec. Use it when the export settings or source history support that choice, not because this example happened to use French. The line report names each character and shows its code point; U+20AC is the euro sign.

Choose the encoding from evidence, not the first successful preview

Ask the file's creator for the actual export encoding, check the exporting application's settings, or compare against a trustworthy original transcript. Review accented names, punctuation, currency signs, and representative lines throughout the file. If you cannot read the language, a successful decoder cannot perform that review for you.

“ANSI” is not precise enough to select the source codec. Nor are Windows-1252 and Latin-1 interchangeable. In the supplied experiment, the single byte 80 decodes to under cp1252, but to the control character U+0080 under latin-1. Both operations succeed. Only the known source tells us which is intended.

Other languages may use different legacy encodings, but a language name alone does not determine one. If the history is unknown and candidate readings disagree, stop before conversion and obtain better source evidence.

Convert only after the source text is readable

For a source confirmed as Windows-1252, create a separate UTF-8 file:

python3 subtitle_encoding.py convert "original-bytes.srt" "repaired-utf8.srt" --from-encoding cp1252
python3 subtitle_encoding.py inspect "repaired-utf8.srt"

The converter decodes the complete source strictly, encodes that text as UTF-8, checks that decoding the output returns the same text, and reads the written bytes back. It does not edit cue numbers, timestamps, dialogue, tags, or line endings. The new byte sequence can differ even though the decoded text stays identical.

It deliberately refuses to:

  • overwrite an existing destination;
  • proceed past a strict decoding error; or
  • write text containing U+FFFD, even if that character was intentional.

The last restriction is conservative: this tool cannot decide what a replacement character was meant to represent. Removing it to make the command pass would discard evidence, not restore text. Python's error-handler documentation explains why replace and ignore are different from strict decoding: they substitute or discard problematic data. Neither belongs in this conversion step.

If an I/O error occurs, do not use the failed output. A partial new file can remain; keep the original and use a fresh destination name for a later attempt. Full tool behavior and usage are in the downloadable README.

What about UTF-8 with BOM?

A UTF-8 BOM is the leading byte sequence EF BB BF. Python's utf-8-sig codec consumes that signature when decoding and adds it when encoding.

For a confirmed UTF-8 source with a BOM, use --from-encoding utf-8-sig. The tool normally writes UTF-8 without a BOM; add --with-bom only when the receiving application's requirements call for it. Changing a BOM cannot turn café into café. This experiment does not establish compatibility with any particular player, editor, or upload platform.

Five small files show why UTF-8 validity is not enough

The experiment uses two entirely self-authored French subtitle cues. They are not taken from a film, a customer, or a translation product:

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é.

On September 13, 2026, a Python 3.14.3 standard-library script generated five byte representations and compared each against that known source. These are deterministic byte and string checks, not a translation-quality benchmark. Save the linked files to disk rather than copying their browser previews: the different bytes are the point of the experiment.

Downloadable copyBytesStrict UTF-8?Correct text when decoded as UTF-8?Saved U+FFFD in its UTF-8 text
Original UTF-8132YesYes0
Original Windows-1252125NoNot decodable as UTF-8; correct under cp1252Not applicable
Wrong decoder, resaved as UTF-8147YesNo0
Replacement characters, resaved as UTF-8137YesNo6
Correctly converted UTF-8132YesYes0

Every copy retains the same two timestamp strings. The correctly converted file is byte-for-byte identical to the UTF-8 original. The two damaged UTF-8 files prove something narrower but useful: neither valid encoding nor unchanged timestamps establishes correct subtitle text. Matching timestamps also says nothing about whether this invented track fits a real video.

To reproduce the five files yourself, download reproduce.py and run it with a directory name that does not exist:

python3 reproduce.py encoding-experiment

The script writes the fixtures and a results.json containing the full text, transformations, hashes, runtime version, and assertions. Compare it with the recorded experiment results; the generation timestamp will differ. The scripts and original teaching files include an MIT license.

The local converter checks also exercised existing-output refusal, source-path refusal, incorrect-codec failure, U+FFFD refusal, CRLF preservation, and explicit BOM output. Those checks verify this tool's file behavior. They do not test player rendering, real-media timing, or BookTranslator translation.

If the broken characters have already been saved

Saved mojibake: investigate the exact history

Our mojibake copy followed a known chain: original UTF-8 bytes were decoded as Windows-1252, then those wrong characters were saved as UTF-8. The result includes café and the broken euro sequence €.

Because this particular transformation lost no bytes, the reproduction script can reverse it: encode the wrong text back through cp1252, then decode the resulting bytes as UTF-8. It verifies the recovered text against the original.

That is evidence for this known chain, not an instruction to apply a global “repair mojibake” operation to every SRT. A file can contain legitimate characters, mixed histories, or text already changed by a previous repair. Prefer the untouched source and the normal conversion path. If only the saved mojibake remains, preserve it and establish how it was produced before attempting an inverse transformation on another copy.

Saved replacement characters: the missing letter is not encoded there

The loss fixture was made by decoding Windows-1252 bytes as UTF-8 with replacement enabled, then saving the result. Different original characters became the same marker. The saved marker does not carry enough information to identify which original character occupied that position.

Do not confuse this with merely seeing in an app. If the original bytes remain untouched, a correct decoding may still recover the text—as it does for our Windows-1252 source. Inspect that source before declaring loss.

If the only remaining copy has actually substituted missing text with U+FFFD or literal question marks, re-export or reacquire a clean source, or verify a manual transcription against reliable material. An ordinary question mark can also be legitimate punctuation; its presence alone proves nothing. A model's plausible replacement is a guess, not recovered file data.

Finish with a readable source—not just a successful command

Before accepting the repaired file, compare representative words and symbols with the trusted source, confirm that cue and timestamp lines have not changed, and open the new file in the application that will use it. The script preserves decoded text; it is not an SRT syntax validator or a playback acceptance test.

If you can now read the subtitles and need no other language, the task is finished. If you need hands-on video review or further editing, the subtitle software comparison explains the different tool roles.

Only once the source text is trustworthy—and you actually need another language—move to BookTranslator's Subtitle Translator and the AI subtitle translation workflow. Treat encoding repair, translation, and viewing the final track as separate jobs. None is proof that the others have succeeded.

相關文章

Fix SRT Subtitle Encoding Without Losing the Original Text