<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Picute</title>
    <description>The latest articles on DEV Community by Picute (@picute).</description>
    <link>https://dev.to/picute</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4003852%2F26c5caed-b5c5-45f6-b3a7-01b73f5e880b.png</url>
      <title>DEV Community: Picute</title>
      <link>https://dev.to/picute</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/picute"/>
    <language>en</language>
    <item>
      <title>What Cipher Is This? A Field Guide to Identifying Unknown Ciphers</title>
      <dc:creator>Picute</dc:creator>
      <pubDate>Thu, 02 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/picute/what-cipher-is-this-a-field-guide-to-identifying-unknown-ciphers-2c3n</link>
      <guid>https://dev.to/picute/what-cipher-is-this-a-field-guide-to-identifying-unknown-ciphers-2c3n</guid>
      <description>&lt;p&gt;You've got a blob of mysterious text. Maybe it fell out of a CTF challenge, an escape room, a geocache, an ARG, or the margin of a secondhand book. It's &lt;em&gt;obviously&lt;/em&gt; enciphered — but with &lt;strong&gt;what&lt;/strong&gt;? Before you can decode anything, you have to identify the cipher, and that's where most people stall.&lt;/p&gt;

&lt;p&gt;The good news: identifying a classical cipher is a methodical process, not a guessing game. Cryptanalysts have a checklist, and you can run most of it by eye in about two minutes. Here it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Look at the alphabet (the character set)
&lt;/h2&gt;

&lt;p&gt;The single most informative clue is &lt;em&gt;which symbols appear&lt;/em&gt;. Sort your ciphertext into one of these buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only letters A–Z&lt;/strong&gt; → a substitution or transposition cipher (Caesar, Vigenère, Playfair, columnar transposition…). This is the most common case and the rest of this guide focuses on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only digits&lt;/strong&gt; → a numeric scheme: A1Z26 (1–26 → letters), a Polybius square (pairs 11–55), a book/Nihilist cipher, or phone-keypad code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letters &lt;em&gt;and&lt;/em&gt; digits, length a multiple of certain bases&lt;/strong&gt; → could be Base64 (&lt;code&gt;A–Z a–z 0–9 + /&lt;/code&gt;, often ending in &lt;code&gt;=&lt;/code&gt;), hexadecimal (&lt;code&gt;0–9 a–f&lt;/code&gt;), or Base32.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Just two symbols&lt;/strong&gt; (A/B, 0/1, •/—) → Bacon's cipher (groups of 5), binary (groups of 8), or Morse (dots and dashes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geometric shapes, dots in boxes, or weird glyphs&lt;/strong&gt; → Pigpen / Masonic, Templar, or another symbol substitution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one observation usually eliminates 80% of the possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Measure the Index of Coincidence
&lt;/h2&gt;

&lt;p&gt;For letters-only ciphertext, the &lt;strong&gt;Index of Coincidence (IoC)&lt;/strong&gt; is the workhorse statistic. It measures the probability that two randomly chosen letters are the same. You don't need to do the arithmetic by hand — but here's what the number tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IoC ≈ 0.067&lt;/strong&gt; → the letter-frequency &lt;em&gt;shape&lt;/em&gt; of natural English survives. That means a &lt;strong&gt;monoalphabetic&lt;/strong&gt; cipher (Caesar, Atbash, simple substitution, keyword) &lt;strong&gt;or a transposition&lt;/strong&gt; (which only rearranges letters, so frequencies are untouched).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoC ≈ 0.038–0.045&lt;/strong&gt; → the frequencies have been &lt;em&gt;flattened&lt;/em&gt;. That's the signature of a &lt;strong&gt;polyalphabetic&lt;/strong&gt; cipher — Vigenère, Beaufort, Autokey, Gronsfeld — where multiple shifting alphabets smear out the peaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So one number splits the letters-only world cleanly in two: peaky frequencies (mono/transposition) vs. flat frequencies (polyalphabetic).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Mono or transposition? Check the histogram
&lt;/h2&gt;

&lt;p&gt;If the IoC said "monoalphabetic-or-transposition," look at the actual letter counts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a few letters dominate (one letter ~12–13%, a long tail of rare letters) &lt;strong&gt;and the common letters aren't E/T/A&lt;/strong&gt;, you have a &lt;strong&gt;monoalphabetic substitution&lt;/strong&gt; — the frequency &lt;em&gt;fingerprint&lt;/em&gt; of English is intact but relabeled. Run a Caesar brute-force (only 25 options) first; if a single shift pops out readable text, you're done. If not, it's a keyword or random substitution — solve it as a cryptogram.&lt;/li&gt;
&lt;li&gt;If the letter frequencies look &lt;strong&gt;exactly like normal English&lt;/strong&gt; (E, T, A, O on top, in roughly the right proportions) but the text is gibberish, the letters haven't been &lt;em&gt;replaced&lt;/em&gt; at all — only &lt;strong&gt;moved&lt;/strong&gt;. That's a &lt;strong&gt;transposition&lt;/strong&gt; (columnar, rail-fence, route). Solve it by testing column counts / rail counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4 — Telltale structural fingerprints
&lt;/h2&gt;

&lt;p&gt;Some ciphers leave signatures you can spot directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No repeated double letters, even length, only 25 distinct letters (no J)&lt;/strong&gt; → &lt;strong&gt;Playfair&lt;/strong&gt; (it never encrypts a doubled letter to a double).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything in groups of five A/B letters&lt;/strong&gt; → &lt;strong&gt;Bacon&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinates like 11, 23, 45 (digits 1–5)&lt;/strong&gt; → a &lt;strong&gt;Polybius square&lt;/strong&gt; / Bifid / Nihilist family.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A keyword-length repeat distance between identical trigrams&lt;/strong&gt; → &lt;strong&gt;Vigenère&lt;/strong&gt; (this is the Kasiski test, and it even reveals the key length).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dots, dashes, and slashes&lt;/strong&gt; → &lt;strong&gt;Morse&lt;/strong&gt; — but if it's &lt;em&gt;fractionated&lt;/em&gt; Morse (Morbit, Pollux, Fractionated Morse) the Morse is then re-encoded into digits or letters, so check for that second layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5 — Confirm by decoding
&lt;/h2&gt;

&lt;p&gt;Identification is a hypothesis; decoding is the proof. Once you've narrowed it to one or two candidates, run the actual decoder. If it produces readable plaintext, you were right. If not, step back to your IoC reading and try the next family.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-minute shortcut
&lt;/h2&gt;

&lt;p&gt;Every step above — character-set classification, IoC, frequency histogram, Caesar standout-shift test, and the structural checks — is mechanical, which means it can be automated. That's exactly what a &lt;strong&gt;cipher identifier&lt;/strong&gt; does: you paste the ciphertext, it computes the statistics, and it hands you a ranked list of likely ciphers with a one-click link to each decoder.&lt;/p&gt;

&lt;p&gt;If you'd rather skip the arithmetic, run your mystery text through this free, in-browser one:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://textmachine.org/en/text-tools/cipher-identifier" rel="noopener noreferrer"&gt;Cipher Identifier — What Cipher Is This?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It does the character-set analysis, Index of Coincidence (with the mono/poly verdict), and Chi-squared shift tests right in your browser — nothing is uploaded — and links straight to a working decoder for each candidate (Caesar, Vigenère, Playfair, Bazeries, Pigpen, and ~30 others).&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;Suppose you're handed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WKH TXLFN EURZQ IRA MXPSV RYHU WKH ODCB GRJ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Character set:&lt;/strong&gt; letters only → substitution or transposition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoC:&lt;/strong&gt; ≈ 0.066 → monoalphabetic or transposition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Histogram:&lt;/strong&gt; the counts have the &lt;em&gt;peaky&lt;/em&gt; shape of English (a few common letters — here &lt;code&gt;R&lt;/code&gt;, &lt;code&gt;H&lt;/code&gt; — and a long rare tail), but the peaks sit on the &lt;em&gt;wrong&lt;/em&gt; letters rather than on E/T/A. The alphabet has been &lt;strong&gt;relabeled&lt;/strong&gt;, so it's a substitution, not a transposition (a transposition would keep the peaks on the real English letters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brute-force Caesar:&lt;/strong&gt; shifting back by 3 gives &lt;code&gt;THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG&lt;/code&gt;. ✅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Identified and decoded: a &lt;strong&gt;Caesar cipher, shift 3&lt;/strong&gt; — in under a minute, using nothing but the checklist.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The takeaway:&lt;/strong&gt; identifying a cipher is a funnel — character set → IoC → frequency shape → structural fingerprints → decode-to-confirm. Learn the funnel and "what cipher is this?" stops being a wall and becomes a two-minute triage. And when you want the triage done for you, &lt;a href="https://textmachine.org/en/text-tools/cipher-identifier" rel="noopener noreferrer"&gt;the identifier&lt;/a&gt; runs the whole funnel in your browser.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy decoding.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cryptography</category>
      <category>security</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Those "fancy fonts" in Instagram and TikTok bios aren't fonts — they're Unicode</title>
      <dc:creator>Picute</dc:creator>
      <pubDate>Mon, 29 Jun 2026 10:45:01 +0000</pubDate>
      <link>https://dev.to/picute/those-fancy-fonts-in-instagram-and-tiktok-bios-arent-fonts-theyre-unicode-2m6d</link>
      <guid>https://dev.to/picute/those-fancy-fonts-in-instagram-and-tiktok-bios-arent-fonts-theyre-unicode-2m6d</guid>
      <description>&lt;p&gt;You paste 𝓱𝓮𝓵𝓵𝓸 into an Instagram bio and it just works — no app, no image, no CSS. Copy it back out and it survives. That's the tell that it was never a font at all. It's Unicode.&lt;/p&gt;

&lt;p&gt;Here's what's actually happening, why it travels everywhere, and the three gotchas worth knowing if you ever build or debug one of these.&lt;/p&gt;

&lt;h2&gt;
  
  
  A "font" changes how a glyph is &lt;em&gt;drawn&lt;/em&gt;. This changes the &lt;em&gt;character&lt;/em&gt;.
&lt;/h2&gt;

&lt;p&gt;A real font — bold, italic, a script typeface — is a rendering instruction layered on top of the same underlying character. The letter &lt;code&gt;h&lt;/code&gt; stays &lt;code&gt;U+0068 LATIN SMALL LETTER H&lt;/code&gt;; the font just paints it differently. Strip the styling and you still have &lt;code&gt;h&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Fancy text works the other way around. &lt;code&gt;𝓱&lt;/code&gt; is not an &lt;code&gt;h&lt;/code&gt; wearing a costume — it's a &lt;strong&gt;different code point&lt;/strong&gt;: &lt;code&gt;U+1D4F1 MATHEMATICAL SCRIPT SMALL H&lt;/code&gt;. Unicode shipped whole parallel alphabets, mostly for mathematicians who need a "script H" and a "bold H" to mean different things in one equation. Generators just borrow those alphabets for aesthetics.&lt;/p&gt;

&lt;p&gt;The blocks you see most often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mathematical Alphanumeric Symbols&lt;/strong&gt; (&lt;code&gt;U+1D400&lt;/code&gt;–&lt;code&gt;U+1D7FF&lt;/code&gt;): bold, italic, script, fraktur, double-struck, monospace, sans-serif.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enclosed Alphanumerics&lt;/strong&gt; (&lt;code&gt;U+2460&lt;/code&gt;–&lt;code&gt;U+24FF&lt;/code&gt;): the ⓑⓤⓑⓑⓛⓔ circled letters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Halfwidth and Fullwidth Forms&lt;/strong&gt; (&lt;code&gt;U+FF00&lt;/code&gt;–&lt;code&gt;U+FFEF&lt;/code&gt;): the ｗｉｄｅ vaporwave spacing.&lt;/li&gt;
&lt;li&gt;Plus scattered small-caps, superscripts, and regional-indicator pairs (flag emoji).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why it survives a copy-paste into &lt;em&gt;any&lt;/em&gt; bio
&lt;/h2&gt;

&lt;p&gt;Because the styling lives in the character, not in CSS, it travels as plain text. A bio field just stores a Unicode string; it has no idea that some of those code points happen to look like a curly H. That "the style rides along with the text" property is exactly why these spread — the output is portable by construction, so it carries into places that never gave you a formatting toolbar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating it is just a lookup table
&lt;/h2&gt;

&lt;p&gt;There's no clever algorithm here. You map each ASCII letter to the same offset inside the target block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ASCII A–Z → Mathematical Script uppercase, which starts at U+1D49C&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// A = 0&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// leave non-letters alone&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCodePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x1D49C&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 𝒜, ℬ, 𝒞, …&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HELLO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// 𝓗𝓔𝓛𝓛𝓞&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…then you keep one table per style. The reason a ready-made &lt;a href="https://textmachine.org/en/text-tools/fancy-text-generator" rel="noopener noreferrer"&gt;fancy text generator&lt;/a&gt; can show dozens of styles at once is just dozens of these maps applied in parallel — the same trick powers variants like &lt;a href="https://textmachine.org/en/text-tools/bubble-text" rel="noopener noreferrer"&gt;bubble text&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three gotchas nobody mentions
&lt;/h2&gt;

&lt;p&gt;The fun stops the moment this text meets a &lt;em&gt;machine&lt;/em&gt; instead of a human eyeball:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accessibility takes a hit.&lt;/strong&gt; A screen reader doesn't see a stylish H — it sees &lt;code&gt;MATHEMATICAL SCRIPT SMALL H&lt;/code&gt;, and will either read that aloud literally or skip the character. A bio written entirely in fancy text can be unreadable to assistive tech. Use it for a flourish, not your whole name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It's invisible to search and matching.&lt;/strong&gt; &lt;code&gt;𝓳𝓸𝓱𝓷&lt;/code&gt; and &lt;code&gt;john&lt;/code&gt; are different byte sequences, so naïve search, @-mentions, and username lookups won't connect them. If you store user-supplied text, this will bite you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Normalization folds it back — and that's the fix.&lt;/strong&gt; Run the string through Unicode &lt;strong&gt;NFKC&lt;/strong&gt; normalization and most of these styles collapse back to plain ASCII:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;𝓳𝓸𝓱𝓷&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NFKC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "john"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line is how you make fancy text searchable, validate a username, or strip decoration server-side.&lt;/p&gt;

&lt;p&gt;So the whole thing is: Unicode has spare alphabets, the styling is baked into the code point rather than the CSS, and &lt;code&gt;NFKC&lt;/code&gt; is the undo button. Handy to know whether you're decorating a bio or sanitizing one.&lt;/p&gt;

</description>
      <category>unicode</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>a11y</category>
    </item>
    <item>
      <title>How to Crack a Cipher Without the Key</title>
      <dc:creator>Picute</dc:creator>
      <pubDate>Mon, 29 Jun 2026 04:57:58 +0000</pubDate>
      <link>https://dev.to/picute/how-to-crack-a-cipher-without-the-key-3gf1</link>
      <guid>https://dev.to/picute/how-to-crack-a-cipher-without-the-key-3gf1</guid>
      <description>&lt;p&gt;You've figured out &lt;em&gt;which&lt;/em&gt; cipher you're staring at — say a monoalphabetic cryptogram, or a Vigenère — but you don't have the key. No keyword, no shift, no crib. Manually, this is where people grind for hours. Automatically, a good solver recovers it in about a second. Here's how that actually works, so the tool isn't a black box.&lt;/p&gt;

&lt;p&gt;The whole game rests on one idea: &lt;strong&gt;you don't search for the key, you search for English.&lt;/strong&gt; A wrong key produces gibberish; the right key produces text that looks like a real language. So if you can &lt;em&gt;score&lt;/em&gt; how English-like a candidate decryption is, breaking the cipher becomes an optimization problem — find the key that maximizes the score. Everything below is a variation on that theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoring function is the secret, and single letters aren't enough
&lt;/h2&gt;

&lt;p&gt;The naive score is letter frequency: real English is ~12.7% E, ~9% T, and so on, so reward decryptions whose letter distribution matches. This is &lt;strong&gt;too weak&lt;/strong&gt;. A decryption that's 95% correct can score &lt;em&gt;as well as or better than&lt;/em&gt; the true plaintext on single-letter counts alone, because shuffling a few letters barely moves the histogram. The search then happily settles on a near-miss garble and calls it done.&lt;/p&gt;

&lt;p&gt;The fix is &lt;strong&gt;n-grams&lt;/strong&gt; — scoring &lt;em&gt;sequences&lt;/em&gt; of letters, not single ones. English is far richer in some letter-pairs and triples (TH, HE, IN, ER; THE, AND, ING) than in others (QZ, JX, VKZ). Any decoding error injects rare, low-probability pairs and triples, which a bigram or trigram score punishes hard. So the fitness function is the &lt;strong&gt;sum of log-probabilities of every trigram&lt;/strong&gt; in the candidate plaintext, using a frequency table built from a large English corpus. Truth scores strictly higher than any near-miss, which is exactly what you need to climb toward.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A useful diagnostic if you ever build one of these: if your solver lands on garbage, check whether &lt;code&gt;score(true plaintext) &amp;gt; score(found)&lt;/code&gt;. If truth scores &lt;em&gt;higher&lt;/em&gt;, your fitness function is fine and your &lt;strong&gt;search&lt;/strong&gt; is stuck — don't tune the scorer, fix the optimizer (next section). If truth scores &lt;em&gt;lower&lt;/em&gt;, the scorer itself is too weak (you're probably on single letters — go to trigrams).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cracking a monoalphabetic substitution (cryptogram)
&lt;/h2&gt;

&lt;p&gt;A simple substitution maps each letter to another, fixed for the whole message. There are 26! ≈ 4×10²⁶ possible alphabets — brute force is hopeless. But the scoring trick makes it tractable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Seed with frequency analysis.&lt;/strong&gt; Count letters in the ciphertext; map the most common cipher letter to E, the next to T, and so on. This is usually 30–60% correct — a decent starting point, not the answer. (You can do this step by hand with a &lt;a href="https://textmachine.org/en/text-tools/frequency-analysis" rel="noopener noreferrer"&gt;frequency analysis tool&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve by local search.&lt;/strong&gt; Swap two letters in the key, re-score, keep the swap if the score went up. Repeat. This is hill-climbing — and on its own it &lt;strong&gt;gets stuck in local optima&lt;/strong&gt;: a key that's better than all its neighbors but still wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escape local optima with simulated annealing.&lt;/strong&gt; The fix is to &lt;em&gt;sometimes accept a worse swap&lt;/em&gt;, with a probability that starts high and "cools" toward zero. Early on the search roams freely and jumps out of bad valleys; late on it behaves like pure hill-climbing and locks onto the peak. Run a few random restarts and keep the best result. This reliably recovers normal English prose.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's precisely what the &lt;a href="https://textmachine.org/en/text-tools/substitution-solver" rel="noopener noreferrer"&gt;substitution cipher solver&lt;/a&gt; does — frequency-seeded, then simulated annealing on trigram fitness — and it recovers both the message &lt;em&gt;and&lt;/em&gt; the full cipher alphabet with no key or crib. Paste a cryptogram and it solves in well under a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cracking a Vigenère without the keyword
&lt;/h2&gt;

&lt;p&gt;Vigenère uses a repeating keyword, so it's polyalphabetic — letter frequencies are smeared flat and the substitution trick above doesn't directly apply. You break it in two stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find the key &lt;em&gt;length&lt;/em&gt;.&lt;/strong&gt; Two classic methods: &lt;strong&gt;Kasiski examination&lt;/strong&gt; looks for repeated sequences in the ciphertext and measures the distances between them — those distances tend to be multiples of the key length. The &lt;strong&gt;Index of Coincidence&lt;/strong&gt; approach tries each candidate length and watches for the one where the slices look like natural (peaky) English.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solve each column independently.&lt;/strong&gt; Once you know the key length L, every L-th letter was enciphered with the &lt;em&gt;same&lt;/em&gt; shift — so the ciphertext splits into L columns, and &lt;strong&gt;each column is just a Caesar cipher.&lt;/strong&gt; Solve each one by frequency / chi-squared against English (only 26 shifts per column), and you've recovered the keyword letter by letter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The robust way to drive this — and what the &lt;a href="https://textmachine.org/en/text-tools/vigenere-solver" rel="noopener noreferrer"&gt;Vigenère solver&lt;/a&gt; does — is to &lt;strong&gt;solve at every plausible key length, then rank the resulting decryptions by English fitness&lt;/strong&gt; and present the best, rather than committing to a single length guessed from a threshold (which fails on repetitive plaintext). A monoalphabetic message naturally collapses to a one-letter key, so the same tool degrades gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  When automatic solving struggles
&lt;/h2&gt;

&lt;p&gt;Auto-solvers are statistical, so they need enough text to be confident:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Too short.&lt;/strong&gt; Under ~40–50 letters there often isn't enough signal; the trigram statistics are noisy. Get more ciphertext if you can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not English.&lt;/strong&gt; The fitness table is language-specific. A French or German plaintext needs a French/German n-gram model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homophones, nulls, or padding.&lt;/strong&gt; Homophonic substitution (several cipher symbols per plaintext letter) and inserted null characters break the one-to-one assumption — identify and strip those first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's not actually a simple substitution/Vigenère.&lt;/strong&gt; If the solver can't find anything English-like at any setting, re-check the cipher type — start again with the &lt;a href="https://textmachine.org/en/text-tools/cipher-identifier" rel="noopener noreferrer"&gt;cipher identifier&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The two-minute version
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify&lt;/strong&gt; the cipher (character set, IoC, structure) — or confirm it's a cryptogram / Vigenère.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste it into the matching auto-solver&lt;/strong&gt; — &lt;a href="https://textmachine.org/en/text-tools/substitution-solver" rel="noopener noreferrer"&gt;substitution&lt;/a&gt; for cryptograms, &lt;a href="https://textmachine.org/en/text-tools/vigenere-solver" rel="noopener noreferrer"&gt;Vigenère&lt;/a&gt; for keyword ciphers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read off the plaintext and the recovered key.&lt;/strong&gt; If it stalls, check the message length and language, and re-confirm the cipher type.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No key, no problem — the statistics of English do the work for you. All of these run entirely in your browser; nothing you paste is uploaded.&lt;/p&gt;

</description>
      <category>cryptography</category>
      <category>security</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to fix garbled (mojibake) subtitles: decode legacy SRT/ASS encodings to UTF-8</title>
      <dc:creator>Picute</dc:creator>
      <pubDate>Fri, 26 Jun 2026 11:03:34 +0000</pubDate>
      <link>https://dev.to/picute/how-to-fix-garbled-mojibake-subtitles-decode-legacy-srtass-encodings-to-utf-8-1eb7</link>
      <guid>https://dev.to/picute/how-to-fix-garbled-mojibake-subtitles-decode-legacy-srtass-encodings-to-utf-8-1eb7</guid>
      <description>&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;You open an &lt;code&gt;.srt&lt;/code&gt; or &lt;code&gt;.ass&lt;/code&gt; subtitle file and instead of text you get garbage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cafÃ©  â€œquotesâ€  ï¿½ï¿½ï¿½  Ã«Â°Â©Ã¬â€"Â´
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Korean turns into &lt;code&gt;ë°©ì†¡&lt;/code&gt;, Japanese into &lt;code&gt;ã‚ãŒã¦&lt;/code&gt;, simplified Chinese into &lt;code&gt;ä½ å¥½&lt;/code&gt; with extra accents. This is &lt;strong&gt;mojibake&lt;/strong&gt; — the file is fine, but it was saved in a &lt;em&gt;legacy&lt;/em&gt; character encoding and your player/editor is reading it as something else (usually UTF-8).&lt;/p&gt;

&lt;p&gt;This post is the practical checklist I reach for: how to identify the real encoding and convert the file to clean UTF-8 — on the command line, in Python, in your editor, or in the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Subtitle files are just text, and text has no inherent encoding — the bytes only mean something once you pick a decoder. Older subtitle files (and a lot of files exported by region-specific tools) are saved in pre-Unicode encodings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Common legacy encoding(s)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Japanese&lt;/td&gt;
&lt;td&gt;Shift-JIS (CP932)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Korean&lt;/td&gt;
&lt;td&gt;EUC-KR / CP949&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chinese (Simplified)&lt;/td&gt;
&lt;td&gt;GB2312 / &lt;strong&gt;GB18030&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chinese (Traditional)&lt;/td&gt;
&lt;td&gt;Big5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cyrillic / Western EU&lt;/td&gt;
&lt;td&gt;Windows-1251 / Windows-1252&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Modern players assume &lt;strong&gt;UTF-8&lt;/strong&gt;. Feed them Shift-JIS bytes and every multi-byte character decodes into the wrong glyphs. The fix is always the same idea: &lt;em&gt;decode with the original encoding, re-encode as UTF-8.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Identify the original encoding
&lt;/h2&gt;

&lt;p&gt;Half the battle is guessing the source encoding. Two quick options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# file gives a rough guess&lt;/span&gt;
file &lt;span class="nt"&gt;-i&lt;/span&gt; subtitle.srt
&lt;span class="c"&gt;# subtitle.srt: text/plain; charset=iso-8859-1   # &amp;lt;- often wrong, but a hint&lt;/span&gt;

&lt;span class="c"&gt;# chardetect (pip install chardet) is usually better for CJK&lt;/span&gt;
chardetect subtitle.srt
&lt;span class="c"&gt;# subtitle.srt: EUC-KR with confidence 0.99&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Heuristics that save time: Korean → try &lt;strong&gt;CP949/EUC-KR&lt;/strong&gt;; Japanese → &lt;strong&gt;Shift-JIS/CP932&lt;/strong&gt;; Simplified Chinese → &lt;strong&gt;GB18030&lt;/strong&gt; (it's a superset of GB2312, so it rarely hurts to use the wider one); Traditional Chinese → &lt;strong&gt;Big5&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Convert with &lt;code&gt;iconv&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Korean EUC-KR -&amp;gt; UTF-8&lt;/span&gt;
iconv &lt;span class="nt"&gt;-f&lt;/span&gt; EUC-KR &lt;span class="nt"&gt;-t&lt;/span&gt; UTF-8 &lt;span class="k"&gt;in&lt;/span&gt;.srt &lt;span class="nt"&gt;-o&lt;/span&gt; out.srt

&lt;span class="c"&gt;# Japanese Shift-JIS -&amp;gt; UTF-8&lt;/span&gt;
iconv &lt;span class="nt"&gt;-f&lt;/span&gt; SHIFT-JIS &lt;span class="nt"&gt;-t&lt;/span&gt; UTF-8 &lt;span class="k"&gt;in&lt;/span&gt;.srt &lt;span class="nt"&gt;-o&lt;/span&gt; out.srt

&lt;span class="c"&gt;# Simplified Chinese -&amp;gt; UTF-8 (use the superset)&lt;/span&gt;
iconv &lt;span class="nt"&gt;-f&lt;/span&gt; GB18030 &lt;span class="nt"&gt;-t&lt;/span&gt; UTF-8 &lt;span class="k"&gt;in&lt;/span&gt;.srt &lt;span class="nt"&gt;-o&lt;/span&gt; out.srt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;iconv&lt;/code&gt; errors out on a stray byte, add &lt;code&gt;//TRANSLIT&lt;/code&gt; or &lt;code&gt;-c&lt;/code&gt; to drop un-mappable characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iconv &lt;span class="nt"&gt;-f&lt;/span&gt; SHIFT-JIS &lt;span class="nt"&gt;-t&lt;/span&gt; UTF-8//TRANSLIT &lt;span class="k"&gt;in&lt;/span&gt;.srt &lt;span class="nt"&gt;-o&lt;/span&gt; out.srt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Convert in Python (batch-friendly)
&lt;/h2&gt;

&lt;p&gt;Useful when you have a folder of files and don't fully trust a single guess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_utf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# {'encoding': 'EUC-KR', 'confidence': 0.99}
&lt;/span&gt;    &lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;encoding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.utf8.srt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) -&amp;gt; utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;srt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.srt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;to_utf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;srt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two gotchas: &lt;code&gt;chardet&lt;/code&gt; can confidently mislabel short files, and it often reports &lt;code&gt;GB2312&lt;/code&gt; for files that contain characters only present in &lt;code&gt;GB18030&lt;/code&gt; — if you see missing glyphs, re-run forcing &lt;code&gt;gb18030&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Convert in your editor (no terminal)
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;VS Code&lt;/strong&gt;: open the file, click the encoding in the status bar (e.g. &lt;code&gt;UTF-8&lt;/code&gt;) → &lt;strong&gt;Reopen with Encoding&lt;/strong&gt; → pick the real one (e.g. &lt;code&gt;Korean (EUC-KR)&lt;/code&gt;). When it looks right, click the encoding again → &lt;strong&gt;Save with Encoding&lt;/strong&gt; → &lt;code&gt;UTF-8&lt;/code&gt;. Sublime Text and Notepad++ have the same "reopen/convert to UTF-8" flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Convert in the browser (no install)
&lt;/h2&gt;

&lt;p&gt;When I just want to drag-and-drop one file without touching a terminal, I use a free browser tool: &lt;strong&gt;&lt;a href="https://picute.net/en/tools/subtitle-encoding-converter" rel="noopener noreferrer"&gt;Picute's subtitle encoding converter&lt;/a&gt;&lt;/strong&gt;. It re-decodes EUC-KR, Shift-JIS, GB18030, Big5, and Windows-125x to UTF-8 entirely client-side — the file never leaves your machine — and previews the result so you can confirm the glyphs are right before downloading.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full disclosure: I'm affiliated with Picute (it's an AI subtitle/caption tool). The converter above is free and needs no signup; I'm including it as one option next to the CLI/Python/editor methods, not as a replacement for them.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A few rules that prevent mojibake next time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always save subtitles as UTF-8&lt;/strong&gt; (without BOM for &lt;code&gt;.srt&lt;/code&gt;/&lt;code&gt;.vtt&lt;/code&gt; — some players choke on the BOM bytes at the start of the first cue).&lt;/li&gt;
&lt;li&gt;If a player still shows boxes after converting, the issue may be a &lt;strong&gt;missing font&lt;/strong&gt; for that script, not the encoding — try a font that covers the glyphs.&lt;/li&gt;
&lt;li&gt;Keep the &lt;em&gt;original&lt;/em&gt; file until you've verified the converted one; a wrong source-encoding guess is silent and easy to miss on a quick scan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole toolkit. Pick whichever layer fits your workflow — they all do the same job: decode with the real encoding, write UTF-8.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
