<?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: Technorology</title>
    <description>The latest articles on DEV Community by Technorology (@technorology).</description>
    <link>https://dev.to/technorology</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%2F4146008%2F23b5a0b7-727b-41b9-b891-dd265787fecd.jpg</url>
      <title>DEV Community: Technorology</title>
      <link>https://dev.to/technorology</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/technorology"/>
    <language>en</language>
    <item>
      <title>Removing an Audio Track From a Video Without Re-Encoding</title>
      <dc:creator>Technorology</dc:creator>
      <pubDate>Sun, 27 Sep 2026 21:14:01 +0000</pubDate>
      <link>https://dev.to/technorology/removing-an-audio-track-from-a-video-without-re-encoding-295l</link>
      <guid>https://dev.to/technorology/removing-an-audio-track-from-a-video-without-re-encoding-295l</guid>
      <description>&lt;p&gt;If you have a video file and you want the same video without sound, the correct operation is not a re-encode. It is a container-level track drop. The video stream stays byte-for-byte identical, the audio stream is discarded, and the operation runs at disk speed.&lt;/p&gt;

&lt;p&gt;Most "mute video" tools re-encode the video anyway. That is slower and lossy, and it is unnecessary for any MP4, MOV, MKV, or similar container. Here is what the operation actually looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;A modern video container holds streams. An MP4 might carry a video stream, a stereo audio stream, and a metadata track. Each stream is independent inside the container; the container's &lt;code&gt;moov&lt;/code&gt; atom (or Matroska equivalent) references each one separately.&lt;/p&gt;

&lt;p&gt;Removing audio means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the container's stream table.&lt;/li&gt;
&lt;li&gt;Skip the audio streams when writing the output.&lt;/li&gt;
&lt;li&gt;Copy the video stream's samples through unchanged.&lt;/li&gt;
&lt;li&gt;Write a new container with only the video stream registered.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing decodes. Nothing re-encodes. The output video is bit-identical to the input at the sample level.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ffmpeg one-liner
&lt;/h2&gt;

&lt;p&gt;On the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-an&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-c copy&lt;/code&gt; copies every stream through without re-encoding, and &lt;code&gt;-an&lt;/code&gt; drops the audio. Runs at whatever your disk can read and write; for a two-hour movie on an SSD, about ten seconds.&lt;/p&gt;

&lt;p&gt;The common mistake is dropping the &lt;code&gt;-c copy&lt;/code&gt; flag. Without it, ffmpeg defaults to re-encoding, which takes minutes and loses quality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Wrong: takes 30 minutes for a movie and produces a worse file&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-an&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The browser equivalent
&lt;/h2&gt;

&lt;p&gt;ffmpeg-wasm ships the same behavior for browser use:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FFmpeg&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ffmpeg/ffmpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fetchFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ffmpeg/util&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;ffmpeg&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;FFmpeg&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&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;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&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;fetchFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputFile&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-i&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;input.mp4&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;-c&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;copy&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;-an&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;output.mp4&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;data&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;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;output.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same flags, same behavior. Runs entirely in the tab through WebAssembly, so the video never leaves the machine. On a modern laptop the operation is bounded by disk IO, not CPU, so it finishes in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does not work
&lt;/h2&gt;

&lt;p&gt;Some tools try to mute by setting the audio track's volume to zero, or by writing silence over the audio samples. Both are wasteful: the audio track is still in the file, it still takes up space, and any player will still allocate a decode buffer for it. Muting is not the same as removing.&lt;/p&gt;

&lt;p&gt;Some tools decode the audio to silence and re-encode it. Same problem: the file still has an audio track that is doing nothing.&lt;/p&gt;

&lt;p&gt;The right operation is a proper track drop at the container level.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you cannot use &lt;code&gt;-c copy&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If your source is a live stream (HLS, RTMP) or a partial file, &lt;code&gt;-c copy&lt;/code&gt; may need the container's index to be fully written before it can copy through. In that case, either wait for the recording to finish, or accept a real re-encode for the muted output.&lt;/p&gt;

&lt;p&gt;If you need to remove audio from only part of the video (mute the first ten seconds, keep audio for the rest), that is a re-encode of the whole file. There is no way to selectively drop samples without re-encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-click version
&lt;/h2&gt;

&lt;p&gt;If you do not want to install ffmpeg locally, &lt;a href="https://wavemp4.com/remove-audio-from-video/" rel="noopener noreferrer"&gt;Wave's remove-audio-from-video tool&lt;/a&gt; does the same operation in the browser. Drop the video, download an MP4 without the audio track. The file stays on your machine, and the operation is a real track drop, not a re-encode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Removing audio from a video is a container operation, not a codec operation. Use &lt;code&gt;-c copy -an&lt;/code&gt; on the command line or the browser tool that runs the same flags. Any tool that takes minutes to do this is doing the wrong operation, and the output will be worse than the input for no reason.&lt;/p&gt;

</description>
      <category>software</category>
      <category>tools</category>
    </item>
    <item>
      <title>Doing PDF Redaction Right: Why a Black Rectangle Is Not Enough</title>
      <dc:creator>Technorology</dc:creator>
      <pubDate>Sun, 27 Sep 2026 21:11:40 +0000</pubDate>
      <link>https://dev.to/technorology/doing-pdf-redaction-right-why-a-black-rectangle-is-not-enough-2nbn</link>
      <guid>https://dev.to/technorology/doing-pdf-redaction-right-why-a-black-rectangle-is-not-enough-2nbn</guid>
      <description>&lt;p&gt;If you have ever received a "redacted" PDF where the sensitive text is behind a black rectangle, and you copied it out anyway, you already know the problem. Drawing a shape over text does not remove the text. The bytes are still in the file. Ctrl+F still finds them. Copy and paste still pastes them.&lt;/p&gt;

&lt;p&gt;Doing redaction correctly is not hard, but the two common approaches (annotation overlays and text replacement) both have failure modes worth understanding before you ship a tool or trust an output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three approaches
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Draw a black rectangle (do not do this)
&lt;/h3&gt;

&lt;p&gt;This is what most quick tools and most home-grown scripts do. They add an annotation or a drawing object on top of the page at the coordinates of the sensitive text. Visually it looks redacted. The text objects underneath are untouched.&lt;/p&gt;

&lt;p&gt;Extracting is trivial:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getDocument&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdfjs-dist&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;pdf&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;getDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redacted.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;promise&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;page&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;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPage&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textContent&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTextContent&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;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&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;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// prints the "redacted" text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every PDF library on earth will do this. So will Preview, so will Acrobat's built-in text selection, so will Ctrl+A / Ctrl+C. If your redacted PDF failed this test, it was decorative, not real.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Text substitution (better, still fragile)
&lt;/h3&gt;

&lt;p&gt;Some tools locate the target text in the content stream and replace those bytes with an equal number of "X" characters or spaces. Ctrl+F now fails, and copy-paste returns garbage. This is closer to real redaction, but it has three failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kerning tables leak the original.&lt;/strong&gt; If the target was "SSN 123-45-6789" and it is replaced with 15 X's, the character widths of the original are still baked into the Tj operator's positioning. A determined reader can reconstruct the original text from the width sequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ligatures and non-ASCII.&lt;/strong&gt; Text runs that used a ligature (fi, ffi) or non-Latin characters may not be substitutable byte-for-byte, and the tool either fails silently or corrupts the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XObject forms and referenced content.&lt;/strong&gt; If the sensitive text lives in a shared XObject that appears on multiple pages, replacing it on one page corrupts the others.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Rasterise, overlay, reflatten (the correct approach)
&lt;/h3&gt;

&lt;p&gt;The reliable method is to render each redacted page to an image, paint the redaction rectangles onto the image, and rebuild the PDF page as an image-only page. The text objects are gone entirely because the page no longer contains text objects; it contains a raster.&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;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&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="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;canvasContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;scale&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="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&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;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;redactionRects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;y&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="nx"&gt;w&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="nx"&gt;h&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="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is bigger (an image instead of vector text) and it loses accessibility (screen readers cannot read the raster), but the text is genuinely gone. Optionally you can OCR the flattened page to add a searchable text layer that contains only the non-redacted content.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compliance check
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the redacted file in a fresh reader.&lt;/li&gt;
&lt;li&gt;Ctrl+A, then Ctrl+C.&lt;/li&gt;
&lt;li&gt;Paste into a plain text editor.&lt;/li&gt;
&lt;li&gt;Search for any of the redacted terms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any redacted term appears, the redaction is decorative. If none appears, the redaction is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I use
&lt;/h2&gt;

&lt;p&gt;I moved to &lt;a href="https://pdfslime.com/redact-pdf" rel="noopener noreferrer"&gt;PDFslime's redaction tool&lt;/a&gt; because it defaults to the rasterise-and-flatten approach, warns you if you try the annotation-only mode, and runs the whole operation in the browser so the sensitive source never touches a server. The tool also runs the paste test on the output automatically and refuses to hand back a file that fails.&lt;/p&gt;

&lt;p&gt;If you build a tool that does redaction, please default to the flattening approach. Users assume redaction is safe; if your default is unsafe you will ship a leaked contract for someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What flatten redaction cannot fix
&lt;/h2&gt;

&lt;p&gt;Embedded images that contain the sensitive information. If page 3 has a photograph of a driver license, redacting the OCR text on page 3 does not touch the image. You have to redact the image itself before adding it to the PDF, or rasterise the page and paint over the image region in the raster.&lt;/p&gt;

&lt;p&gt;And metadata. A PDF's Info dictionary and XMP packet often contain the author's name and previous document title, neither of which any page-level redaction can touch. Strip metadata separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;annotation overlay (broken) &amp;lt; byte substitution (leaky) &amp;lt; rasterise-and-flatten (correct). Every "redacted" file worth taking seriously should be built with the last approach and tested with the paste test.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>software</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
