<?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: WgeorgeAssistantIA</title>
    <description>The latest articles on DEV Community by WgeorgeAssistantIA (@wgeorgeassistantia).</description>
    <link>https://dev.to/wgeorgeassistantia</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%2F3988867%2Fb500a859-a1d4-4ff7-a1d4-f187a1632580.png</url>
      <title>DEV Community: WgeorgeAssistantIA</title>
      <link>https://dev.to/wgeorgeassistantia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wgeorgeassistantia"/>
    <language>en</language>
    <item>
      <title>Committed Memory Above RAM Doesn't Mean Your PC Is Swapping</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Wed, 14 Oct 2026 05:37:47 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/committed-memory-above-ram-doesnt-mean-your-pc-is-swapping-1opm</link>
      <guid>https://dev.to/wgeorgeassistantia/committed-memory-above-ram-doesnt-mean-your-pc-is-swapping-1opm</guid>
      <description>&lt;p&gt;One rule in my Windows diagnostic tool said: &lt;em&gt;"Your PC is compensating for missing RAM by using the disk."&lt;/em&gt; The condition behind it was one line: committed memory is greater than physical RAM.&lt;/p&gt;

&lt;p&gt;On my own machine, that line was true. And my PC was fine.&lt;/p&gt;

&lt;p&gt;This post explains why "commit &amp;gt; RAM" proves nothing, and what I measure instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the first version did
&lt;/h2&gt;

&lt;p&gt;The rule read two numbers and compared them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;committed memory (what Windows has promised to processes)&lt;/li&gt;
&lt;li&gt;total physical RAM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the first was larger than the second, it flagged "swap in use" and suggested more RAM or fewer programs.&lt;/p&gt;

&lt;p&gt;It sounded reasonable. It was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why commit is the wrong signal
&lt;/h2&gt;

&lt;p&gt;Committed memory is memory that has been &lt;strong&gt;reserved&lt;/strong&gt;, not memory that is being &lt;strong&gt;used&lt;/strong&gt;. A process can reserve a lot and touch very little of it. Browsers are the typical example: they reserve large regions up front.&lt;/p&gt;

&lt;p&gt;So commit can exceed RAM on a perfectly healthy machine, with no page ever read back from the disk.&lt;/p&gt;

&lt;p&gt;There is a second reason. Commit is charged against the &lt;strong&gt;commit limit&lt;/strong&gt;, which is RAM plus the pagefile. A machine with a large pagefile can commit far more than its RAM by design, so &lt;code&gt;commit &amp;gt; RAM&lt;/code&gt; was never a pressure signal on its own. (Thanks to Kai for pointing this out in the comments.) In the rule below it is only a precondition: the other two conditions do the real work.&lt;/p&gt;

&lt;p&gt;Here is what my own PC showed on September 24:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Committed memory: &lt;strong&gt;22.5 GB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Physical RAM: &lt;strong&gt;15.9 GB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Available RAM: &lt;strong&gt;24 %&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Pages read from disk to serve memory: &lt;strong&gt;0.4 per second&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Commit was well above RAM. The disk was almost idle. The rule fired anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually shows a PC is compensating on disk
&lt;/h2&gt;

&lt;p&gt;The real signal is the &lt;strong&gt;hard page fault rate&lt;/strong&gt;: pages that Windows has to read from disk because the data it needs is no longer in RAM. The counter is &lt;code&gt;Pages Input/sec&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I read it twice, at the start and at the end of my measurement window, and divide the difference by the elapsed time. It is a raw cumulative counter, so one reading alone tells you nothing:&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;$m1&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;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_PerfRawData_PerfOS_Memory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-Sleep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Seconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$m2&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;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_PerfRawData_PerfOS_Memory&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$seconds&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;$m2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Timestamp_Sys100NS&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;$m1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Timestamp_Sys100NS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1e7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$pagesPerSec&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;$m2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PagesInputPersec&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;$m1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PagesInputPersec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$seconds&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail that cost me time: I use the WMI class, not &lt;code&gt;Get-Counter&lt;/code&gt;. Counter names are translated according to the language of Windows, so a script that works on an English install can fail silently on a French one. The WMI class property names stay the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two thresholds, and why there are two
&lt;/h2&gt;

&lt;p&gt;The new condition needs three things together:&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;$SEUIL_PAGES_DISQUE&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;100&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# pages/s read from disk for memory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$SEUIL_RAM_DISPO_SWAP&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;25&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# % of RAM available&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;$commitGb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ramTotalGb&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="nv"&gt;$pctRamAvailable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-lt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SEUIL_RAM_DISPO_SWAP&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;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pagesPerSec&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="nv"&gt;$pagesPerSec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SEUIL_PAGES_DISQUE&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="c"&gt;# flag it&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;100 pages per second is roughly 400 KB/s of reads, at 4 KB per page. That is a level where the disk is clearly doing the memory's job.&lt;/p&gt;

&lt;p&gt;Why not use the page counter alone? Because &lt;code&gt;Pages Input/sec&lt;/code&gt; also counts programs being &lt;strong&gt;loaded&lt;/strong&gt; from disk. If the user opens an application during the measurement, the counter jumps even though RAM is fine. Requiring low available RAM as well keeps those reads from being blamed on missing memory.&lt;/p&gt;

&lt;p&gt;If the counter cannot be read, the rule says nothing. A false "your PC is compensating" is worse than silence.&lt;/p&gt;

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

&lt;p&gt;I ran the real analysis on my machine: the only thing still flagged was a full disk, in 15 seconds. I also have a simulated test bench, where heavy paging triggers the rule. I did not build a separate case for "high commit, idle disk": my own PC on September 24 was that case.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I have not proved
&lt;/h2&gt;

&lt;p&gt;The thresholds come from one machine, mine, and from reasoning about the counters. I do not know yet how 100 pages/s behaves across very different hardware. That rate is about 400 KB/s: close to free on an NVMe SSD, where a 4 KB random read takes tens of microseconds, but potentially dominant on a spinning disk, where each fault can cost a seek. A raw count only means something once it is weighted by device latency. The next version should read the disk's read latency (or read queue length) next to the fault rate, and flag a problem when the added time is visible, not when a count crosses a fixed number. A field test on about ten PCs is still the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;This is part of &lt;a href="https://nyctale.fr" rel="noopener noreferrer"&gt;Nyctale&lt;/a&gt;, a Windows PC diagnostic tool that tries to name the real cause of a slow PC and says so when no software will fix it. This correction shipped in version 1.0.3. The quick diagnostic is free.&lt;/p&gt;

&lt;p&gt;If you monitor memory on Windows: which counter do you trust to tell a slow PC from a PC that just reserves a lot?&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>windows</category>
      <category>performance</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Four Bugs I Only Found by Driving the Real Window of a PySide6 App</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:28:12 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/four-bugs-i-only-found-by-driving-the-real-window-of-a-pyside6-app-138m</link>
      <guid>https://dev.to/wgeorgeassistantia/four-bugs-i-only-found-by-driving-the-real-window-of-a-pyside6-app-138m</guid>
      <description>&lt;p&gt;My app is a desktop tool that traces images into SVG. The tracing engine is vtracer, a Rust extension, and the UI is PySide6. The engine had good unit tests. Then I used the app for real, and four bugs showed up that none of those tests could see.&lt;/p&gt;

&lt;p&gt;All four come from the same place: the interaction between Qt, threads and a native extension. If you build a PySide6 or PyQt app that runs work in a &lt;code&gt;QThread&lt;/code&gt;, you may hit the same ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: the app died with no exception
&lt;/h2&gt;

&lt;p&gt;While a trace was running, the user could paint another stroke. The handler for the mask-changed signal put the selection back with &lt;code&gt;canvas.set_mask(...)&lt;/code&gt;. But &lt;code&gt;set_mask&lt;/code&gt; emits the same signal again, so the handler called itself.&lt;/p&gt;

&lt;p&gt;That is infinite recursion, but not the kind Python complains about. It went through Qt's C++ signal machinery, so Python's recursion limit never triggered. The process simply died with a native stack overflow (&lt;code&gt;0xC00000FD&lt;/code&gt; on Windows). No traceback, no message.&lt;/p&gt;

&lt;p&gt;The rule I took from it: &lt;strong&gt;never react to a Qt signal by modifying the thing that emitted it.&lt;/strong&gt; A guard flag fixes 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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_suspend_mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear_mask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;# emits mask_changed
&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_suspend_mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_mask_changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_suspend_mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bug 2: the UI froze forever after a Rust panic
&lt;/h2&gt;

&lt;p&gt;The worker looked like this:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QThread&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Signal&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="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_fn&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a corrupt image, vtracer panicked. pyo3 turns a Rust panic into a Python &lt;code&gt;PanicException&lt;/code&gt;, and that class derives from &lt;code&gt;BaseException&lt;/code&gt;, not &lt;code&gt;Exception&lt;/code&gt;. My &lt;code&gt;except&lt;/code&gt; never caught it. &lt;code&gt;failed&lt;/code&gt; was never emitted, so the code that re-enables the buttons never ran. Every button stayed greyed out, with no message.&lt;/p&gt;

&lt;p&gt;The fix is to catch &lt;code&gt;BaseException&lt;/code&gt; in the worker (re-raising &lt;code&gt;KeyboardInterrupt&lt;/code&gt; and &lt;code&gt;SystemExit&lt;/code&gt;), and to add a safety net: &lt;code&gt;finished&lt;/code&gt; is emitted even when &lt;code&gt;run()&lt;/code&gt; ends on an uncaught exception, so a handler on it can reset the "busy" state.&lt;/p&gt;

&lt;p&gt;If you wrap any native extension, check what its panics or errors derive from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 3: a second worker silently refused to start
&lt;/h2&gt;

&lt;p&gt;After a click, the app ran one worker, and when it finished it should start a second one. I called the next &lt;code&gt;_run()&lt;/code&gt; directly from the &lt;code&gt;done&lt;/code&gt; handler. Sometimes nothing happened.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_run()&lt;/code&gt; starts with a guard:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_worker&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isRunning&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: &lt;code&gt;done&lt;/code&gt; is emitted from inside &lt;code&gt;run()&lt;/code&gt;, that is, from the worker's own thread, before the thread has finished stopping. At that exact moment &lt;code&gt;isRunning()&lt;/code&gt; can still return &lt;code&gt;True&lt;/code&gt;. The new call was rejected by its own guard, depending on timing. It never showed up when I tested one case alone, only inside the full test suite.&lt;/p&gt;

&lt;p&gt;The fix is to let the event loop turn once before chaining:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_on_selection_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;QTimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;singleShot&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;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_apply_selection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My rule now: &lt;strong&gt;chaining one worker from another's &lt;code&gt;done&lt;/code&gt; always goes through &lt;code&gt;QTimer.singleShot(0, ...)&lt;/code&gt;&lt;/strong&gt;, never a direct call.&lt;/p&gt;

&lt;p&gt;A related trap sits in the same area. Each worker removes itself from &lt;code&gt;self._worker&lt;/code&gt; on &lt;code&gt;finished&lt;/code&gt;. A chain started from &lt;code&gt;done&lt;/code&gt; had already installed the next worker, and the previous worker's &lt;code&gt;finished&lt;/code&gt; then wiped it out. Nothing blocked two jobs from running on the same session. The fix is to forget a worker only if it is still the current one:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_forget_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;worker&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_worker&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bug 4: a keyboard shortcut bypassed the greyed-out buttons
&lt;/h2&gt;

&lt;p&gt;I disabled the buttons while a trace was running. But a shortcut like Ctrl+O does not go through a button. Pressing it during a computation opened a new image, which closed the session and deleted its temporary folder under the running thread. The thread then died with "Cannot create output file".&lt;/p&gt;

&lt;p&gt;The fix is a single predicate used by every entry point, shortcuts included:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_idle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_worker&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isRunning&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;open_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_idle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I test this now
&lt;/h2&gt;

&lt;p&gt;Engine tests cannot catch these. The failures only exist when the real window, the real event loop and the real thread run together. So I keep a separate file of regression tests that drives the actual &lt;code&gt;MainWindow&lt;/code&gt; in Qt's offscreen mode, with modal dialogs disabled (otherwise a test blocks silently on a message box).&lt;/p&gt;

&lt;p&gt;Two practical details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To test "Ctrl+O during a computation", force a slow job (&lt;code&gt;time.sleep(1.5)&lt;/code&gt; in the worker). On a small test image a real trace ends in tens of milliseconds, and the test would check nothing.&lt;/li&gt;
&lt;li&gt;To test the panic case, raise a custom &lt;code&gt;BaseException&lt;/code&gt; subclass in the worker and assert that the buttons are enabled again afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would tell my past self
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Qt signals cross threads and C++ code. Python's safety nets do not follow them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;done&lt;/code&gt; is not "the thread has stopped".&lt;/li&gt;
&lt;li&gt;Every way to start work needs the same guard, not just the buttons.&lt;/li&gt;
&lt;li&gt;Test the glue, not just the engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;This is part of &lt;a href="https://www.vectorpop.fr/vectofix" rel="noopener noreferrer"&gt;VectoFix&lt;/a&gt;, a local Windows tool that traces an image into SVG, measures what the tracing lost compared with the source, and lets you fix a zone with a brush. Everything runs on the user's machine.&lt;/p&gt;

&lt;p&gt;Have you hit a thread or signal bug that only appeared with the real UI? I am curious what your regression tests look like.&lt;/p&gt;

</description>
      <category>python</category>
      <category>pyside6</category>
      <category>qt</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Windows Event ID 37 Doesn't Mean Your CPU Is Overheating</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:07:57 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/windows-event-id-37-doesnt-mean-your-cpu-is-overheating-4f9i</link>
      <guid>https://dev.to/wgeorgeassistantia/windows-event-id-37-doesnt-mean-your-cpu-is-overheating-4f9i</guid>
      <description>&lt;p&gt;I build a small Windows diagnostic tool. One of its rules looked at the System event log, found &lt;code&gt;Kernel-Processor-Power&lt;/code&gt; Event ID 37, and told the user: &lt;em&gt;"your cooling system is probably the problem."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then I ran it on my own PC, which has no cooling problem. It told me my cooling was the problem.&lt;/p&gt;

&lt;p&gt;This post is about why Event 37 is a terrible proxy for overheating, and the three filters that fixed the false positive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Event 37 actually says
&lt;/h2&gt;

&lt;p&gt;Event ID 37 from &lt;code&gt;Microsoft-Windows-Kernel-Processor-Power&lt;/code&gt; reads roughly: &lt;em&gt;the speed of processor N is being limited by system firmware.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Heat is one reason firmware does that. It is not the only one. A power limit, a weak charger, a battery state or an OEM power profile produce the same event. The event tells you a limit was applied. It does not tell you why.&lt;/p&gt;

&lt;p&gt;My first version of the rule was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;count Event 37 over the last 14 days&lt;/li&gt;
&lt;li&gt;ignore the ones right after the latest boot&lt;/li&gt;
&lt;li&gt;if three or more remain, flag "physical cooling problem"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was also compared with the CPU load measured during an 8-second scan, taken while the user is idle by definition. Two things measured at two different times, used as one argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers on my machine
&lt;/h2&gt;

&lt;p&gt;Over 14 days my log had 12 Event 37 entries. The old rule kept 11 of them as "not at boot" and concluded the worst.&lt;/p&gt;

&lt;p&gt;Looking at them one by one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;8 of the 12&lt;/strong&gt; happened within 10 minutes after a boot. Not just the latest boot: any boot in the period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The other 4&lt;/strong&gt; were not heat spikes at all. Each reported a cap lasting 33 to 48 hours. That is a permanent power ceiling, not a thermal event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real overheating episodes: zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: ignore every boot, not just the last one
&lt;/h2&gt;

&lt;p&gt;Firmware often applies a limit while the system starts. Collect every boot in the window with Event 12 from &lt;code&gt;Kernel-General&lt;/code&gt;, then drop any Event 37 that lands in the 10 minutes after one.&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;$boots&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;Get-WinEvent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilterHashtable&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="nx"&gt;LogName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'System'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProviderName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Microsoft-Windows-Kernel-General'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;StartTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;AddDays&lt;/span&gt;&lt;span class="err"&gt;(-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="err"&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="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;TimeCreated&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;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ev37&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;$atBoot&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;$boots&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="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="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TimeCreated&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="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TotalMinutes&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="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ge&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="o"&gt;-and&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;-le&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&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;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;$atBoot&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;continue&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="c"&gt;# ...next filters&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;
  
  
  Fix 2: only count events on mains power
&lt;/h2&gt;

&lt;p&gt;On battery, limiting the CPU is intended behaviour. &lt;code&gt;Kernel-Power&lt;/code&gt; Event 105 records power source changes, and its &lt;code&gt;AcOnline&lt;/code&gt; field says whether the machine was plugged in. For each Event 37, take the latest Event 105 before it. If the machine was on battery, it is not evidence of anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3: use the duration, not just the count
&lt;/h2&gt;

&lt;p&gt;Event 37 carries a &lt;code&gt;CapDurationInSeconds&lt;/code&gt; field. A thermal throttle comes and goes in minutes. A cap that lasts a day or more is a configuration, not a symptom. I treat anything of an hour or longer as a permanent cap and drop 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;$xml&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;xml&lt;/span&gt;&lt;span class="p"&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;ToXml&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$dur&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;$xml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EventData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Data&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="nx"&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;'CapDurationInSeconds'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'#text'&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;$dur&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&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;continue&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;Only what survives all three filters counts as an episode. On my machine: 0. The verdict also changed from a statement ("your cooling is the problem") to a hedged one ("very probably") with a simple test the user can do themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  A PowerShell trap I hit on the way
&lt;/h2&gt;

&lt;p&gt;In the same session I broke the translation table. My loop used a variable named &lt;code&gt;$t&lt;/code&gt; for the event time. The table of translated strings was called &lt;code&gt;$T&lt;/code&gt;. PowerShell variable names are case-insensitive, so &lt;code&gt;$t&lt;/code&gt; silently overwrote &lt;code&gt;$T&lt;/code&gt; and every string came back empty. If you use single-letter variables in a script that already has one in capitals, rename one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I have not proved
&lt;/h2&gt;

&lt;p&gt;I recalibrated this on one machine, mine. The three filters remove the false positive I could see, but I do not yet know the false-positive rate across other laptops and desktops. A 10-PC field test is next. If you have Event 37 data from machines with a real cooling fault, I would like to see how the filters behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;This is part of &lt;a href="https://nyctale.fr" rel="noopener noreferrer"&gt;Nyctale&lt;/a&gt;, a Windows PC diagnostic tool that tries to name the actual cause of a slow or hot PC, and says "no software will fix this" when that is the honest answer. The fixed rule shipped in version 1.0.3. The quick diagnostic is free.&lt;/p&gt;

&lt;p&gt;Have you used Event 37 for anything? Did you find other signals that separate real thermal throttling from firmware power limits?&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>windows</category>
      <category>debugging</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Identify Technologies Mentioned in a YouTube Video</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:41:54 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/how-to-identify-technologies-mentioned-in-a-youtube-video-e91</link>
      <guid>https://dev.to/wgeorgeassistantia/how-to-identify-technologies-mentioned-in-a-youtube-video-e91</guid>
      <description>&lt;p&gt;"Which technologies does this video talk about?" sounds like a named-entity task, and it is, with a twist: the entities are product names that are short, ambiguous, constantly new and often misheard. This post covers the three problems you meet, and a practical approach to each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: what counts as a technology?
&lt;/h2&gt;

&lt;p&gt;If you do not decide, the model will. Without a definition you get everything from "JavaScript" to "the cloud" to the presenter's company. Write the definition into the prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include: named products, frameworks, libraries, languages, services, CLIs and protocols&lt;/li&gt;
&lt;li&gt;Exclude: generic words ("API", "database", "framework"), company names that are not products, channel names, people&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then maintain a block list of the noise you actually observe. In our runs, recurring false positives included generic words such as "API" and "Google", and the names of channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: the same tool, many spellings
&lt;/h2&gt;

&lt;p&gt;Speakers say "Postgres", "PostgreSQL", "Postgres DB". Captions write "Next JS", "next.js" or "Nexjs". Two things help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normalise&lt;/strong&gt;: lower-case, remove spaces and punctuation, strip common suffixes (&lt;code&gt;js&lt;/code&gt;, &lt;code&gt;db&lt;/code&gt;) to create a matching key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep an alias list&lt;/strong&gt; for the display name and for deduplication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Watch for duplicates that differ by one letter. We saw a tool appear twice because one occurrence was misspelt; a fuzzy match on names within the same video catches it.&lt;/p&gt;

&lt;p&gt;Translation can also distort names. If your pipeline summarises in another language, instruct the model to keep product names exactly as spoken. A product name turned into a translated phrase is no longer searchable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: mentioned does not mean recommended
&lt;/h2&gt;

&lt;p&gt;A technology can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Used&lt;/strong&gt; in the demo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recommended&lt;/strong&gt; as an alternative&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compared&lt;/strong&gt; against another&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Criticised&lt;/strong&gt; or dismissed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Classify the mention type, or at least keep the quote so a reader can judge. This is the difference between a useful tool list and a misleading one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ambiguous names need context
&lt;/h2&gt;

&lt;p&gt;"Neon", "Serena" and "Phantom" are all valid product names, and all share their name with something unrelated. Give the model the surrounding sentence, and when you look up the official site, pass the category as well. A good result for an ambiguous name needs context at both extraction and resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it on real videos
&lt;/h2&gt;

&lt;p&gt;Pick ten videos in your field, run the pipeline and read the list yourself. You will quickly see what kind of noise appears and where the prompt needs a rule. Update the block list and the prompt, run again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our implementation
&lt;/h2&gt;

&lt;p&gt;We built these rules into &lt;a href="https://www.vidscope.fr" rel="noopener noreferrer"&gt;VidScope&lt;/a&gt;, which returns the tools mentioned in a YouTube video with a timestamp and an official link, and avoids returning a link when the match is unclear.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is this a named-entity recognition problem?&lt;/strong&gt;&lt;br&gt;
Yes, with the twist that names are short, ambiguous and often misheard in captions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I stop generic words being returned?&lt;/strong&gt;&lt;br&gt;
Define what counts as a tool in the prompt and keep a block list of observed noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I handle different spellings of the same tool?&lt;/strong&gt;&lt;br&gt;
Normalise names to a matching key and keep an alias list.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nlp</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Extract Tools and Technologies from YouTube Videos Automatically</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Fri, 09 Oct 2026 15:48:12 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/how-to-extract-tools-and-technologies-from-youtube-videos-automatically-32ie</link>
      <guid>https://dev.to/wgeorgeassistantia/how-to-extract-tools-and-technologies-from-youtube-videos-automatically-32ie</guid>
      <description>&lt;p&gt;If you watch technical YouTube videos for work, you know the problem: a 40-minute walkthrough mentions a dozen tools, a few commands and a couple of versions, and an hour later you cannot remember which ones. This post shows a practical pipeline to extract the tools and technologies mentioned in a video automatically, step by step, and the pitfalls that make the naive version unreliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline in four steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Get the transcript&lt;/li&gt;
&lt;li&gt;Split it into chunks the model can handle&lt;/li&gt;
&lt;li&gt;Extract candidate tools with their timestamps&lt;/li&gt;
&lt;li&gt;Verify each candidate and attach an official link&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step has a failure mode. We will go through them in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: get the transcript
&lt;/h2&gt;

&lt;p&gt;Most videos have captions, either written by the author or generated automatically. Libraries such as &lt;a href="https://github.com/yt-dlp/yt-dlp" rel="noopener noreferrer"&gt;yt-dlp&lt;/a&gt; can fetch them along with timing data. Keep the timing: every segment of text has a start time, and that is what lets you link each tool back to the exact moment of the video later.&lt;/p&gt;

&lt;p&gt;Pitfall: automatic captions mangle product names. Words like "Supabase", "Vercel" or "Kubernetes" come out phonetically. Do not clean them yet; the model will need context to recover them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: split the transcript into chunks
&lt;/h2&gt;

&lt;p&gt;Long videos do not fit in one prompt, and even when they do, extraction quality drops as the input grows. A simple approach is to cut the transcript into chunks of fixed size, run them in parallel and merge the results.&lt;/p&gt;

&lt;p&gt;We found chunk size matters more than expected. In our tests on a transcript of roughly 475,000 characters, small chunks found more tools than large ones: very large chunks returned fewer distinct tools, because the model summarises instead of listing. Smaller chunks cost more calls but give better recall. A middle size of about 30,000 characters gave a good balance between the number of calls and the tools found.&lt;/p&gt;

&lt;p&gt;The mistake to avoid is silent truncation. An early version of our pipeline cut the text at a fixed length and analysed only the first minutes of a seven-hour video without telling anyone. If you cap the input, say so in the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: extract candidates with timestamps
&lt;/h2&gt;

&lt;p&gt;Ask the model for structured output, not prose. For each candidate tool, request the name, a short description, a category and a verbatim quote from the transcript with its timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&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;"Supabase"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"we are going to use Supabase for the backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00:12:41"&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;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;Two rules improve the results a lot. First, define what counts as a tool in the prompt, and list exclusions (generic words, channel names, company names that are not products). Second, require a verbatim quote. A candidate whose quote does not appear in the transcript is rejected.&lt;/p&gt;

&lt;p&gt;After merging the chunks, deduplicate by normalised name and drop candidates without a valid timestamp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: verify and link
&lt;/h2&gt;

&lt;p&gt;This is the step most tutorials skip, and the one that matters most. A name alone is ambiguous: "Neon", "Serena" or "Codex" each match several unrelated things on the web. If you resolve the official website with a naive search, you will get cinema studios and food standards instead of software.&lt;/p&gt;

&lt;p&gt;What worked for us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build the search query from the tool's category and description, not only its name&lt;/li&gt;
&lt;li&gt;Accept canonical open-source repositories when a tool has no website of its own&lt;/li&gt;
&lt;li&gt;Block aggregator sites, course platforms, forums and social networks as official sources&lt;/li&gt;
&lt;li&gt;Score the match, and return nothing when the confidence is low&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is the hardest culture shift: an empty link is better than a wrong one. Users forgive a missing link; they do not forgive a confident wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;A good extraction pipeline is mostly about discipline: keep the timestamps, never truncate silently, require quotes, and prefer no link to a bad link. Models are the easy part. The rest is verification.&lt;/p&gt;

&lt;p&gt;If you want to see this applied rather than build it yourself, we built &lt;a href="https://www.vidscope.fr" rel="noopener noreferrer"&gt;VidScope&lt;/a&gt; to explore this problem: paste a YouTube link and it returns the tools mentioned with timestamps and official links.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I extract tools from any YouTube video?&lt;/strong&gt;&lt;br&gt;
From any video that has a transcript, though automatic captions can misspell product names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why keep timestamps?&lt;/strong&gt;&lt;br&gt;
They let you check each tool at the moment it was mentioned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just ask an AI to summarise the video?&lt;/strong&gt;&lt;br&gt;
A summary tells you the idea; it rarely gives you the exact tools, versions and the evidence for each.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built an AI Tool That Extracts Tech Stacks from YouTube Videos</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Sat, 12 Sep 2026 18:57:29 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/i-built-an-ai-tool-that-extracts-tech-stacks-from-youtube-videos-1520</link>
      <guid>https://dev.to/wgeorgeassistantia/i-built-an-ai-tool-that-extracts-tech-stacks-from-youtube-videos-1520</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmhxgf2rh5ym35iv9isgt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmhxgf2rh5ym35iv9isgt.jpeg" alt=" " width="799" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every developer has done this: you open a 45-minute "Build a SaaS in a weekend" video, and by minute 12 you're already pausing every 30 seconds to screenshot a terminal command or squint at a VS Code sidebar to figure out which ORM the creator just installed. You rewind three times because you missed the name of the vector database. By the time the video ends, you've spent an hour to extract maybe eight lines of useful information: a framework name, two library names, one hosting provider. I built VidScope because I got tired of doing this manually, and I wanted to share how the extraction pipeline actually works under the hood — and why "AI video summarizer" was the wrong way to think about the problem from the start.&lt;/p&gt;

&lt;p&gt;The first mistake I made was treating this as a summarization problem. Early prototypes fed the full YouTube transcript into an LLM and asked for "a summary of the tools used." The results looked plausible and were frequently wrong — the model would infer a database was PostgreSQL because the creator said "SQL," or assume a deployment target was Vercel because the video mentioned Next.js, even when the actual clip named Railway. Summarization models are optimized to sound coherent, not to be strictly grounded in the source. For a tool whose entire value proposition is "tell me exactly what was mentioned," a confidently wrong answer is worse than no answer. That's the core design constraint that shaped everything else: extraction, not interpretation.&lt;/p&gt;

&lt;p&gt;The pipeline itself is simpler than people expect. VidScope pulls the video's transcript (falling back to audio transcription when no captions exist), chunks it to stay inside context limits for long-form content — some tutorials and conference talks run past two hours — and runs extraction passes that are explicitly instructed to only output tool, framework, or library names that appear verbatim or near-verbatim in the transcript text. No inference from context, no "this is probably React because they mentioned JSX." If a name isn't in the transcript, it doesn't make it into the report. This is the "0% hallucination" constraint, and it's enforced at the prompt level with strict extraction rules, not just requested politely.&lt;/p&gt;

&lt;p&gt;The part that took the most iteration wasn't the extraction — it was verification. A raw list of tool names is only marginally more useful than pausing the video yourself. What makes the report actually save time is that every extracted name gets resolved to its official documentation or homepage link automatically, so a developer can go from "watched half a tutorial" to "have the actual docs open in five tabs" in under 30 seconds. Getting this resolution step accurate meant handling ambiguous names (there are at least four different open-source projects called "Flow"), filtering out generic terms that get mentioned but aren't tools (the model initially flagged "the cloud" and "database" as extracted entities before I tightened the extraction schema), and deduplicating variants of the same tool referenced multiple times under slightly different names across a single video.&lt;/p&gt;

&lt;p&gt;Long videos exposed a separate scaling problem: a two-hour conference talk produces a transcript that's tens of thousands of tokens, and naively extracting per-chunk without merging state across chunks produces duplicate or fragmented tool lists — the same framework mentioned in minute 5 and minute 90 shows up twice with slightly different context. The fix was maintaining an extraction state across chunks rather than treating each chunk independently, which also meant the final report generation step needed to be idempotent regardless of how many chunks a video's transcript happened to split into. This is the unglamorous part of building an "AI feature" that nobody talks about: the actual model call is a small fraction of the engineering effort compared to chunking, state management, and making outputs consistent at different video lengths.&lt;/p&gt;

&lt;p&gt;None of this matters if the tool doesn't fit into how developers actually work. The workflow I optimized for is: paste a YouTube link, wait 30 seconds, get a clean list of every tool and framework mentioned with verified links, export to Markdown or PDF, move on. No account required to try it, no video re-upload, no watching required at all. It's built for the moment when you find a promising tutorial at 11pm and want to know in thirty seconds whether it's using a stack you actually want to learn, before committing 45 minutes to watching it.&lt;/p&gt;

&lt;p&gt;If you've ever kept a messy notes file of "tools mentioned in videos I watched," or rewound a stream three times to catch a package name, that's exactly the problem VidScope solves. You can try it free on two videos, no credit card required, at &lt;a href="https://www.vidscope.fr" rel="noopener noreferrer"&gt;vidscope.fr&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How I Built a Local Silence Remover That Processes a 5GB Audio File in Under 6 Minutes</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:44:45 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/how-i-built-a-local-silence-remover-that-processes-a-5gb-audio-file-in-under-6-minutes-1ebf</link>
      <guid>https://dev.to/wgeorgeassistantia/how-i-built-a-local-silence-remover-that-processes-a-5gb-audio-file-in-under-6-minutes-1ebf</guid>
      <description>&lt;p&gt;VoxCut is a dedicated silence remover for audio and video: it automatically detects and cuts silent pauses — dead air, long breaths, gaps between takes — from podcasts, interviews and recordings. Unlike full editors that bundle silence removal as one feature among many, it does this one job, entirely on your device.&lt;/p&gt;

&lt;p&gt;I want to walk through the actual engineering problem behind it, because "remove silence from audio" sounds trivial until someone hands you a 72-hour, 5GB recording and expects it back in a few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach falls over first
&lt;/h2&gt;

&lt;p&gt;The obvious way to build this: load the whole file into memory, scan the waveform, cut the quiet parts, re-encode. That's roughly what most desktop audio editors do — and it's exactly why they choke on anything past a couple of GB. Once you're loading multi-hour recordings, you hit a RAM ceiling that has nothing to do with your CPU.&lt;/p&gt;

&lt;p&gt;VoxCut instead runs a local, FFmpeg-based streaming pass over the file rather than materializing the whole thing in memory. That single architectural choice is what lets it handle files "beyond 5GB" without a hard ceiling — the real limit becomes your disk space, not your RAM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsmyzqw1pq7ulsw1ktty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsmyzqw1pq7ulsw1ktty.png" alt="VoxCut waveform before/after" width="799" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection: comparing amplitude against a threshold
&lt;/h2&gt;

&lt;p&gt;The detection logic itself is simple in concept and easy to get wrong in practice: compare each segment's amplitude against a decibel threshold and a minimum pause duration. Too aggressive, and you cut a natural breath mid-sentence. Too conservative, and you leave dead air in.&lt;/p&gt;

&lt;p&gt;VoxCut previews exactly what will be cut before anything is removed, so the sensitivity setting is something you tune by ear rather than trust blindly — nothing is deleted until you confirm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turbo Mode trade-off (V2)
&lt;/h2&gt;

&lt;p&gt;The 2.0 release added a Pro-only Turbo Mode that's a good example of an honest trade-off rather than a free performance win. The standard export stitches kept segments together with crossfades, which sounds smoother but costs time on files with hundreds of detected cuts. Turbo mode removes silence in a single pass instead — no crossfades — which is roughly 2x faster on realistic content (podcasts, interviews, meetings).&lt;/p&gt;

&lt;p&gt;The cost: sharper cuts instead of smooth transitions. For most spoken-word content that's barely audible; for content that leans on the crossfades to hide the cut points, standard mode is still the default.&lt;/p&gt;

&lt;p&gt;On realistic content (not the "silence-only" edge case that inflates marketing numbers), expect roughly 35-50x real-time in standard mode and 70-100x in Turbo, depending on the machine. A 72-hour, 5GB file was processed in under 6 minutes on ordinary consumer hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going cross-platform: Windows/Linux desktop, then Android
&lt;/h2&gt;

&lt;p&gt;VoxCut started as a Windows/Linux desktop app. Porting the same core logic to Android (shipped as v2.0.2, same week as the 2.0 desktop release) meant a separate app with its own constraints: mobile storage, a different free-tier limit (5 minutes per file vs. 10 minutes on desktop), and Pro features gated differently — AI noise reduction and loudness normalization are Android-Pro-only, priced independently (€14.99 lifetime) from the desktop license.&lt;/p&gt;

&lt;p&gt;Keeping "100% local, no upload" true on both platforms was the one non-negotiable constraint across the port — it's the core promise, not a checkbox feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I started over, I'd build the streaming-pass architecture from day one instead of retrofitting it — the in-memory prototype worked fine on my own test files and only broke once real users started throwing multi-hour recordings at it. Test with files bigger than you think anyone will use.&lt;/p&gt;




&lt;p&gt;VoxCut is free to try (Windows, Linux, Android) — &lt;a href="https://voxcutpro.com" rel="noopener noreferrer"&gt;voxcutpro.com&lt;/a&gt;. Feedback and questions welcome in the comments, this is my first shipped piece of software and I'm still learning in public.&lt;/p&gt;

&lt;h3&gt;
  
  
  About VoxCut
&lt;/h3&gt;

&lt;p&gt;VoxCut is a dedicated silence remover for audio and video. It automatically removes silence, pauses and dead air while processing your files locally — with no upload and no subscription.&lt;/p&gt;

&lt;p&gt;VoxCut — Silence Remover for Audio &amp;amp; Video · &lt;a href="https://voxcutpro.com" rel="noopener noreferrer"&gt;https://voxcutpro.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>audio</category>
      <category>ffmpeg</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Why I built a local-first PNG-to-SVG converter instead of using an online tool</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Fri, 04 Sep 2026 12:58:26 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/why-i-built-a-local-first-png-to-svg-converter-instead-of-using-an-online-tool-4415</link>
      <guid>https://dev.to/wgeorgeassistantia/why-i-built-a-local-first-png-to-svg-converter-instead-of-using-an-online-tool-4415</guid>
      <description>&lt;p&gt;Most "convert your image to SVG" tools online are the same story: upload your file to a server you don't control, wait, download a result that's either bloated with thousands of tiny paths or stripped of the details that made the image worth vectorizing in the first place. I hit this wall enough times doing client logo work that I built my own converter — VectorPop — and kept it entirely local, no upload, no subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;Vectorizing a raster image well isn't really about tracing pixels — any tool can call a tracing library and spit out an SVG. The actual work is everything that happens &lt;em&gt;before&lt;/em&gt; tracing: merging near-identical colors so you don't get 40 shades of "almost the same blue," rebuilding gradients as real &lt;code&gt;&amp;lt;linearGradient&amp;gt;&lt;/code&gt; elements instead of dozens of flat color bands, and cleanly separating a subject from its background. Skip that preprocessing and you get technically-valid SVGs that are unusable in a real design workflow — too many paths, wrong colors, gradients turned into stripes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical approach
&lt;/h2&gt;

&lt;p&gt;VectorPop wraps &lt;code&gt;vtracer&lt;/code&gt; (the Rust tracing engine) with a Pillow-based preprocessing layer I built specifically to solve those problems: color quantization and merging before tracing, gradient reconstruction that samples the original pixels to estimate a real gradient rather than approximating it geometrically, and an "Optimize" button that tries ~12 setting combinations and keeps whichever one is closest to the source image — no deep learning, just systematic comparison. Background removal works two ways: flat-color removal with a tolerance slider for logos on solid backgrounds, and optional AI segmentation (rembg) for photos — the AI path is opt-in and the app degrades gracefully if the dependency isn't present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local, and why this pricing model
&lt;/h2&gt;

&lt;p&gt;Everything runs on-device: no image ever leaves your machine, which matters if you're vectorizing client logos or anything under NDA, and it also means no per-conversion cost to pass on to the user. That's why VectorPop is free for unlimited vectorization with 3 SVG exports/day, and a one-time €39 for the Pro tier (unlimited exports, PDF/high-res PNG, AI cutout, batch processing) — no subscription, because a desktop tool that runs locally shouldn't need one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;VectorPop is a Windows app, live on the Microsoft Store, built by a solo dev after getting tired of redoing SVGs by hand in Illustrator for logo and print work. If you've fought with online converters that either paywall basic exports or upload your files to a server, it's worth a look: &lt;a href="https://vectorpop.fr" rel="noopener noreferrer"&gt;vectorpop.fr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Curious what this community thinks of the gradient-reconstruction approach vs. just tracing flat color bands — happy to go into more technical detail in the comments.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>python</category>
      <category>svg</category>
      <category>windows</category>
    </item>
    <item>
      <title>Stop Copy-Pasting Your Invoices: The Complete Guide to Local PDF Mail Merge</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Thu, 27 Aug 2026 19:01:59 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/stop-copy-pasting-your-invoices-the-complete-guide-to-local-pdf-mail-merge-3pn3</link>
      <guid>https://dev.to/wgeorgeassistantia/stop-copy-pasting-your-invoices-the-complete-guide-to-local-pdf-mail-merge-3pn3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk08gy8vqpseyg5xpucd6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk08gy8vqpseyg5xpucd6.png" alt=" " width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run a small business, a freelance practice, or a local club, you probably spend more time than you'd like doing administrative chores. One of the worst offenders? Copying and pasting data from a spreadsheet to create individual documents.&lt;/p&gt;

&lt;p&gt;Whether it's monthly invoices, employee training certificates, or personalized event badges, doing this manually is a guaranteed way to waste hours and introduce typos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter the PDF Mail Merge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mail merge isn't just for sending letters anymore. By using your spreadsheet as a simple database and connecting it to a PDF template, you can automate this entire workflow. But there is a catch.&lt;/p&gt;

&lt;p&gt;Many people default to online web tools. While convenient, this means uploading sensitive financial data, client names, and addresses to a third-party server. If you care about data privacy (or GDPR compliance), this is a red flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Local-First Solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The safest and most efficient way to handle this is locally on your own computer. We developed &lt;a href="https://www.inoneshot.fr/" rel="noopener noreferrer"&gt;InOneShot&lt;/a&gt; exactly for this purpose.&lt;/p&gt;

&lt;p&gt;Here is how a &lt;strong&gt;local PDF mail merge&lt;/strong&gt; works in 3 steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare your Data&lt;/strong&gt;: Keep a clean Excel or CSV file. One row per invoice or person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design your Template&lt;/strong&gt;: Create a beautiful PDF design once. Leave blank spaces for the variable data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge and Generate&lt;/strong&gt;: Use InOneShot to map your spreadsheet columns to your PDF. As an offline &lt;strong&gt;bulk PDF generator&lt;/strong&gt;, the app runs locally on your machine, generating hundreds of unique, named PDFs in seconds.
You get a clean ZIP file of documents. No monthly subscriptions, no data privacy concerns, just hours of your life back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Automating your admin work doesn't require complex coding or expensive cloud tools. Sometimes, a smart local app is all you need.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Ready to get your time back? Download &lt;a href="https://www.inoneshot.fr/" rel="noopener noreferrer"&gt;InOneShot&lt;/a&gt;, the ultimate local PDF creator, and start automating your paperwork today!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>automation</category>
      <category>softwaredevelopment</category>
      <category>saas</category>
    </item>
    <item>
      <title>An Auto-Editor Alternative With No Command Line (for People Who Don't Code)</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Sat, 04 Jul 2026 14:02:32 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/an-auto-editor-alternative-with-no-command-line-for-people-who-dont-code-2fej</link>
      <guid>https://dev.to/wgeorgeassistantia/an-auto-editor-alternative-with-no-command-line-for-people-who-dont-code-2fej</guid>
      <description>&lt;p&gt;Long interviews, webinars and full sessions crash browser tools and choke on upload. Here's how to trim silence from large, multi-hour audio files without hitting size limits.&lt;/p&gt;

&lt;p&gt;Trimming silence from a three-minute clip is easy — almost any tool handles it. The pain starts when the file is big: a two-hour interview, a full-day workshop recording, a webinar, a long-form podcast in lossless WAV. That's where most silence removers fall apart, and where the choice of tool actually matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why large files break most silence removers
&lt;/h2&gt;

&lt;p&gt;The majority of popular silence-removal tools are browser-based or cloud-based. That's fine for short clips, but with large files it creates real friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload limits — many web tools cap uploads at 100–500 MB, well under a multi-hour lossless recording.&lt;/li&gt;
&lt;li&gt;Slow, fragile uploads — sending a 2–4 GB file over your connection can take ages, and a dropped connection means starting over.&lt;/li&gt;
&lt;li&gt;Browser memory limits — loading a huge waveform into a browser tab can freeze or crash it.&lt;/li&gt;
&lt;li&gt;Privacy exposure — a confidential interview or client recording leaves your machine and sits on someone else's server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix: process large files locally
&lt;/h2&gt;

&lt;p&gt;A desktop app that runs on your own machine sidesteps every one of those problems. There's no upload — the file never leaves your computer — so file size is limited by your disk and RAM, not by someone's server quota. Processing reads the file directly, which is far faster than streaming gigabytes to the cloud and back, and your audio stays completely private.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for when files are big
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Runs locally / offline — no upload step, no size cap tied to a server.&lt;/li&gt;
&lt;li&gt;Handles multi-gigabyte files without choking — headroom for multi-hour lossless recordings.&lt;/li&gt;
&lt;li&gt;Batch processing — so you can queue a whole folder of long recordings and walk away.&lt;/li&gt;
&lt;li&gt;A preview before you commit — you don't want to reprocess a 3 GB file because the threshold was off.&lt;/li&gt;
&lt;li&gt;Lossless format support (WAV, FLAC) — long recordings are often captured uncompressed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing it with VoxCut
&lt;/h2&gt;

&lt;p&gt;VoxCut is a desktop app for Windows and Linux built for exactly this. It runs 100% locally — your audio never leaves your computer — and handles files up to 5 GB, which comfortably covers multi-hour interviews, webinars and full-length episodes in WAV or FLAC. You set a sensitivity threshold, see a before/after waveform (blue for voice, grey for silence) so you know what will be cut before you export, and batch-process a whole folder of long recordings in one go. It's a one-time purchase, no subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for long recordings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Preview first on the waveform — a small threshold error is annoying on a 3-minute clip and expensive on a 2-hour one.&lt;/li&gt;
&lt;li&gt;Keep a 400–600 ms minimum silence so natural pauses survive across a long conversation.&lt;/li&gt;
&lt;li&gt;Batch overnight — queue the whole folder and let it run while you do something else.&lt;/li&gt;
&lt;li&gt;Always keep the original — trimming is destructive to timing, so archive the source file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Big files shouldn't mean big headaches. Once you stop fighting upload limits and process long recordings locally, trimming silence from a multi-hour session becomes just as quick as a short clip — and your recordings stay private the whole time.&lt;/p&gt;

</description>
      <category>podcast</category>
      <category>video</category>
      <category>productivity</category>
      <category>silence</category>
    </item>
    <item>
      <title>How to Automatically Remove Silences From a Podcast (Without Manual Editing)</title>
      <dc:creator>WgeorgeAssistantIA</dc:creator>
      <pubDate>Wed, 17 Jun 2026 11:02:48 +0000</pubDate>
      <link>https://dev.to/wgeorgeassistantia/how-to-automatically-remove-silences-from-a-podcast-without-manual-editing-2366</link>
      <guid>https://dev.to/wgeorgeassistantia/how-to-automatically-remove-silences-from-a-podcast-without-manual-editing-2366</guid>
      <description>&lt;p&gt;If you record podcasts, interviews, or voiceovers, you already know the truth: &lt;strong&gt;the recording is the easy part — the editing is where the hours disappear.&lt;/strong&gt; And a huge chunk of that editing time goes into one tedious, repetitive task: cutting out silences, pauses, and dead air.&lt;/p&gt;

&lt;p&gt;The good news? You don't have to do it by hand anymore. Here's how to remove silences from your audio automatically, and why it makes such a difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why silences matter more than you think
&lt;/h2&gt;

&lt;p&gt;A few seconds of dead air feels harmless while you're recording. But across a 45-minute episode, those pauses add up — often to &lt;strong&gt;10–20% of the total runtime&lt;/strong&gt;. That's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Longer episodes that feel slower and lose listeners&lt;/li&gt;
&lt;li&gt;More file size to host and deliver&lt;/li&gt;
&lt;li&gt;A less professional, less "tight" listening experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Removing silences makes your content punchier, shorter, and noticeably more polished — without changing a single word you said.&lt;/p&gt;

&lt;h2&gt;
  
  
  The slow way: cutting silences manually
&lt;/h2&gt;

&lt;p&gt;The traditional approach is to open your recording in an editor (Audacity, Audition, Premiere…), scrub through the waveform, find each gap, select it, and delete it. Repeat a few hundred times per episode.&lt;/p&gt;

&lt;p&gt;It works, but it's mind-numbing — and it's the single biggest reason editing a podcast can take &lt;strong&gt;2–3× longer than the recording itself.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fast way: automatic silence detection
&lt;/h2&gt;

&lt;p&gt;Modern tools can analyze your audio, detect every silent passage based on a volume threshold, and trim them in one pass. Instead of hunting for gaps manually, you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load your audio file&lt;/strong&gt; (MP3, WAV, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set the sensitivity&lt;/strong&gt; — how quiet and how long a passage must be before it counts as "silence"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the tool detect and trim&lt;/strong&gt; every silent section automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; your cleaned-up file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What used to take an hour now takes a couple of minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple tool for this: VoxCut
&lt;/h2&gt;

&lt;p&gt;I build a small Windows app called &lt;a href="https://voxcutpro.com" rel="noopener noreferrer"&gt;VoxCut&lt;/a&gt; that does exactly this. You drop in a recording, and it shows you a &lt;strong&gt;before/after waveform&lt;/strong&gt; — blue for voice, grey for silence — so you can see precisely what's being removed before you commit. One click, and the dead air is gone.&lt;/p&gt;

&lt;p&gt;It's designed to do one job well rather than be a full DAW: adjustable sensitivity, fast processing, and a clean interface with no learning curve. There's a free version to try it, and a one-time Pro upgrade (no subscription).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Full disclosure: I'm the developer of VoxCut, so I'm obviously biased. But the workflow above works with any silence-detection tool — the point is to stop doing this by hand.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for the best results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't over-trim.&lt;/strong&gt; Leaving a small natural pause (150–300 ms) between sentences keeps speech sounding human. Cutting &lt;em&gt;every&lt;/em&gt; millisecond makes it feel rushed and robotic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tune the threshold to your recording.&lt;/strong&gt; A noisy room needs a higher silence threshold than a treated studio, or background hiss gets mistaken for speech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always keep your original file.&lt;/strong&gt; Trim a copy, so you can re-do it if you cut too aggressively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do silence removal first&lt;/strong&gt;, then your other edits (EQ, leveling, music) on the tightened file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Manually cutting silences is one of those tasks that adds zero creativity and eats enormous amounts of time. Automating it is one of the highest-leverage changes you can make to your editing workflow — you get shorter, tighter, more professional episodes, and you get your evenings back.&lt;/p&gt;

&lt;p&gt;If you want to try it on Windows, you can grab VoxCut at &lt;strong&gt;&lt;a href="https://voxcutpro.com" rel="noopener noreferrer"&gt;voxcutpro.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>python</category>
      <category>podcast</category>
    </item>
  </channel>
</rss>
