<?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: 코딩나우(하늘아래)</title>
    <description>The latest articles on DEV Community by 코딩나우(하늘아래) (@_8729c5bde46be2).</description>
    <link>https://dev.to/_8729c5bde46be2</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%2F3963698%2F35ec10a0-2beb-4cc6-a512-724301643a25.png</url>
      <title>DEV Community: 코딩나우(하늘아래)</title>
      <link>https://dev.to/_8729c5bde46be2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_8729c5bde46be2"/>
    <language>en</language>
    <item>
      <title>WiFi QR codes: the five escapes, the all-hex SSID trap, and what ECC level H costs you</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 30 Sep 2026 03:24:32 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/wifi-qr-codes-the-five-escapes-the-all-hex-ssid-trap-and-what-ecc-level-h-costs-you-217c</link>
      <guid>https://dev.to/_8729c5bde46be2/wifi-qr-codes-the-five-escapes-the-all-hex-ssid-trap-and-what-ecc-level-h-costs-you-217c</guid>
      <description>&lt;p&gt;The payload behind a "join this network" QR code is a single line of text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WIFI:T:WPA;S:cafe-wifi;P:p@ssw0rd;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worth knowing up front: &lt;strong&gt;this is not an official standard.&lt;/strong&gt; It is the convention &lt;a href="https://github.com/zxing/zxing/wiki/Barcode-Contents" rel="noopener noreferrer"&gt;ZXing&lt;/a&gt; introduced, which the early Android scanners shipped with, and which everything else then matched. There is no RFC to point at when something disagrees.&lt;/p&gt;

&lt;p&gt;Which matters, because the moment a password contains punctuation, the format starts biting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five characters have to be escaped
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;S:&lt;/code&gt; and &lt;code&gt;P:&lt;/code&gt;, a backslash escapes the five characters that would otherwise end a field: &lt;code&gt;\&lt;/code&gt; &lt;code&gt;;&lt;/code&gt; &lt;code&gt;,&lt;/code&gt; &lt;code&gt;:&lt;/code&gt; &lt;code&gt;"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I built payloads for a set of passwords and parsed them two ways — once by naively splitting on &lt;code&gt;;&lt;/code&gt; and &lt;code&gt;:&lt;/code&gt; (how you write it the first time), once following the escape rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  password 'p@ssw0rd'
     payload   WIFI:T:WPA;S:cafe-wifi;P:p@ssw0rd;;
     naive     'p@ssw0rd'          OK
     spec      'p@ssw0rd'          OK

  password 'coffee;time'
     payload   WIFI:T:WPA;S:cafe-wifi;P:coffee\;time;;
     naive     'coffee\'           &amp;lt;- truncated
     spec      'coffee;time'       OK

  password 'a:b:c'
     payload   WIFI:T:WPA;S:cafe-wifi;P:a\:b\:c;;
     naive     'a\:b\:c'           &amp;lt;- backslashes kept
     spec      'a:b:c'             OK

  password 'back\slash'
     payload   WIFI:T:WPA;S:cafe-wifi;P:back\\slash;;
     naive     'back\\slash'       &amp;lt;- doubled
     spec      'back\slash'        OK

  password 'say"hi"'
     payload   WIFI:T:WPA;S:cafe-wifi;P:say\"hi\";;
     naive     'say\"hi\"'         &amp;lt;- backslashes kept
     spec      'say"hi"'           OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;;&lt;/code&gt; case is the dangerous one. It does not throw, it does not warn — it hands back &lt;code&gt;coffee\&lt;/code&gt; and the user gets an authentication failure with no clue why. The other three come back visibly wrong, which at least gets noticed.&lt;/p&gt;

&lt;p&gt;One nuance from the same run: I escaped a comma in the &lt;strong&gt;SSID&lt;/strong&gt; (&lt;code&gt;cafe\,shop&lt;/code&gt;) and the naive parser still read the password correctly, because a comma never splits a &lt;code&gt;;&lt;/code&gt;-delimited string. The comma is in the escape set for spec compliance, not because &lt;code&gt;;&lt;/code&gt;-splitting needs it. Worth knowing if you are auditing someone else's encoder and wondering why their commas are bare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it survive an actual QR code?
&lt;/h2&gt;

&lt;p&gt;Escaping is only half the question. The other half is whether a real encoder and a real decoder agree once the payload has backslashes in it. So I baked each payload into an actual QR and read it back with OpenCV 5.0.0:&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;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode&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;ecc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QRCODE_ENCODER_CORRECT_LEVEL_M&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QRCodeEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correction_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ecc&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QRCodeEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&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="k"&gt;def&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;img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1 pixel per module is too small for the detector: scale up and add a quiet zone
&lt;/span&gt;    &lt;span class="n"&gt;big&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interpolation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INTER_NEAREST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;big&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copyMakeBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;big&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BORDER_CONSTANT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;QRCodeDetector&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;detectAndDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;big&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  'p@ssw0rd'     -&amp;gt; 33x33 modules (version 4)  round-trip matches
  'coffee;time'  -&amp;gt; 33x33 modules (version 4)  round-trip matches
  'a:b:c'        -&amp;gt; 33x33 modules (version 4)  round-trip matches
  'back\slash'   -&amp;gt; 33x33 modules (version 4)  round-trip matches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Byte-identical both ways. The backslashes are ordinary payload bytes as far as QR is concerned — nothing at the barcode layer touches them. Every failure I have seen with these codes lives in the string handling above it.&lt;/p&gt;

&lt;p&gt;Two things that cost me time here, in case you run the same experiment: the encoder emits &lt;strong&gt;one pixel per module&lt;/strong&gt;, so feeding its output straight to the detector fails — you have to scale up. And it needs a quiet zone; without the border the detector finds nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SSID that gets read as hex
&lt;/h2&gt;

&lt;p&gt;This one is not about punctuation at all. If the SSID consists &lt;strong&gt;entirely of hex digits&lt;/strong&gt;, it has to be wrapped in double quotes, or a reader is entitled to decode it as hex bytes rather than as text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  SSID 'deadbeef'   all hex: True   -&amp;gt; needs quotes
       WIFI:T:WPA;S:"deadbeef";P:pw123456;;

  SSID '5060'       all hex: True   -&amp;gt; needs quotes
       WIFI:T:WPA;S:"5060";P:pw123456;;

  SSID 'cafe-5060'  all hex: False  -&amp;gt; fine as-is
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;5060&lt;/code&gt; is the trap. A four-digit network name looks like the least exotic thing in the world, and a router that ships with the last four of its serial number as the SSID hits this by accident. &lt;code&gt;cafe&lt;/code&gt; and &lt;code&gt;deadbeef&lt;/code&gt; and &lt;code&gt;abc123&lt;/code&gt; are all in the same boat.&lt;/p&gt;

&lt;p&gt;The check is two lines, and skipping it is the kind of bug that reproduces on exactly one customer's network.&lt;/p&gt;

&lt;h2&gt;
  
  
  What error correction costs
&lt;/h2&gt;

&lt;p&gt;The other practical question: how much room does a long password actually take? I fixed a 9-character SSID and grew the password, encoding at each of the four error-correction levels. Numbers are the module count per side:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;password&lt;/th&gt;
&lt;th&gt;payload&lt;/th&gt;
&lt;th&gt;L&lt;/th&gt;
&lt;th&gt;M&lt;/th&gt;
&lt;th&gt;Q&lt;/th&gt;
&lt;th&gt;H&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8 chars&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things I took from this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short passwords are free.&lt;/strong&gt; 8 and 12 characters produce an identical 33x33 grid at L and M. There is no size argument for a weaker password until you are past about 12 characters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;H is not a small ask.&lt;/strong&gt; At a 63-character password, H needs 57 modules against L's 41 — the same physical square has modules &lt;strong&gt;1.4x narrower on each side&lt;/strong&gt;, which is exactly the thing that makes a print-out stop scanning from across a room. H buys you damage tolerance you mostly do not need on a laminated card taped to a wall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The jump points are what matter, not the length.&lt;/strong&gt; Growth is a staircase, and the steps land in different places per level: at L the grid holds at 33 all the way to a 20-character password and only steps up at 32, while at M it has already stepped up by 16. If you are sitting just past a step, three fewer characters can keep you a version smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  The encoder and parser, in full
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SPECIAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;;,:&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&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="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SPECIAL&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0123456789abcdefABCDEF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WPA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;%s&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;           &lt;span class="c1"&gt;# quote AFTER escaping - see below
&lt;/span&gt;    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WIFI:T:%s;S:%s;P:%s;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;H:true;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WIFI:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):]&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;      &lt;span class="c1"&gt;# escaped: take the next char verbatim
&lt;/span&gt;            &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&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="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&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="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&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;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;s&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="sh"&gt;'"'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;               &lt;span class="c1"&gt;# structural quotes, not part of the name
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser is a character loop rather than a &lt;code&gt;split&lt;/code&gt;, and that is the whole point. There is no regex that splits on unescaped &lt;code&gt;;&lt;/code&gt; without either lookbehind gymnastics or a bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ordering trap I wrote for myself
&lt;/h3&gt;

&lt;p&gt;My first version of &lt;code&gt;build&lt;/code&gt; wrapped the SSID in quotes and &lt;em&gt;then&lt;/em&gt; escaped the whole thing. That produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WIFI:T:WPA;S:\"deadbeef\";P:pw123456;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quotes are now escaped, which means they are literal &lt;code&gt;"&lt;/code&gt; characters in the network name rather than the wrapper that says "read this as text, not hex". You get a payload that is valid, parses cleanly, and describes a network called &lt;code&gt;"deadbeef"&lt;/code&gt; — with the quotes — which does not exist.&lt;/p&gt;

&lt;p&gt;Escape first, wrap second. And on the way back out, strip the wrapper, or you hand the caller the same wrong name.&lt;/p&gt;

&lt;p&gt;Note &lt;code&gt;T:nopass&lt;/code&gt; for an open network, and that the trailing &lt;code&gt;;;&lt;/code&gt; is two semicolons — one ends the last field, one ends the payload.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I did not test:&lt;/strong&gt; actual phone cameras. Everything above is OpenCV encoding and decoding on one machine. Whether iOS Camera and the Android scanner agree on the all-hex SSID rule, or on a bare comma, I cannot tell you — I have no way to assert what a closed scanner does without testing every one of them. If you have a phone in hand and a minute, that is the gap I would most like filled in the comments.&lt;/p&gt;

&lt;p&gt;The version for people who just want a code for the guest network, with the placement and safety bits: &lt;a href="https://www.coding-now.com/en/guides/wifi-qr-code?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/wifi-qr-code?utm_source=devto&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>opencv</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>That JSON error quotes what the server actually sent - and V8 cuts it at exactly 20 characters</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 30 Sep 2026 01:42:57 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/that-json-error-quotes-what-the-server-actually-sent-and-v8-cuts-it-at-exactly-20-characters-5638</link>
      <guid>https://dev.to/_8729c5bde46be2/that-json-error-quotes-what-the-server-actually-sent-and-v8-cuts-it-at-exactly-20-characters-5638</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SyntaxError: Unexpected token '&amp;lt;', "&amp;lt;!DOCTYPE "... is not valid JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everyone knows what this means by now: the server sent HTML, your &lt;code&gt;res.json()&lt;/code&gt; tried to parse it, the parser quit on the first character.&lt;/p&gt;

&lt;p&gt;What I had never thought about is the quoted part. &lt;code&gt;"&amp;lt;!DOCTYPE "&lt;/code&gt; is not a canned string V8 prints for HTML responses. It is &lt;strong&gt;the literal first ten characters of whatever you handed the parser&lt;/strong&gt;. Which makes it a free, zero-effort peek at the response body — and it is more precise than I assumed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the cut happens
&lt;/h2&gt;

&lt;p&gt;I grew the input one character at a time on node v24.13.0 (V8 13.6.233.17):&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 -&amp;gt; Unexpected token '&amp;lt;', "&amp;lt;xxxxxxxxxxxxxxxxxxx" is not valid JSON
21 -&amp;gt; Unexpected token '&amp;lt;', "&amp;lt;xxxxxxxxx"... is not valid JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;20 characters or fewer: quoted whole. 21 or more: first ten, then &lt;code&gt;...&lt;/code&gt;.&lt;/strong&gt; One threshold, one prefix width.&lt;/p&gt;

&lt;p&gt;Except the ten is not measured from the start of the string. It is measured from &lt;strong&gt;where parsing failed&lt;/strong&gt;. Push the failure point inward with a value that starts out valid, and the quote grows to match:&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="k"&gt;for &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;pad&lt;/span&gt; &lt;span class="k"&gt;of&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// '[' opens an array, then junk&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fails at 1  -&amp;gt; "[oooooooooo"...            11 chars
fails at 2  -&amp;gt; "[ oooooooooo"...           12
fails at 3  -&amp;gt; "[  oooooooooo"...          13
fails at 6  -&amp;gt; "[     oooooooooo"...       16
fails at 11 -&amp;gt; "          oooooooooo"...   20  &amp;lt;- and now it slides
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the actual rule: &lt;strong&gt;the quoted window ends ten characters past the failure point and is capped at 20 characters wide&lt;/strong&gt;, sliding forward once it hits the cap — and if the whole input fits in 20, you get all of it with no &lt;code&gt;...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For an HTML response none of that matters, because &lt;code&gt;&amp;lt;&lt;/code&gt; fails at index 0 and the window is just the first ten. But it explains why the same error sometimes quotes eleven characters, or a run of text from the middle of the payload.&lt;/p&gt;

&lt;p&gt;The message still gives you two free readings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A trailing &lt;code&gt;...&lt;/code&gt; means the body was longer than 20 characters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No&lt;/strong&gt; &lt;code&gt;...&lt;/code&gt; means the quotes contain the entire response. Whatever is between them is everything the server sent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second one is the useful half. A short body is fully diagnosed from the error line alone, without opening DevTools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prefix is a fingerprint
&lt;/h2&gt;

&lt;p&gt;Ten characters is enough to identify most things that show up where JSON should be. Each row below is the message that body actually produced:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quoted prefix&lt;/th&gt;
&lt;th&gt;Body started as&lt;/th&gt;
&lt;th&gt;Typically&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"&amp;lt;!DOCTYPE "...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;an HTML document&lt;/td&gt;
&lt;td&gt;404/500 page, login page, SPA &lt;code&gt;index.html&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"&amp;lt;script sr"...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTML starting with &lt;code&gt;&amp;lt;script src=&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;a script injected ahead of the page, a CDN or WAF interstitial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"&amp;lt;?xml vers"...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XML&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Error&amp;gt;&amp;lt;Code&amp;gt;&lt;/code&gt; from object storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"Internal S"...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;one line of plain text&lt;/td&gt;
&lt;td&gt;a proxy or gateway wrote the 500 itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"undefined"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the string &lt;code&gt;"undefined"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;not the server — you passed a missing value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"[object Object]"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a stringified object&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;JSON.parse&lt;/code&gt; on something already parsed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last two are worth internalising, because they print &lt;strong&gt;without&lt;/strong&gt; any &lt;code&gt;Unexpected token&lt;/code&gt; prefix — just &lt;code&gt;"undefined" is not valid JSON&lt;/code&gt;. There is no token to name, so V8 skips that clause entirely. If you see that shape, stop looking at the network tab; nothing came off the wire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meanwhile, nobody honours your Accept header
&lt;/h2&gt;

&lt;p&gt;I assumed a framework would at least try to answer JSON when you ask for it explicitly. It does not. Against this site's own Next.js 16 dev server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; http://localhost:3000/api/nope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;404&lt;/span&gt; &lt;span class="ne"&gt;Not Found&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text/html; charset=utf-8&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ko"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;    &lt;span class="nt"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="err"&gt;26,283&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;26,283 bytes of rendered 404 page, in reply to a request that said it wanted JSON. Production, on Vercel, sent 20,672 bytes of the same thing. The &lt;code&gt;Accept&lt;/code&gt; header changed nothing on either.&lt;/p&gt;

&lt;h2&gt;
  
  
  And &lt;code&gt;fetch&lt;/code&gt; hands it to you as a success
&lt;/h2&gt;

&lt;p&gt;Same call against five targets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;status&lt;/th&gt;
&lt;th&gt;ok&lt;/th&gt;
&lt;th&gt;redirected&lt;/th&gt;
&lt;th&gt;Content-Type&lt;/th&gt;
&lt;th&gt;&lt;code&gt;res.json()&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;missing API route, dev&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;text/html&lt;/td&gt;
&lt;td&gt;SyntaxError&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;missing API route, prod&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;text/html&lt;/td&gt;
&lt;td&gt;SyntaxError&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;http -&amp;gt; https redirect&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;true&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;text/html&lt;/td&gt;
&lt;td&gt;SyntaxError&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;healthy JSON API&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;application/json&lt;/td&gt;
&lt;td&gt;parsed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;port with nothing on it&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;TypeError&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the first four rows &lt;code&gt;fetch&lt;/code&gt; never threw. Every exception came from &lt;code&gt;res.json()&lt;/code&gt;. So the message points at JSON while the actual evidence — &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;Content-Type&lt;/code&gt;, &lt;code&gt;redirected&lt;/code&gt; — was already sitting in your hand one line earlier.&lt;/p&gt;

&lt;p&gt;Two of those rows deserve a note:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;redirected: true&lt;/code&gt;&lt;/strong&gt; is the login-page case. &lt;code&gt;fetch&lt;/code&gt; follows redirects silently, so an expired session gets you a 200 with a login page in the body and no indication that you went somewhere else. &lt;code&gt;res.url&lt;/code&gt; is the tell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The last row is a different error entirely.&lt;/strong&gt; No &lt;code&gt;res&lt;/code&gt; exists. Node says &lt;code&gt;TypeError: fetch failed&lt;/code&gt;, Chrome 152 says &lt;code&gt;TypeError: Failed to fetch&lt;/code&gt;. Same situation, two strings, and only one of them is searchable alongside the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrapper
&lt;/h2&gt;

&lt;p&gt;The fix is to look at the body before the parser does, and to put the body in the error when it is not JSON:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&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;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;          &lt;span class="c1"&gt;// text first, not json&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Not JSON: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;`final URL: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; (redirected)&lt;/span&gt;&lt;span class="dl"&gt;'&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="s2"&gt;\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;`first 200 chars: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;res.text()&lt;/code&gt; first is the part that matters. The body is a stream you get &lt;strong&gt;once&lt;/strong&gt;, so calling &lt;code&gt;res.text()&lt;/code&gt; after &lt;code&gt;res.json()&lt;/code&gt; already failed throws a second, less interesting error about the body being disturbed. Reverse the order and the body is still yours at the moment you need it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Content-Type&lt;/code&gt; check also catches the case an &lt;code&gt;res.ok&lt;/code&gt; guard cannot: a dev server or proxy answering &lt;code&gt;/api/...&lt;/code&gt; with &lt;code&gt;index.html&lt;/code&gt; at status &lt;strong&gt;200&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python tells you nothing
&lt;/h2&gt;

&lt;p&gt;Worth knowing if you move between the two. Four bodies that fail for four different reasons, through &lt;code&gt;json.loads&lt;/code&gt; on python 3.11.9:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'&amp;lt;!DOCTYPE html&amp;gt;\n&amp;lt;html&amp;gt;'   -&amp;gt; Expecting value: line 1 column 1 (char 0)
''                          -&amp;gt; Expecting value: line 1 column 1 (char 0)
'Internal Server Error'     -&amp;gt; Expecting value: line 1 column 1 (char 0)
'undefined'                 -&amp;gt; Expecting value: line 1 column 1 (char 0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identical. &lt;code&gt;char 0&lt;/code&gt; says the first character was not the start of a value and stops there — it never says which character. The V8 message is doing real work that &lt;code&gt;json&lt;/code&gt; does not, so on the Python side you have to print &lt;code&gt;r.text[:200]&lt;/code&gt; yourself.&lt;/p&gt;

&lt;p&gt;One more asymmetry in the same direction: &lt;code&gt;urllib&lt;/code&gt; &lt;strong&gt;raises&lt;/strong&gt; &lt;code&gt;HTTPError&lt;/code&gt; on a 404, where &lt;code&gt;fetch&lt;/code&gt; resolves happily. &lt;code&gt;requests&lt;/code&gt; behaves like &lt;code&gt;fetch&lt;/code&gt; and leaves &lt;code&gt;r.ok&lt;/code&gt; to you.&lt;/p&gt;




&lt;p&gt;The long version, with the five causes and how the headers separate them, is here: &lt;a href="https://www.coding-now.com/en/guides/unexpected-token-doctype-json?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/unexpected-token-doctype-json?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If anyone knows whether that 20/10 window is stable across V8 versions, I would like to hear it — I only measured one, and I did not check SpiderMonkey or JavaScriptCore at all. And if you have seen a prefix that is not in the table above, post it; the fingerprint list is the part that gets more useful with more eyes.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>node</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Windows tar reads RAR and 7z - and silently writes a tar when you ask it for a RAR</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Tue, 29 Sep 2026 06:49:54 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/windows-tar-reads-rar-and-7z-and-silently-writes-a-tar-when-you-ask-it-for-a-rar-5ca0</link>
      <guid>https://dev.to/_8729c5bde46be2/windows-tar-reads-rar-and-7z-and-silently-writes-a-tar-when-you-ask-it-for-a-rar-5ca0</guid>
      <description>&lt;p&gt;The &lt;code&gt;tar.exe&lt;/code&gt; that ships in &lt;code&gt;C:\Windows\System32&lt;/code&gt; is libarchive, not GNU tar. That has two consequences worth knowing: it extracts formats you would not expect, and it fails at one of them in a way that returns exit code 0.&lt;/p&gt;

&lt;p&gt;Measured on Windows 11 25H2, build 26200.9457.&lt;/p&gt;

&lt;h2&gt;
  
  
  It reads RAR and 7z
&lt;/h2&gt;

&lt;p&gt;No install, no Store extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-tf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archive.rar&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c"&gt;# list without extracting&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-xf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archive.rar&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c"&gt;# extract here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-xf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archive.7z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\unpacked&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 1,731-byte RAR listed its entry and extracted to 4,585 bytes. A 909KB 7z extracted all 29 files. Both returned exit code 0.&lt;/p&gt;

&lt;p&gt;The RAR tested carried the signature &lt;code&gt;52 61 72 21 1A 07 00&lt;/code&gt; — &lt;strong&gt;RAR4&lt;/strong&gt;. RAR5, which current WinRAR writes by default, was &lt;strong&gt;not tested&lt;/strong&gt; here; I had no RAR5 sample. That is a gap in the measurement, not a known failure. &lt;code&gt;tar -tf&lt;/code&gt; on your own file answers it in a second.&lt;/p&gt;

&lt;p&gt;This is also why the "you need WinRAR" advice is mostly obsolete on Windows 11: the shell has had native RAR/7z handling since 23H2. You can see it in the registry — &lt;code&gt;.rar&lt;/code&gt; lists &lt;strong&gt;&lt;code&gt;ArchiveFolder&lt;/code&gt;&lt;/strong&gt; among its handlers. On my machine the &lt;em&gt;default&lt;/em&gt; was an installed archiver that had claimed the association years ago, which is the usual reason nobody notices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now the part that cost me time
&lt;/h2&gt;

&lt;p&gt;Ask it to &lt;em&gt;create&lt;/em&gt; a RAR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-cf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out.rar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$LASTEXITCODE&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# 0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 0. A file appears. It is not a RAR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source folder   5,806,287 bytes
out.rar         5,837,312 bytes    &amp;lt;- larger than the input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Byte 257 of &lt;code&gt;out.rar&lt;/code&gt; reads &lt;code&gt;ustar&lt;/code&gt;. libarchive cannot write the RAR format — it is proprietary — so &lt;code&gt;-a&lt;/code&gt; (auto-detect by extension) fell through to an &lt;strong&gt;uncompressed tar&lt;/strong&gt; and used the name you asked for. No warning, no non-zero exit, and the only visible symptom is that your "compressed" archive grew.&lt;/p&gt;

&lt;p&gt;If a build script does this, the artifact ships and fails at whoever tries to open it with a real RAR tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does write correctly
&lt;/h2&gt;

&lt;p&gt;Verified by signature, not by extension:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;First bytes&lt;/th&gt;
&lt;th&gt;Actual format&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tar -a -cf out.7z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;37 7A BC AF 27 1C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7z&lt;/td&gt;
&lt;td&gt;798,214&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tar -a -cf out.zip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;50 4B 03 04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;zip&lt;/td&gt;
&lt;td&gt;1,798,875&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tar -czf out.tar.gz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1F 8B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;gzip&lt;/td&gt;
&lt;td&gt;1,788,453&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tar -a -cf out.rar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2E 2F 00 00&lt;/code&gt; + &lt;code&gt;ustar&lt;/code&gt; @257&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;plain tar&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5,837,312&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same 5,806,287-byte source for all four.&lt;/p&gt;

&lt;p&gt;Two things stand out. &lt;strong&gt;7z came out 2.25x smaller than zip&lt;/strong&gt; — 13.7% of the source versus 31.0%. And &lt;strong&gt;tar.gz landed within 1% of zip&lt;/strong&gt;, which I did not expect; the deflate stream is doing nearly all the work in both, and the container overhead barely registers at this size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical notes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Check the signature, not the extension, when a script produces an archive.&lt;/strong&gt; Two bytes is enough for zip and gzip, six for 7z, and &lt;code&gt;ustar&lt;/code&gt; at offset 257 catches a tar wearing someone else's name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-a&lt;/code&gt; picks the format from the filename.&lt;/strong&gt; That is convenient until the requested format is unwritable, at which point it degrades silently. Being explicit (&lt;code&gt;-czf&lt;/code&gt; for tar.gz) removes the guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passwords are out of scope.&lt;/strong&gt; There is no flag to supply one, so encrypted archives need a real archiver. Creating RAR and handling encrypted archives are the two jobs that still justify installing something — extraction is not one of them.&lt;/p&gt;




&lt;p&gt;The version for people who just want the file open, with the Explorer route: &lt;a href="https://www.coding-now.com/en/guides/open-rar-7z-windows?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/open-rar-7z-windows?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If anyone has a RAR5 sample handy, I would like to know whether &lt;code&gt;tar -tf&lt;/code&gt; lists it on 25H2 — that is the one row of my table I could not fill in.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Windows encodes M4A and MP3 at the same bitrate, so the file sizes land 0.6% apart</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Tue, 29 Sep 2026 03:37:48 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/windows-encodes-m4a-and-mp3-at-the-same-bitrate-so-the-file-sizes-land-06-apart-1blh</link>
      <guid>https://dev.to/_8729c5bde46be2/windows-encodes-m4a-and-mp3-at-the-same-bitrate-so-the-file-sizes-land-06-apart-1blh</guid>
      <description>&lt;p&gt;"Convert M4A to MP3 to save space" is advice you see everywhere. I measured it with the encoders that ship in Windows, and on the same source the two files came out &lt;strong&gt;0.6% apart&lt;/strong&gt;. Here is what is actually going on, and the PowerShell that does the conversion without installing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;One 10-second, 44.1kHz, 16-bit stereo WAV — 1,764,044 bytes, which Windows reports as 1,411,200 bps (44100 × 16 × 2, exactly CD rate). Everything below went through &lt;code&gt;Windows.Media.Transcoding.MediaTranscoder&lt;/code&gt; on Windows 11, with sizes from the filesystem and bitrates from &lt;code&gt;MusicProperties&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same preset, same bitrate, same size
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Bitrate reported&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;source WAV&lt;/td&gt;
&lt;td&gt;1,764,044&lt;/td&gt;
&lt;td&gt;1,411,200 bps&lt;/td&gt;
&lt;td&gt;10.00s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M4A &lt;code&gt;CreateM4a("High")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;243,233&lt;/td&gt;
&lt;td&gt;192,512 bps&lt;/td&gt;
&lt;td&gt;10.01s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M4A &lt;code&gt;CreateM4a("Medium")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;162,994&lt;/td&gt;
&lt;td&gt;128,448 bps&lt;/td&gt;
&lt;td&gt;10.01s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M4A &lt;code&gt;CreateM4a("Low")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;122,926&lt;/td&gt;
&lt;td&gt;96,392 bps&lt;/td&gt;
&lt;td&gt;10.01s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MP3 &lt;code&gt;CreateMp3("High")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;241,775&lt;/td&gt;
&lt;td&gt;192,000 bps&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The High presets are &lt;strong&gt;192kbps for both&lt;/strong&gt;. That is the whole explanation for the size result: AAC may be the better codec at a given bitrate, but Windows' presets never give it a lower bitrate to prove that with. You get the same bits either way, and the 1,458-byte gap is container overhead.&lt;/p&gt;

&lt;p&gt;So if you are converting M4A to MP3 to save space, you are doing work for nothing. The only good reason is that &lt;strong&gt;something downstream only accepts MP3&lt;/strong&gt; — an upload validator, a car stereo, a hardware player.&lt;/p&gt;

&lt;h2&gt;
  
  
  The round trip is worse than it looks
&lt;/h2&gt;

&lt;p&gt;Converting that M4A onward to MP3 gave 239,471 bytes. Similar size, but the audio inside has now been through two lossy encoders. And going back the other way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source WAV            1,764,044 bytes
  -&amp;gt; M4A (High)         243,233 bytes
  -&amp;gt; WAV again        1,921,106 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The recovered WAV is larger than the original WAV.&lt;/strong&gt; A lossless container makes the file big again; it does not make the discarded audio reappear. If you ever need this spelled out for a non-technical colleague, that line usually does it.&lt;/p&gt;

&lt;p&gt;There is a subtler one. Windows reports the source WAV as 10.00s, the M4A as 10.01s, and the MP3 as 9.96s. Encoder delay and padding are not free, and the container's idea of duration drifts with each hop. If you are stitching converted clips together, that drift is where the click at the seam comes from.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conversion, with nothing installed
&lt;/h2&gt;

&lt;p&gt;Windows ships the encoders; PowerShell can reach them through WinRT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Runtime.WindowsRuntime&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.WindowsRuntimeSystemExtensions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetMethods&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$op&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'AsTask'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetParameters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetParameters&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="nf"&gt;ParameterType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'IAsyncOperation*'&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'AsTask'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetParameters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetParameters&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="nf"&gt;ParameterType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'IAsyncActionWithProgress*'&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Wait-Op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeGenericMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Out-Null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Wait-Act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeGenericMethod&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Storage.StorageFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Windows.Storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WindowsRuntime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Storage.StorageFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Windows.Storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WindowsRuntime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Media.MediaProperties.MediaEncodingProfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Windows.Media&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WindowsRuntime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Media.Transcoding.MediaTranscoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Windows.Media&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WindowsRuntime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$folderPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Users\you\Music"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$folder&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wait-Op&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Storage.StorageFolder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;GetFolderFromPathAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$folderPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Storage.StorageFolder&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$profile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Media.MediaProperties.MediaEncodingProfile&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;CreateMp3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"High"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$folderPath&lt;/span&gt;&lt;span class="s2"&gt;\*.m4a"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wait-Op&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Storage.StorageFile&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;GetFileFromPathAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Storage.StorageFile&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wait-Op&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$folder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateFileAsync&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nx"&gt;ChangeExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".mp3"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Storage.StorageFile&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Windows.Media.Transcoding.MediaTranscoder&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$prep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wait-Op&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PrepareFileTranscodeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$profile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Media.Transcoding.PrepareTranscodeResult&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CanTranscode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Wait-Act&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TranscodeAsync&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$prep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FailureReason&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things worth knowing before you run it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;Wait-Op&lt;/code&gt; / &lt;code&gt;Wait-Act&lt;/code&gt; wrappers are the point, not boilerplate.&lt;/strong&gt; Windows PowerShell 5.1 has no &lt;code&gt;await&lt;/code&gt;, and these calls are asynchronous. Skip the wait and you measure a file that is still being written — that is how a ten-second clip first came out of this script as 4KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CanTranscode&lt;/code&gt; is where failures surface.&lt;/strong&gt; It returns false for a missing codec and for DRM-protected files. Old iTunes purchases (&lt;code&gt;.m4p&lt;/code&gt;) land here and cannot be converted this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swap the profile, not the script.&lt;/strong&gt; &lt;code&gt;CreateM4a&lt;/code&gt;, &lt;code&gt;CreateWav&lt;/code&gt; and &lt;code&gt;CreateMp3&lt;/code&gt; all take the same &lt;code&gt;"High" | "Medium" | "Low"&lt;/code&gt;, so the same loop covers every direction — which is how the table above was produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprises people
&lt;/h2&gt;

&lt;p&gt;M4A is a container, not a codec. It is the audio-only sibling of &lt;code&gt;.mp4&lt;/code&gt;, which is why renaming one to &lt;code&gt;.mp4&lt;/code&gt; often still plays — same internal structure, different extension. It is also why renaming solves nothing when something validates the format.&lt;/p&gt;

&lt;p&gt;And on Windows specifically: there is no Store extension to install. Unlike HEIC, M4A plays out of the box. When a double-click does nothing, the format is not the problem — no program is associated with the extension.&lt;/p&gt;




&lt;p&gt;The version written for people who just received a voice memo they cannot open: &lt;a href="https://www.coding-now.com/en/guides/m4a-to-mp3?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/m4a-to-mp3?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Curious whether other platforms' presets pin AAC and MP3 to the same bitrate the way Windows does — if ffmpeg defaults differ on your setup, I would like to see the numbers.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>audio</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Resize, format, quality: I measured all three image-size levers and one wins by 10x</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Mon, 28 Sep 2026 06:30:20 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/resize-format-quality-i-measured-all-three-image-size-levers-and-one-wins-by-10x-244o</link>
      <guid>https://dev.to/_8729c5bde46be2/resize-format-quality-i-measured-all-three-image-size-levers-and-one-wins-by-10x-244o</guid>
      <description>&lt;p&gt;There are exactly three ways to make an image file smaller: change its dimensions, change its format, change its quality. Everyone knows this. What is less obvious is how unevenly they pay off — so I measured all three on the same photo instead of guessing.&lt;/p&gt;

&lt;p&gt;Source: a 6400x4000 photograph, &lt;code&gt;C:\Windows\Web\Screen\img102.jpg&lt;/code&gt;, 1,389,605 bytes as shipped. Everything below is Pillow 12.3.0 on Windows 11. PSNR is measured against the decoded source, so higher is closer to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 1: format
&lt;/h2&gt;

&lt;p&gt;Same pixels, same dimensions, different container.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;vs PNG&lt;/th&gt;
&lt;th&gt;PSNR (dB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PNG (lossless)&lt;/td&gt;
&lt;td&gt;7,058,497&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPEG q100&lt;/td&gt;
&lt;td&gt;4,228,908&lt;/td&gt;
&lt;td&gt;59.9%&lt;/td&gt;
&lt;td&gt;55.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPEG q90&lt;/td&gt;
&lt;td&gt;1,358,048&lt;/td&gt;
&lt;td&gt;19.2%&lt;/td&gt;
&lt;td&gt;49.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPEG q80&lt;/td&gt;
&lt;td&gt;992,596&lt;/td&gt;
&lt;td&gt;14.1%&lt;/td&gt;
&lt;td&gt;46.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPEG q70&lt;/td&gt;
&lt;td&gt;746,185&lt;/td&gt;
&lt;td&gt;10.6%&lt;/td&gt;
&lt;td&gt;44.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP q90&lt;/td&gt;
&lt;td&gt;745,952&lt;/td&gt;
&lt;td&gt;10.6%&lt;/td&gt;
&lt;td&gt;47.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP q80&lt;/td&gt;
&lt;td&gt;321,938&lt;/td&gt;
&lt;td&gt;4.6%&lt;/td&gt;
&lt;td&gt;44.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP q70&lt;/td&gt;
&lt;td&gt;227,010&lt;/td&gt;
&lt;td&gt;3.2%&lt;/td&gt;
&lt;td&gt;43.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rows in there are the whole argument for WebP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JPEG q70   746,185 bytes   44.9 dB
WebP q80   321,938 bytes   44.9 dB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Identical PSNR, 2.3x smaller.&lt;/strong&gt; Not "WebP is roughly 25-30% better" — on this photo, at matched measured quality, it is less than half the size. Pick the WebP row by PSNR rather than by quality number, because the quality scales are not the same scale.&lt;/p&gt;

&lt;p&gt;Note the PNG row too. Storing an already-lossy photo losslessly costs 7MB to preserve JPEG artifacts perfectly. PNG is the right container for screenshots, logos and anything with text edges — not for photographs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 2: the quality slider
&lt;/h2&gt;

&lt;p&gt;Where does it stop paying? Same source, JPEG only:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;PSNR (dB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;4,228,908&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;55.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;1,953,547&lt;/td&gt;
&lt;td&gt;−54%&lt;/td&gt;
&lt;td&gt;51.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1,358,048&lt;/td&gt;
&lt;td&gt;−30%&lt;/td&gt;
&lt;td&gt;49.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;1,173,332&lt;/td&gt;
&lt;td&gt;−14%&lt;/td&gt;
&lt;td&gt;47.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;992,596&lt;/td&gt;
&lt;td&gt;−15%&lt;/td&gt;
&lt;td&gt;46.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;746,185&lt;/td&gt;
&lt;td&gt;−25%&lt;/td&gt;
&lt;td&gt;44.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;641,501&lt;/td&gt;
&lt;td&gt;−14%&lt;/td&gt;
&lt;td&gt;43.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;596,403&lt;/td&gt;
&lt;td&gt;−7%&lt;/td&gt;
&lt;td&gt;42.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Going 100 → 90 throws away 68% of the bytes and 6 dB. Going 60 → 50 throws away 7% of the bytes for another 0.4 dB. The bottom of the slider is where you pay in quality and stop being paid in bytes — the savings are already gone by the time you get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 3: dimensions — this is the one that wins
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;th&gt;Pixels&lt;/th&gt;
&lt;th&gt;JPEG q80&lt;/th&gt;
&lt;th&gt;WebP q80&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6400&lt;/td&gt;
&lt;td&gt;25,600,000&lt;/td&gt;
&lt;td&gt;992,596&lt;/td&gt;
&lt;td&gt;321,938&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3200&lt;/td&gt;
&lt;td&gt;6,400,000&lt;/td&gt;
&lt;td&gt;250,737&lt;/td&gt;
&lt;td&gt;97,554&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;td&gt;1,600,000&lt;/td&gt;
&lt;td&gt;74,902&lt;/td&gt;
&lt;td&gt;30,510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;625,000&lt;/td&gt;
&lt;td&gt;35,108&lt;/td&gt;
&lt;td&gt;14,246&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;400,000&lt;/td&gt;
&lt;td&gt;24,407&lt;/td&gt;
&lt;td&gt;9,960&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Halving the width quarters the pixel count, and the bytes roughly follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6400 -&amp;gt; 3200   992,596 -&amp;gt; 250,737   (25% of previous)
3200 -&amp;gt; 1600   250,737 -&amp;gt;  74,902   (30%)
1600 -&amp;gt;  800    74,902 -&amp;gt;  24,407   (33%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not exactly 25% each time, and the drift is in a consistent direction: the smaller the image, the less it shrinks per halving. Downscaling averages neighbouring pixels, so fine detail turns into per-pixel variation the encoder can no longer predict away — entropy per pixel goes up as pixel count goes down.&lt;/p&gt;

&lt;p&gt;Put the levers together and the format argument stops looking important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6400px PNG          7,058,497 bytes
1600px WebP q80        30,510 bytes     231x smaller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resizing did most of that. The best format trick available at full size (PNG → WebP q80) was a 22x cut; resizing to 1600px on top of it took another 10x. If your pipeline compresses hard but serves camera-resolution images, you are optimising the small lever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means in a pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Resize first, then encode.&lt;/strong&gt; Encoding a 25-megapixel image to throw the pixels away afterwards wastes both CPU and bytes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose a target width from the layout, not from the source.&lt;/strong&gt; A 1600px-wide image is enough for a 800px content column at 2x DPR. Everything past that is invisible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compare formats at matched PSNR, not matched quality numbers.&lt;/strong&gt; &lt;code&gt;quality=80&lt;/code&gt; means different things to different encoders, which is exactly how "WebP saves 25%" becomes folklore instead of measurement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the original.&lt;/strong&gt; Every number above is one-way. Re-encoding an already-compressed image compounds artifacts.&lt;/p&gt;

&lt;p&gt;Reproducing this is about ten lines:&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;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;

&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;nbytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tell&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;width&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LANCZOS&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="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;nbytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JPEG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;nbytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WEBP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The version for people who just want their photo to fit in an email, with the per-use-case settings: &lt;a href="https://www.coding-now.com/en/guides/reduce-image-file-size?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/reduce-image-file-size?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Curious what the matched-PSNR gap looks like on your images — the 2.3x here is one photo, and photos with large flat areas should favour WebP even harder.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The browser has no MP3 encoder, so a client-side audio trimmer writes WAV by hand</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:29:39 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/the-browser-has-no-mp3-encoder-so-a-client-side-audio-trimmer-writes-wav-by-hand-13gg</link>
      <guid>https://dev.to/_8729c5bde46be2/the-browser-has-no-mp3-encoder-so-a-client-side-audio-trimmer-writes-wav-by-hand-13gg</guid>
      <description>&lt;p&gt;I wanted a trimmer that cuts a clip out of a song without uploading the file anywhere. Decoding is easy — &lt;code&gt;decodeAudioData&lt;/code&gt; takes an ArrayBuffer of almost any format the browser can play and hands back a Float32 &lt;code&gt;AudioBuffer&lt;/code&gt;. Getting audio back &lt;em&gt;out&lt;/em&gt; is where the platform stops helping.&lt;/p&gt;

&lt;h2&gt;
  
  
  MediaRecorder will not give you MP3 (or WAV)
&lt;/h2&gt;

&lt;p&gt;I checked what Chrome 152 actually admits to:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/mpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/wav&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/aac&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/ogg;codecs=opus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/webm;codecs=opus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/mp4;codecs=mp4a.40.2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;types&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;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MediaRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isTypeSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)]));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;audio/mpeg&lt;/span&gt;                 &lt;span class="err"&gt;false&lt;/span&gt;
&lt;span class="err"&gt;audio/mp3&lt;/span&gt;                  &lt;span class="err"&gt;false&lt;/span&gt;
&lt;span class="err"&gt;audio/wav&lt;/span&gt;                  &lt;span class="err"&gt;false&lt;/span&gt;
&lt;span class="err"&gt;audio/aac&lt;/span&gt;                  &lt;span class="err"&gt;false&lt;/span&gt;
&lt;span class="err"&gt;audio/ogg;&lt;/span&gt;&lt;span class="py"&gt;codecs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;opus      false&lt;/span&gt;
&lt;span class="err"&gt;audio/webm;&lt;/span&gt;&lt;span class="py"&gt;codecs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;opus     true&lt;/span&gt;
&lt;span class="err"&gt;audio/mp4;&lt;/span&gt;&lt;span class="py"&gt;codecs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mp4a.40.2 true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the only containers on offer are WebM/Opus and MP4/AAC — neither of which is what someone expects to receive when they asked to "cut an MP3". MP3 output means shipping an encoder: lamejs, or ffmpeg.wasm at 25MB+. For a tool whose entire pitch is "nothing is uploaded and nothing is installed", that is a lot of weight for a format conversion nobody asked for.&lt;/p&gt;

&lt;h2&gt;
  
  
  So you write the WAV header yourself
&lt;/h2&gt;

&lt;p&gt;It's 44 bytes. This is the whole encoder from the trimmer I run:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encodeWAV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startSample&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endSample&lt;/span&gt;&lt;span class="p"&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;numCh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberOfChannels&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;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;endSample&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startSample&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;sampleRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sampleRate&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;blockAlign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numCh&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;dataSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;blockAlign&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;ab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dataSize&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;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DataView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ab&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;writeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RIFF&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dataSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;writeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WAVE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;writeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fmt &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// PCM&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;numCh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sampleRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sampleRate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;blockAlign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blockAlign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// bits per sample&lt;/span&gt;
  &lt;span class="nf"&gt;writeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUint32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dataSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// then interleave the channels as 16-bit LE from offset 44&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that matter more than they look:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slice by sample index, not by seconds.&lt;/strong&gt; The selection times become indices once — &lt;code&gt;Math.floor(selStart * sr)&lt;/code&gt; and &lt;code&gt;Math.floor(selEnd * sr)&lt;/code&gt;, clamped to &lt;code&gt;buffer.length&lt;/code&gt; — and everything downstream counts samples. Carrying seconds around and converting in two places is how you end up a sample off on one edge, which is audible as a click when a ringtone loops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;buffer.sampleRate&lt;/code&gt;, not the AudioContext's.&lt;/strong&gt; On the machine I tested, &lt;code&gt;new AudioContext().sampleRate&lt;/code&gt; is &lt;strong&gt;48000&lt;/strong&gt; while a typical MP3 decodes at 44100. If you render the selection through an AudioContext (or an &lt;code&gt;OfflineAudioContext&lt;/code&gt; created with the context's rate) you silently resample the whole clip for no reason. Read the samples straight out of the decoded buffer and copy them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The file size nobody expects
&lt;/h2&gt;

&lt;p&gt;PCM is uncompressed, so the arithmetic is the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;44 + 44100 × 30 × 2ch × 2bytes = 5,292,044
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I generated exactly that clip to check, and the file is 5,292,044 bytes — 5.0 MB for 30 seconds. The same 30 seconds as a 192kbps MP3 is about 0.7 MB. Users notice this the moment they try to move the file to a phone, so it's worth saying out loud in the UI rather than letting them discover it in a transfer dialog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the format actually bites: ringtones
&lt;/h2&gt;

&lt;p&gt;This is the use case that sends people to an audio trimmer in the first place, and the platform rules are asymmetric:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;What it accepts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;MP3, WAV (PCM), AAC/M4A, OGG, FLAC — per the platform's supported-formats table, so the WAV works as-is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS&lt;/td&gt;
&lt;td&gt;goes through GarageBand, and Apple's documentation caps ringtones at &lt;strong&gt;30 seconds&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So on Android the raw WAV is fine and the only complaint is the 5MB. On iOS the format matters less than the length — and if the clip is longer, the export offers to shorten it for you, choosing the part it keeps. Which means the length constraint belongs in the &lt;em&gt;trimmer&lt;/em&gt;, not in the phone: let people type exact start/end times and show the selection length while they drag.&lt;/p&gt;

&lt;p&gt;That last point is the actual design lesson here. The encoder question (&lt;code&gt;MediaRecorder&lt;/code&gt; versus a hand-written header) has one answer and you implement it once. The 30 seconds is what people get wrong every time, and only the UI can prevent it.&lt;/p&gt;




&lt;p&gt;The non-developer walkthrough — cutting the clip, moving it, and the GarageBand and Files-app steps on each platform: &lt;a href="https://www.coding-now.com/en/guides/make-ringtone-from-mp3?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/make-ringtone-from-mp3?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Has anyone shipped lamejs in production for this? I'd like to know what the bundle cost looked like in practice.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>audio</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Windows can decode (and encode) HEIC without ImageMagick - and the browser ships no decoder</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 23 Sep 2026 04:23:43 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/windows-can-decode-and-encode-heic-without-imagemagick-but-your-browser-cant-decode-it-at-all-51ca</link>
      <guid>https://dev.to/_8729c5bde46be2/windows-can-decode-and-encode-heic-without-imagemagick-but-your-browser-cant-decode-it-at-all-51ca</guid>
      <description>&lt;p&gt;Someone hands you a folder of iPhone photos, every file is &lt;code&gt;.heic&lt;/code&gt;, and the form you need to upload them to wants JPG. The usual advice is "install ImageMagick" or "use this website". Neither is necessary on Windows, and the website advice hides something worth knowing about browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renaming is not converting (the 10-second check)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 16 photo.heic | xxd
00000000: 0000 0020 6674 7970 6865 6963  ... ftypheic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy it to &lt;code&gt;photo.jpg&lt;/code&gt; and those bytes don't move. Windows agrees about what it really is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="s2"&gt;"C:\tmp\renamed.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CodecInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FriendlyName&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# -&amp;gt; Microsoft HEIF Decoder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Viewers sniff content, so the renamed file opens fine — right up until something validates the format.&lt;/p&gt;

&lt;h2&gt;
  
  
  The no-install route: WIC from PowerShell
&lt;/h2&gt;

&lt;p&gt;Windows has had a HEIF codec since the Store extensions shipped, and WIC exposes it to .NET, so a whole folder is a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Users\you\Pictures\*.heic"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frames&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="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Windows.Media.Imaging.JpegBitmapEncoder&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QualityLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Frames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ChangeExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.jpg'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine (Windows 11 25H2, build 26200.9457) that reported &lt;code&gt;Microsoft HEIF Decoder&lt;/code&gt;, handed back a &lt;code&gt;Bgr32&lt;/code&gt; frame, and turned a 75,897-byte HEIC into a 59,241-byte quality-90 JPEG.&lt;/p&gt;

&lt;p&gt;Two Store packages matter here: &lt;code&gt;Microsoft.HEIFImageExtension&lt;/code&gt; (1.2.48 here) is the free one, and because the picture inside a HEIC is HEVC-compressed, some machines also need &lt;code&gt;Microsoft.HEVCVideoExtension&lt;/code&gt; (2.5.33 here), which is a paid Store item unless your OEM shipped it. Check what you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-AppxPackage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HEIF|HEVC"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: Windows will also &lt;em&gt;write&lt;/em&gt; HEIC
&lt;/h2&gt;

&lt;p&gt;WPF has no HEIF encoder class, but WinRT does, and PowerShell can reach it. This is how I produced a real &lt;code&gt;.heic&lt;/code&gt; to test against without owning an iPhone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Runtime.WindowsRuntime&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# ... AsTask/Await helpers omitted ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$encId&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Graphics.Imaging.BitmapEncoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;HeifEncoderId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$encoder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AwaitOp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Graphics.Imaging.BitmapEncoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$encId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dstStream&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Windows.Graphics.Imaging.BitmapEncoder&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetSoftwareBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$bitmap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;AwaitAct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FlushAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 1000x380 PNG came back out as a 75,897-byte &lt;code&gt;.heic&lt;/code&gt; whose first bytes were &lt;code&gt;ftypheic&lt;/code&gt;. Useful for fixtures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprised me: browsers ship no HEIC decoder
&lt;/h2&gt;

&lt;p&gt;No browser decodes HEIC on its own. In Chrome 152:&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="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-photo.heic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// img.naturalWidth === 0, img.complete === true&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-photo.heic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// InvalidStateError: The source image could not be decoded.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Correction (2026-09-28):&lt;/strong&gt; a reader on r/PowerShell pointed out that this measures the &lt;em&gt;absence of a built-in decoder&lt;/em&gt;, not an impossibility — libheif compiled to WebAssembly decodes HEIC client-side, and &lt;code&gt;heic2any&lt;/code&gt; / &lt;code&gt;libheif-js&lt;/code&gt; are exactly that (2.6MB and 8.4MB unpacked). The reason converter sites upload is that download cost, not a browser limitation. Corrected here and in the original article.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;drawImage&lt;/code&gt; on that element throws &lt;code&gt;InvalidStateError&lt;/code&gt; too. So a site that converts HEIC either ships its own decoder or uploads your file to a server, and in practice nearly all of them upload. If the photos are of people or documents, that matters.&lt;/p&gt;

&lt;p&gt;(For contrast, the same page loaded a PNG at 1000x380 without complaint, so this is about the format, not the page.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Python: Pillow alone won't open it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-photo.heic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;UnidentifiedImageError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="n"&gt;identify&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test-photo.heic&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need the plugin that registers the opener:&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;import&lt;/span&gt; &lt;span class="n"&gt;pillow_heif&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&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="n"&gt;pillow_heif&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_heif_opener&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;src&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;.&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;*.heic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&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;.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the portable option — and the one to pick if you need EXIF handling or transparency rules you control, rather than whatever WIC's default frame gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A folder, nothing installed, Windows&lt;/td&gt;
&lt;td&gt;PowerShell + WIC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform or in a pipeline&lt;/td&gt;
&lt;td&gt;Pillow + &lt;code&gt;pillow-heif&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A test fixture, no iPhone&lt;/td&gt;
&lt;td&gt;WinRT &lt;code&gt;HeifEncoderId&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client-side in a browser&lt;/td&gt;
&lt;td&gt;Only with a bundled WASM decoder (heic2any 2.6MB, libheif-js 8.4MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photos of people or documents&lt;/td&gt;
&lt;td&gt;Anything except an upload-based site&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One more thing worth setting expectations on: the JPEG is usually &lt;strong&gt;bigger&lt;/strong&gt; than the HEIC, because HEIC is the more efficient format. Converting "to save space" is backwards; resize instead.&lt;/p&gt;

&lt;p&gt;Everything above was measured on Windows 11 25H2 with the HEIF and HEVC extensions installed — the codec name, the frame format, the byte counts, the Chrome errors and the Pillow failure are all from that run, not from memory.&lt;/p&gt;




&lt;p&gt;The non-developer version, including the iPhone setting that stops HEIC files appearing in the first place: &lt;a href="https://www.coding-now.com/en/guides/heic-to-jpg?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/heic-to-jpg?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Has anyone found a shipping browser that decodes HEIC, or a flag that enables it?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Converting WebP to JPG on Windows with no dependencies (and the alpha channel gotcha)</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Wed, 23 Sep 2026 01:53:39 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/converting-webp-to-jpg-on-windows-with-no-dependencies-and-the-alpha-channel-gotcha-1dp8</link>
      <guid>https://dev.to/_8729c5bde46be2/converting-webp-to-jpg-on-windows-with-no-dependencies-and-the-alpha-channel-gotcha-1dp8</guid>
      <description>&lt;p&gt;Someone sends you a folder of &lt;code&gt;.webp&lt;/code&gt; files and the upload form only takes JPG. You don't need ImageMagick for this: Windows has shipped a WebP decoder since 1809 (as the Store-delivered &lt;code&gt;Microsoft.WebpImageExtension&lt;/code&gt;), and WIC exposes it to PowerShell. But the built-in path has two sharp edges I only found by testing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: renaming is not converting
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 16 photo.webp | xxd
&lt;span class="go"&gt;00000000: 5249 4646 4e0a 0000 5745 4250 5650 3820  RIFF....WEBPVP8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rename it to &lt;code&gt;photo.jpg&lt;/code&gt; and those bytes don't change. Most viewers sniff the content and open it anyway, which is exactly why people think the rename worked - until something that actually validates the format rejects it. Python agrees about what it really is:&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;renamed.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;WEBP&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The no-install route: WIC via PowerShell
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Pictures\image.webp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$frame&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frames&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="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Windows.Media.Imaging.JpegBitmapEncoder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QualityLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Frames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ChangeExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.jpg'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$enc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BitmapDecoder.Create&lt;/code&gt; picks the codec by content, and on a machine with the WebP extension you can confirm which one it chose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$dec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$dec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CodecInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FriendlyName&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# -&amp;gt; Microsoft Webp Decoder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A whole folder is the same thing in a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Pictures\*.webp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Windows.Media.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OnLoad'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frames&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="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Windows.Media.Imaging.JpegBitmapEncoder&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QualityLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Frames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ChangeExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.jpg'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotcha 1: the alpha channel is gone
&lt;/h2&gt;

&lt;p&gt;This is the part worth knowing before you run it over a folder of logos. The frame that comes back from the Microsoft WebP decoder is &lt;code&gt;Bgr32&lt;/code&gt; - no alpha. Transparent pixels arrive &lt;strong&gt;black&lt;/strong&gt;, and because the data is already flattened, switching to &lt;code&gt;PngBitmapEncoder&lt;/code&gt; doesn't save you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c"&gt;# -&amp;gt; Bgr32&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# transparent WebP -&amp;gt; PNG, then inspect the corner pixel:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#   (0, 0, 0, 255)     # opaque black, not transparent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pillow reads the same file as &lt;code&gt;RGBA&lt;/code&gt;, so if transparency matters, do it in Python and decide what goes behind it:&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;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;src&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;.&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;*.webp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;       &lt;span class="c1"&gt;# composite onto white instead of black
&lt;/span&gt;        &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;bg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paste&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bg&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&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;.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotcha 2: animated WebP silently loses everything after frame 1
&lt;/h2&gt;

&lt;p&gt;The decoder does expose the frames - &lt;code&gt;$dec.Frames.Count&lt;/code&gt; returned 4 for my test file, and Pillow reported &lt;code&gt;n_frames = 4&lt;/code&gt; for the same one. But &lt;code&gt;Frames[0]&lt;/code&gt; is what the snippet above encodes, and JPEG has nowhere to put the rest. If the motion matters, GIF is two lines:&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="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anim.webp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anim.gif&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_all&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  And it gets bigger
&lt;/h2&gt;

&lt;p&gt;WebP -&amp;gt; JPG is a re-encode of already-lossy data, so you pay twice: a little more generation loss, and usually a larger file. My 2.6 KB test WebP became 7.4 KB as a quality-90 JPEG. That tracks with Google's own numbers - lossy WebP is 25-34% smaller than JPEG at equivalent SSIM, lossless is 26% smaller than PNG - so converting "to save space" is backwards. Keep the original, and if size is the goal, resize instead of re-encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One or two files, no install&lt;/td&gt;
&lt;td&gt;Any browser-based converter that runs client-side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A folder, nothing installed&lt;/td&gt;
&lt;td&gt;PowerShell + WIC (above)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transparency preserved or flattened your way&lt;/td&gt;
&lt;td&gt;Pillow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animation kept&lt;/td&gt;
&lt;td&gt;Pillow -&amp;gt; GIF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything above was run on Windows 11 with &lt;code&gt;Microsoft.WebpImageExtension&lt;/code&gt; 1.2.31: the codec name, the &lt;code&gt;Bgr32&lt;/code&gt; frame format, the black corner pixel, the frame counts and the file sizes are measured, not assumed.&lt;/p&gt;




&lt;p&gt;Longer version with the non-developer paths (Paint, Photos, a drag-and-drop converter that doesn't upload): &lt;a href="https://www.coding-now.com/en/guides/webp-to-jpg?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/webp-to-jpg?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Has the alpha-drop bitten anyone else, or is there a WIC flag I missed?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Transparent PNGs: the 3 background-removal algorithms, why JPG can't do it, and the color-key gotcha</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Tue, 22 Sep 2026 01:39:12 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/transparent-pngs-the-3-background-removal-algorithms-why-jpg-cant-do-it-and-the-color-key-gotcha-3gl6</link>
      <guid>https://dev.to/_8729c5bde46be2/transparent-pngs-the-3-background-removal-algorithms-why-jpg-cant-do-it-and-the-color-key-gotcha-3gl6</guid>
      <description>&lt;p&gt;Every "make this background transparent" tool is doing one of three things under the hood, and picking the wrong one for your picture is why a logo comes back with a hole in it, or the background is only half gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transparency lives in the alpha channel
&lt;/h2&gt;

&lt;p&gt;A pixel needs a fourth value beyond R, G, B: alpha, how see-through it is. Only formats that define an alpha channel can store it - PNG and WebP per-pixel, GIF as one fully-transparent color, never JPEG.&lt;/p&gt;

&lt;p&gt;This isn't an implementation detail, it's in the spec. The HTML Living Standard says it outright for canvas export:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For image types that do not support an alpha component, the serialized image must be the bitmap image composited onto an opaque black background using the source-over compositing operator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So &lt;code&gt;canvas.toBlob(cb, 'image/jpeg')&lt;/code&gt; on a transparent canvas doesn't warn you, it silently paints black behind everything and hands you that. Pillow is less quiet about it - trying to save an RGBA image as JPEG raises &lt;code&gt;OSError: cannot write mode RGBA as JPEG&lt;/code&gt; outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three removal methods
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI subject detection&lt;/strong&gt; - segments the main subject (person, product, animal) from everything else. Handles busy backgrounds, one click, but check fine edges (hair, glass) before you trust it. This is what Windows 11 Paint's Remove Background button and the Photos app do (Paint since the update Microsoft announced in Sept 2023, version 11.2306.30).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connected area (flood fill / magic wand)&lt;/strong&gt; - starts at a point you click and grows outward through similar, touching colors. GIMP's Fuzzy Select and macOS Preview's Instant Alpha both work this way. If the background is split into several disconnected regions by the subject, you click each region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Color key&lt;/strong&gt; - removes every pixel close to one chosen color, anywhere in the image. GIMP's Color to Alpha and PowerPoint's Set Transparent Color use this. Fast on a flat background, but if that color also shows up &lt;em&gt;inside&lt;/em&gt; the subject, it goes transparent too.&lt;/p&gt;

&lt;p&gt;I actually ran both non-AI methods on the same test image with Pillow - a blue circle logo on white, with a white square cut out in the middle (standing in for white text or a hole in a badge):&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;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;

&lt;span class="c1"&gt;# color key: kill every near-white pixel in the whole image
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;color_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&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;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&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;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&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;&amp;lt;=&lt;/span&gt;&lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;

&lt;span class="c1"&gt;# connected area: flood fill from a known-background pixel
&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logo.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floodfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&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="n"&gt;thresh&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Color key made the inner white square transparent along with the background - a hole in the logo. Flood-fill from the corner only removed the background that's actually connected to it, leaving the inner square intact. Same tolerance, same image, opposite result, because they're answering different questions ("is this pixel near white?" vs "is this pixel reachable from here through near-white pixels?").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; if the background color repeats inside the subject (white lettering on a white-background logo, a stamp with a white rim), color key gives you a hole - use flood fill or AI instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it in bulk: rembg
&lt;/h2&gt;

&lt;p&gt;For more than a couple of images, &lt;a href="https://github.com/danielgatis/rembg" rel="noopener noreferrer"&gt;rembg&lt;/a&gt; (MIT license, Python 3.11-3.13) wraps several AI segmentation models behind one CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"rembg[cpu,cli]"&lt;/span&gt;

rembg i photo.jpg photo-cutout.png        &lt;span class="c"&gt;# one file&lt;/span&gt;
rembg p input_folder output_folder        &lt;span class="c"&gt;# a whole folder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It downloads the model on first use (cached under &lt;code&gt;~/.rembg/models/&lt;/code&gt;) and runs locally after that - nothing gets uploaded, which matters if the images aren't meant to leave your machine. The many online "remove.bg"-style tools are AI too, but the file goes to their server; fine for anything you'd publish anyway, not for scanned IDs or internal docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the browser, without a server round-trip
&lt;/h2&gt;

&lt;p&gt;If you're building this into a web page rather than a script, the color-key approach is a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; pixel loop plus a distance check - no upload, no dependency:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;knockoutBackground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;tolerance&lt;/span&gt;&lt;span class="p"&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&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;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&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;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&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;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;);&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;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;d&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;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&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;imageData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use this approach (plus a small feather at the tolerance edge, to soften jagged outlines) in a small &lt;a href="https://www.coding-now.com/en/image-converter?utm_source=devto" rel="noopener noreferrer"&gt;image converter tool&lt;/a&gt; - drop an image, eyedropper the background, adjust tolerance, export as PNG or WebP. Same color-key limitation applies: same-color pixels inside the subject go transparent too.&lt;/p&gt;




&lt;p&gt;Full write-up with the side-by-side comparison image, Paint/Photos/PowerPoint/Preview steps, and a troubleshooting list: &lt;a href="https://www.coding-now.com/en/guides/transparent-background-png?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/transparent-background-png?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's your go-to for batch background removal - rembg, something cloud-based, or hand-rolled?&lt;/p&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>No HyperTerminal on Windows 10/11? Free serial terminals and the 4 settings that matter</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:59:17 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/no-hyperterminal-on-windows-1011-free-serial-terminals-and-the-4-settings-that-matter-19oj</link>
      <guid>https://dev.to/_8729c5bde46be2/no-hyperterminal-on-windows-1011-free-serial-terminals-and-the-4-settings-that-matter-19oj</guid>
      <description>&lt;p&gt;Old equipment manuals still say "connect with HyperTerminal". On Windows 10 or 11 you won't find it: according to Hilgraeve, who wrote it and licensed it to Microsoft, it stopped shipping with Windows Vista and 7. Hilgraeve still sells HyperTerminal Private Edition ($69.99 as of September 2026, free trial), but free tools cover everything it did.&lt;/p&gt;

&lt;p&gt;And no, Windows Terminal doesn't do serial. The GitHub request for it (microsoft/terminal #1280) has been open since 2019 and sits in the Icebox milestone.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Find the COM port
&lt;/h2&gt;

&lt;p&gt;Device Manager -&amp;gt; &lt;strong&gt;Ports (COM &amp;amp; LPT)&lt;/strong&gt;. Unplug and replug the device; the entry that disappears and returns is yours. Or ask PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.Ports.SerialPort&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;GetPortNames&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-PnpDevice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PresentOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FriendlyName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No port at all? Install the adapter chip's driver (CH340, CP210x, FTDI) and rule out a charge-only USB cable.&lt;/p&gt;

&lt;p&gt;A big number like COM34 is harmless. On the PC I wrote this on, nine unplugged port devices still carried COM3 to COM20, while the board plugged in right now was on COM34. One catch: through the Win32 API, ports above 9 must be opened as &lt;code&gt;\\.\COM10&lt;/code&gt; (per the CreateFile docs), and some old programs never learned that.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The four settings
&lt;/h2&gt;

&lt;p&gt;A serial line has no negotiation. Both ends assume the same rules, and if one differs the port still opens but the output is wrong.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Usual value&lt;/th&gt;
&lt;th&gt;Symptom when wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speed (baud)&lt;/td&gt;
&lt;td&gt;9600 or 115200&lt;/td&gt;
&lt;td&gt;A stream of unreadable characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data format&lt;/td&gt;
&lt;td&gt;8N1&lt;/td&gt;
&lt;td&gt;Only some characters come out wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flow control&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Sending stalls, or certain bytes vanish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Line ending&lt;/td&gt;
&lt;td&gt;CR, LF or CR+LF&lt;/td&gt;
&lt;td&gt;Commands get no reply&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;8N1 is 8 data bits, no parity, 1 stop bit, and it's what Arduino's &lt;code&gt;Serial.begin()&lt;/code&gt; uses unless told otherwise. 7E1 (7 data bits, even parity) is the same length on the wire, which is why the speed can be right and some characters still turn into others. You'll meet it in things like Modbus ASCII.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PuTTY gotcha:&lt;/strong&gt; its default serial flow control is XON/XOFF (per the defaults in its source). XON/XOFF sends 0x11 and 0x13 inside the data stream, so binary data containing those bytes gets eaten as flow control. Set Connection &amp;gt; Serial &amp;gt; Flow control to None unless your device really uses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The free programs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PuTTY&lt;/strong&gt; - Session &amp;gt; Connection type: Serial, Serial line &lt;code&gt;COM3&lt;/code&gt;, Speed. Data bits, parity and flow control live under Connection &amp;gt; Serial. Blank window? Press Enter a few times; some devices wait for the PC to speak first. Shortcut-friendly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;putty&lt;/span&gt;&lt;span class="err"&gt;.exe&lt;/span&gt; &lt;span class="na"&gt;-serial &lt;/span&gt;&lt;span class="kd"&gt;COM3&lt;/span&gt; &lt;span class="na"&gt;-sercfg &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;n&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kd"&gt;N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same options work with &lt;code&gt;plink&lt;/code&gt;, so a Windows Terminal profile running &lt;code&gt;plink.exe -serial COM3 -sercfg 115200,8,n,1,N&lt;/code&gt; turns a tab into a serial console (syntax per the PuTTY manual).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tera Term&lt;/strong&gt; - the closest thing to HyperTerminal, including XMODEM/YMODEM/ZMODEM/Kermit file transfer under File &amp;gt; Transfer. File &amp;gt; New connection &amp;gt; Serial, then Setup &amp;gt; Serial port for the line settings. You can't switch ports from that dialog while connected; disconnect first. 5.7.0 (Sept 2026) ships as an installer and as a plain ZIP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RealTerm&lt;/strong&gt; - open source, built for binary streams: hex and 8/16/32-bit views, timestamped capture. Port tab for speed and port.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CNTerminal&lt;/strong&gt; (my tool, open source) - one 8 MB exe, no install: hex send/receive, an ASCII/hex converter, a preview of the exact bytes before sending, and a DTR toggle so an ESP32 doesn't reset on connect. Honest limit: it's fixed at 8N1 with no flow control, so use Tera Term for 7E1 gear or XMODEM.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Nothing installable? PowerShell
&lt;/h2&gt;

&lt;p&gt;Windows PowerShell 5.1 ships with Windows and can use .NET's SerialPort directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.IO.Ports.SerialPort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'COM3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'None'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'One'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadTimeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c"&gt;# default line ending is LF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'no reply'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defaults I checked: 9600, 8N1, no handshake, DTR and RTS both off. The open errors tell you what's wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Access to the port 'COM3' is denied.&lt;/code&gt; - another program (Arduino serial monitor, another terminal, an upload tool) already has it open.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;The port 'COM99' does not exist.&lt;/code&gt; - wrong number, or the device is unplugged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;mode COM3&lt;/code&gt; in a command prompt prints the port's current speed, parity, data bits and DTR/RTS state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist when nothing shows up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Port not listed: driver, cable, another USB socket.&lt;/li&gt;
&lt;li&gt;Port won't open: close whatever else holds it.&lt;/li&gt;
&lt;li&gt;Garbled text: fix the speed; if only some characters are off, check 8N1 vs 7E1.&lt;/li&gt;
&lt;li&gt;No reply: try CR, LF and CR+LF. Can't see what you type? The device doesn't echo; turn on local echo.&lt;/li&gt;
&lt;li&gt;Board reboots on connect: that's the DTR/RTS auto-reset circuit on ESP32 and Arduino boards.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The full guide, with diagrams and a side-by-side comparison table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/hyperterminal-alternative?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/hyperterminal-alternative?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNTerminal (free, no install):&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/cnterminal?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/cnterminal?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What are you still using HyperTerminal-era manuals for?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>embedded</category>
      <category>arduino</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Text to speech on Windows: the built-in voices, edge-tts, and why subtitle dubbing never fits</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Mon, 21 Sep 2026 01:02:18 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/text-to-speech-on-windows-the-built-in-voices-edge-tts-and-why-subtitle-dubbing-never-fits-4o6l</link>
      <guid>https://dev.to/_8729c5bde46be2/text-to-speech-on-windows-the-built-in-voices-edge-tts-and-why-subtitle-dubbing-never-fits-4o6l</guid>
      <description>&lt;p&gt;Narration for a video, a spoken prompt in an app, a subtitle file turned into audio: all of it starts with getting text read out and saved to a file. Windows can do it with what's already installed, and the neural voices are a &lt;code&gt;pip install&lt;/code&gt; away. Here's what each route costs you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The voices already in Windows
&lt;/h2&gt;

&lt;p&gt;Narrator and Edge's Read Aloud use the built-in speech engine, but neither will save what it reads. &lt;code&gt;System.Speech&lt;/code&gt; will:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Speech&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Speech.Synthesis.SpeechSynthesizer&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# What's available&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstalledVoices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VoiceInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" ("&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VoiceInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Culture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;")"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectVoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Microsoft Zira Desktop"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="c"&gt;# -10 .. 10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetOutputToWaveFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"C:\temp\narration.wav"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Speak&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\temp\script.txt"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Encoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No install, no network, and it writes a WAV. The delivery is unmistakably synthetic, so it suits prompts and beeps more than narration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha: added voices that never show up
&lt;/h2&gt;

&lt;p&gt;Add a voice under Settings &amp;gt; Time &amp;amp; language &amp;gt; Speech and it often doesn't appear in &lt;code&gt;GetInstalledVoices()&lt;/code&gt;. The two generations of voices live in different registry hives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SAPI 5 - what System.Speech reads&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'HKLM:\SOFTWARE\Microsoft\Speech\Voices\Tokens'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# OneCore - what Settings installs, and Narrator uses&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'HKLM:\SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Windows 11 (25H2) machine I checked, the SAPI hive had two voices and the OneCore hive had a third one that &lt;code&gt;System.Speech&lt;/code&gt; could never select. If a voice you installed isn't in the list, this is why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neural voices from the command line
&lt;/h2&gt;

&lt;p&gt;The voices Edge reads pages with are neural, and &lt;code&gt;edge-tts&lt;/code&gt; drives them directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;edge-tts

&lt;span class="c"&gt;# text -&amp;gt; MP3, with a matching .srt&lt;/span&gt;
edge-tts &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; hello.mp3 &lt;span class="nt"&gt;--write-subtitles&lt;/span&gt; hello.srt

edge-tts &lt;span class="nt"&gt;--list-voices&lt;/span&gt;

&lt;span class="c"&gt;# a script file, in a chosen voice&lt;/span&gt;
edge-tts &lt;span class="nt"&gt;--voice&lt;/span&gt; en-US-AriaNeural &lt;span class="nt"&gt;--file&lt;/span&gt; script.txt &lt;span class="nt"&gt;--write-media&lt;/span&gt; narration.mp3

edge-tts &lt;span class="nt"&gt;--rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;% &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; slower.mp3
edge-tts &lt;span class="nt"&gt;--volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;% &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; quieter.mp3
edge-tts &lt;span class="nt"&gt;--pitch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50Hz&lt;/span&gt; &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; &lt;span class="nt"&gt;--write-media&lt;/span&gt; lower.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Options are from the project's README and CLI help. No API key, no account, and it writes MP3 rather than WAV, which matters once a batch of scripts piles up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dubbing subtitles: the lengths never match
&lt;/h2&gt;

&lt;p&gt;A subtitle file states exactly how long each line is on screen. Read that line aloud and it is nearly always longer, because people skim subtitles while a synthesizer pronounces every syllable. Translate first and the gap widens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cue  1  00:00:00,000 --&amp;gt; 00:00:03,500   (3.5s)   speech 3.2s   fits
cue  2  00:00:03,500 --&amp;gt; 00:00:07,000   (3.5s)   speech 4.6s   overruns into cue 3
cue  3  00:00:07,000 --&amp;gt; 00:00:12,000   (5.0s)   speech 4.4s   fits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What actually works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raise the rate. 10-20% faster still sounds natural.&lt;/li&gt;
&lt;li&gt;Shorten the line. The narration doesn't have to match the subtitle word for word.&lt;/li&gt;
&lt;li&gt;Merge two short cues into one sentence, which buys room either side.&lt;/li&gt;
&lt;li&gt;If it has to be frame-accurate, cut per cue in an editor instead of fighting the timings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Making something new rather than dubbing? Generate the narration first and cut the visuals to its length. Then nothing has to be squeezed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part people skip: licensing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ElevenLabs free tier: 10k credits a month, and &lt;strong&gt;no commercial licence&lt;/strong&gt; on the free plan (paid starts at $6/month).&lt;/li&gt;
&lt;li&gt;Naver Clova Dubbing free tier: monthly download and character limits, attribution required, ads and promotion need a paid plan.&lt;/li&gt;
&lt;li&gt;The Edge neural voices have no quota through &lt;code&gt;edge-tts&lt;/code&gt;, but they are Microsoft's service, so read the terms before shipping commercial work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Figures are from September 2026 and these terms change often. "Free to generate, but not for monetised content" is a common combination, and it's the sort of thing that surfaces after the video is published.&lt;/p&gt;




&lt;p&gt;The full version, with diagrams and a GUI option for people who don't want a terminal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/text-to-speech-mp3?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/text-to-speech-mp3?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which route do you use for narration?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Windows Task Scheduler: why your scheduled task doesn't run (and what 0x41303 means)</title>
      <dc:creator>코딩나우(하늘아래)</dc:creator>
      <pubDate>Fri, 18 Sep 2026 01:05:40 +0000</pubDate>
      <link>https://dev.to/_8729c5bde46be2/windows-task-scheduler-why-your-scheduled-task-doesnt-run-and-what-0x41303-means-2iep</link>
      <guid>https://dev.to/_8729c5bde46be2/windows-task-scheduler-why-your-scheduled-task-doesnt-run-and-what-0x41303-means-2iep</guid>
      <description>&lt;p&gt;Creating a scheduled task in Windows takes five minutes. Then the time comes and nothing happens, or the program starts but you never see its window. Almost always it's one of a few defaults. The defaults below come from &lt;code&gt;New-ScheduledTaskSettingsSet&lt;/code&gt;, and the result codes from Microsoft's Task Scheduler constants.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four gates
&lt;/h2&gt;

&lt;p&gt;A task has to get through four gates before anything visible happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Trigger           -&amp;gt; 0x41303: the task has not yet run
2. Conditions        -&amp;gt; on battery: doesn't start (default)
3. Security options  -&amp;gt; "whether user is logged on or not": no window
4. Action            -&amp;gt; 0x80070002: file not found, 0x1: the program's exit code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Status and Last Run Result columns and the History tab tell you which gate stopped it. If History is empty, click &lt;strong&gt;Enable All Tasks History&lt;/strong&gt; in the Actions pane. The same events are in Event Viewer under Applications and Services Logs &amp;gt; Microsoft &amp;gt; Windows &amp;gt; TaskScheduler &amp;gt; Operational.&lt;/p&gt;

&lt;h2&gt;
  
  
  No window: the security option
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Run whether user is logged on or not&lt;/strong&gt; runs the task in session 0, not on your desktop. A GUI program still starts but never shows up, and the task sits at &lt;code&gt;0x41301&lt;/code&gt; (currently running). The default for an already running task is &lt;em&gt;Do not start a new instance&lt;/em&gt;, so the next scheduled run is skipped too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programs you need to see: &lt;strong&gt;Run only when user is logged on&lt;/strong&gt;, the default for Create Basic Task.&lt;/li&gt;
&lt;li&gt;Scripts that need no window: &lt;em&gt;whether logged on or not&lt;/em&gt;, which also stops the console window flashing. You have to save the password. &lt;em&gt;Do not store password&lt;/em&gt; uses an S4U logon, which per Microsoft's docs has no access to the network or encrypted files, so skip it for backups to a NAS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Only fails on a laptop: the power conditions
&lt;/h2&gt;

&lt;p&gt;Two defaults on the Conditions tab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start the task only if the computer is on AC power&lt;/strong&gt;: on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop if the computer switches to battery power&lt;/strong&gt;: on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clear both for tasks that should run on battery.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other defaults
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;New-ScheduledTaskSettingsSet&lt;/code&gt; with no arguments shows them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DisallowStartIfOnBatteries : True
StopIfGoingOnBatteries     : True
StartWhenAvailable         : False   &amp;lt;- runs missed while the PC was off are skipped
WakeToRun                  : False
ExecutionTimeLimit         : PT72H   &amp;lt;- stopped after 3 days
MultipleInstances          : IgnoreNew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 3-day limit bites anything meant to stay open, like trading apps or servers. And an empty &lt;strong&gt;Start in (optional)&lt;/strong&gt; makes the task run from &lt;code&gt;C:\Windows\System32&lt;/code&gt;, so a program that loads its config from a relative path fails only when scheduled. For a batch file, &lt;code&gt;cd /d "%~dp0"&lt;/code&gt; on the first line fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Last Run Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Success (exit code 0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x41301&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The task is currently running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x41303&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The task has not yet run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x80070002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The system cannot find the file specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x800710E0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The operator or administrator has refused the request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;0x1&lt;/code&gt;, &lt;code&gt;0x2&lt;/code&gt; ...&lt;/td&gt;
&lt;td&gt;Usually the program's own exit code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;0x41303&lt;/code&gt; isn't a fault. Plenty of built-in tasks only run in specific situations: on the PC I checked, 76 of about 200 tasks showed it. For a batch file that ends with &lt;code&gt;0x1&lt;/code&gt;, append &lt;code&gt;&amp;gt;&amp;gt; log.txt 2&amp;gt;&amp;amp;1&lt;/code&gt; to its commands to see which one failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating it from the command line
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;schtasks&lt;/code&gt; has no Start in option, so if you need one, use PowerShell and fix the defaults while you're at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-ScheduledTaskAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Tools\report.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WorkingDirectory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Tools"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$trigger&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-ScheduledTaskTrigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Daily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-At&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;9am&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-ScheduledTaskSettingsSet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AllowStartIfOnBatteries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DontStopIfGoingOnBatteries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartWhenAvailable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionTimeLimit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Register-ScheduledTask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TaskName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Morning report"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For quick one-liners &lt;code&gt;schtasks&lt;/code&gt; is fine. Paths with spaces need an inner pair of single quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;schtasks&lt;/span&gt; &lt;span class="na"&gt;/create /tn &lt;/span&gt;&lt;span class="s2"&gt;"Report"&lt;/span&gt; &lt;span class="na"&gt;/tr &lt;/span&gt;&lt;span class="s2"&gt;"'C:\Program Files\Report\report.exe' --quiet"&lt;/span&gt; &lt;span class="na"&gt;/sc &lt;/span&gt;&lt;span class="kd"&gt;daily&lt;/span&gt; &lt;span class="na"&gt;/st &lt;/span&gt;&lt;span class="m"&gt;07&lt;/span&gt;:00
&lt;span class="nb"&gt;schtasks&lt;/span&gt; &lt;span class="na"&gt;/run /tn &lt;/span&gt;&lt;span class="s2"&gt;"Report"&lt;/span&gt;
&lt;span class="nb"&gt;schtasks&lt;/span&gt; &lt;span class="na"&gt;/query /tn &lt;/span&gt;&lt;span class="s2"&gt;"Report"&lt;/span&gt; &lt;span class="na"&gt;/v /fo &lt;/span&gt;&lt;span class="kd"&gt;list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The full guide, with diagrams and the Create Basic Task walkthrough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://www.coding-now.com/en/guides/windows-task-scheduler?utm_source=devto" rel="noopener noreferrer"&gt;https://www.coding-now.com/en/guides/windows-task-scheduler?utm_source=devto&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which default has bitten you?&lt;/p&gt;

</description>
      <category>windows</category>
      <category>powershell</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
