<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://echostorm44.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://echostorm44.github.io/" rel="alternate" type="text/html" /><updated>2026-07-24T18:25:38+00:00</updated><id>https://echostorm44.github.io/feed.xml</id><title type="html">Rum and Code</title><subtitle>Programming, musings, projects</subtitle><author><name>Echostorm</name></author><entry><title type="html">SharpImage</title><link href="https://echostorm44.github.io/2026/03/11/project-sharpimage.html" rel="alternate" type="text/html" title="SharpImage" /><published>2026-03-11T00:00:00+00:00</published><updated>2026-03-11T00:00:00+00:00</updated><id>https://echostorm44.github.io/2026/03/11/project-sharpimage</id><content type="html" xml:base="https://echostorm44.github.io/2026/03/11/project-sharpimage.html"><![CDATA[<p><a href="https://github.com/Echostorm44/SharpImage">Github page</a></p>

<h1 id="the-story-behind-sharpimage">The Story Behind SharpImage</h1>

<p>Every .NET developer who’s tried to do serious image processing has probably reached for ImageMagick at some point. I know I did. It’s the gold standard for a reason — decades of refinement, support for just about every format under the sun, and it just works. The team behind ImageMagick has done incredible work, and I want to be clear: SharpImage isn’t a criticism of what they’ve built. If anything, this project exists <em>because</em> of how much I respect what they created.</p>

<p>So why build it?</p>

<p>I wanted to bring that same capability to .NET — but without the native dependencies. No P/Invoke, no C/C++ bindings, no wrestling with native library distribution across different platforms and architectures. Just a NuGet package that works. Oh, and I was curious whether modern C# could actually compete with C on performance. Turns out, with .NET 10 and a few tricks, it can.</p>

<h2 id="what-it-does">What It Does</h2>

<p>SharpImage is a pure C# reimplementation of an image processing pipeline. Same formats, same operations, same quality but in C#. It started as a personal challenge, got a bit out of hand, and now it’s a library, a CLI tool, and there’s even a Avalonia-based Photoshop style example application if you want to poke around and see what is possible though I encourage you to make something a little nicer.</p>

<h2 id="formats">Formats</h2>

<p>Supporting 34 image formats covers most of what you’d encounter day-to-day, but I’m particularly happy with the camera raw support — CR2, NEF, ARW, DNG, and 27 other raw formats from various cameras. Proprietary formats are decode-only (because, well, they’re proprietary), but DNG supports both read and write. The demosaicing algorithms — Bilinear, VNG, and AHD — handle everything from quick previews to quality-priority processing.</p>

<p>The full list: PNG, JPEG, GIF, BMP, TGA, TIFF, WebP, ICO, AVIF, HEIC, JPEG 2000, JPEG XL, OpenEXR, PSD, DPX, Cineon, HDR, FITS, DICOM, QOI, Farbfeld, WBMP, PNM, PCX, XBM, XPM, DDS, SGI, PIX, SUN, SVG, PDF, and all those camera raw formats.</p>

<p>Beyond formats, there are 40+ colorspaces covered. Not just sRGB — things like Lab, OkLab, Jzazbz, and even HCLp for those who care about perceptually uniform color math.</p>

<h2 id="performance">Performance</h2>

<p>I’ll let the numbers do the talking here.</p>

<table>
  <thead>
    <tr>
      <th>Operation</th>
      <th>Size</th>
      <th>Time</th>
      <th>Memory</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Resize Lanczos3</td>
      <td>1024→512</td>
      <td>1.3 ms</td>
      <td>106 KB</td>
    </tr>
    <tr>
      <td>Resize Lanczos3 (upscale)</td>
      <td>1024→2048</td>
      <td>4.6 ms</td>
      <td>298 KB</td>
    </tr>
    <tr>
      <td>GaussianBlur σ=1.0</td>
      <td>512×512</td>
      <td>1.4 ms</td>
      <td>10 KB</td>
    </tr>
    <tr>
      <td>PNG Encode</td>
      <td>512×512</td>
      <td>7.2 ms</td>
      <td>37 KB</td>
    </tr>
    <tr>
      <td>PNG Decode</td>
      <td>512×512</td>
      <td>1.0 ms</td>
      <td>777 KB</td>
    </tr>
    <tr>
      <td>JPEG Encode Q85</td>
      <td>512×512</td>
      <td>10.0 ms</td>
      <td>73 KB</td>
    </tr>
    <tr>
      <td>JPEG Decode</td>
      <td>512×512</td>
      <td>9.8 ms</td>
      <td>3.4 MB</td>
    </tr>
    <tr>
      <td>Raw Demosaic Bilinear</td>
      <td>1024×1024</td>
      <td>5.2 ms</td>
      <td>644 B</td>
    </tr>
  </tbody>
</table>

<p>Benchmarks on .NET 10.0.3 with AVX-512, ShortRun via BenchmarkDotNet. The design is just .NET idioms done intentionally: <code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code> instead of array indexing, SIMD intrinsics via <code class="language-plaintext highlighter-rouge">Vector256</code>/<code class="language-plaintext highlighter-rouge">Vector128</code>, zero-allocation hot paths with <code class="language-plaintext highlighter-rouge">ArrayPool&lt;T&gt;</code>, and inner loops that let the JIT remove bounds checks.</p>

<h2 id="what-i-learned">What I Learned</h2>

<p>This was my first deep dive into writing performance-sensitive managed code at this scale. Getting to par and past with code written in C required rethinking how you structure pixel loops, how you lay out data in memory, and more than I want to get into here but proving it is possible, that C# isn’t the slow little brother anymore felt good.</p>

<h2 id="try-it">Try It</h2>

<p>If you want to play with it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet add package Echostorm.SharpImage
</code></pre></div></div>

<p>Or clone and build from the <a href="https://github.com/Echostorm44/SharpImage">GitHub repo</a>. There’s a CLI with about 228 commands, or use it as a library. The test suite has 1,600+ tests covering formats, colorspaces, transforms, and effects, plus integration tests that compare output against ImageMagick directly if you are curious.</p>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="sharpimage" /><category term="avalonia" /><category term="image" /><category term="codecs" /><category term="csharp" /><summary type="html"><![CDATA[Github page The Story Behind SharpImage Every .NET developer who’s tried to do serious image processing has probably reached for ImageMagick at some point. I know I did. It’s the gold standard for a reason — decades of refinement, support for just about every format under the sun, and it just works. The team behind ImageMagick has done incredible work, and I want to be clear: SharpImage isn’t a criticism of what they’ve built. If anything, this project exists because of how much I respect what they created. So why build it? I wanted to bring that same capability to .NET — but without the native dependencies. No P/Invoke, no C/C++ bindings, no wrestling with native library distribution across different platforms and architectures. Just a NuGet package that works. Oh, and I was curious whether modern C# could actually compete with C on performance. Turns out, with .NET 10 and a few tricks, it can. What It Does SharpImage is a pure C# reimplementation of an image processing pipeline. Same formats, same operations, same quality but in C#. It started as a personal challenge, got a bit out of hand, and now it’s a library, a CLI tool, and there’s even a Avalonia-based Photoshop style example application if you want to poke around and see what is possible though I encourage you to make something a little nicer. Formats Supporting 34 image formats covers most of what you’d encounter day-to-day, but I’m particularly happy with the camera raw support — CR2, NEF, ARW, DNG, and 27 other raw formats from various cameras. Proprietary formats are decode-only (because, well, they’re proprietary), but DNG supports both read and write. The demosaicing algorithms — Bilinear, VNG, and AHD — handle everything from quick previews to quality-priority processing. The full list: PNG, JPEG, GIF, BMP, TGA, TIFF, WebP, ICO, AVIF, HEIC, JPEG 2000, JPEG XL, OpenEXR, PSD, DPX, Cineon, HDR, FITS, DICOM, QOI, Farbfeld, WBMP, PNM, PCX, XBM, XPM, DDS, SGI, PIX, SUN, SVG, PDF, and all those camera raw formats. Beyond formats, there are 40+ colorspaces covered. Not just sRGB — things like Lab, OkLab, Jzazbz, and even HCLp for those who care about perceptually uniform color math. Performance I’ll let the numbers do the talking here. Operation Size Time Memory Resize Lanczos3 1024→512 1.3 ms 106 KB Resize Lanczos3 (upscale) 1024→2048 4.6 ms 298 KB GaussianBlur σ=1.0 512×512 1.4 ms 10 KB PNG Encode 512×512 7.2 ms 37 KB PNG Decode 512×512 1.0 ms 777 KB JPEG Encode Q85 512×512 10.0 ms 73 KB JPEG Decode 512×512 9.8 ms 3.4 MB Raw Demosaic Bilinear 1024×1024 5.2 ms 644 B Benchmarks on .NET 10.0.3 with AVX-512, ShortRun via BenchmarkDotNet. The design is just .NET idioms done intentionally: Span&lt;T&gt; instead of array indexing, SIMD intrinsics via Vector256/Vector128, zero-allocation hot paths with ArrayPool&lt;T&gt;, and inner loops that let the JIT remove bounds checks. What I Learned This was my first deep dive into writing performance-sensitive managed code at this scale. Getting to par and past with code written in C required rethinking how you structure pixel loops, how you lay out data in memory, and more than I want to get into here but proving it is possible, that C# isn’t the slow little brother anymore felt good. Try It If you want to play with it: dotnet add package Echostorm.SharpImage Or clone and build from the GitHub repo. There’s a CLI with about 228 commands, or use it as a library. The test suite has 1,600+ tests covering formats, colorspaces, transforms, and effects, plus integration tests that compare output against ImageMagick directly if you are curious.]]></summary></entry><entry><title type="html">SharpShare</title><link href="https://echostorm44.github.io/2026/03/01/project-sharpshare.html" rel="alternate" type="text/html" title="SharpShare" /><published>2026-03-01T00:00:00+00:00</published><updated>2026-03-01T00:00:00+00:00</updated><id>https://echostorm44.github.io/2026/03/01/project-sharpshare</id><content type="html" xml:base="https://echostorm44.github.io/2026/03/01/project-sharpshare.html"><![CDATA[<p><a href="https://github.com/Echostorm44/SharpShare">Github page</a></p>

<h1 id="sharpshare">SharpShare</h1>

<p>SharpShare came from a frustration in trying to share a bunch of large files regularly with user’s who are not technically savvy and just want it to work.  The project does the heavy lifting to open a port but keeps the entire process as secure and private as it can be.</p>

<p>The process is simple.</p>

<ul>
  <li>Both users start the applicaiton and select a folder to share. Nothing outside that folder is visible to the other party.</li>
  <li>One person decides to play host and the program discovers their IP and generates a secure pass phrase to give to the other party, the users are directly connected and all traffic between them is encrypted.</li>
  <li>Both users can see the contents of each other’s shared folders and can download anything from those folders they wish.</li>
</ul>

<p>The <a href="https://github.com/Echostorm44/SharpShare/releases/download/v1.0/SharpShare.zip">SharpShare.zip</a> file in Releases can simply be unzipped somewhere and run as a portable program, it has no requirements and there is no installer.</p>

<p><img width="1054" height="804" alt="image" src="https://github.com/user-attachments/assets/394f5bc5-ca86-4cd4-9bbf-adc8f2a6c71e" /></p>

<p>Security is fully detailed in <a href="https://github.com/Echostorm44/SharpShare/blob/master/Security.md">Security.md</a></p>
<h2 id="the-short-version">The Short Version</h2>

<ul>
  <li>Every session is protected by a <strong>passphrase only you and your peer know</strong>.</li>
  <li>All data travels over an <strong>encrypted tunnel</strong> — nobody in the middle can read your files.</li>
  <li>Both sides <strong>verify each other’s identity</strong> before any files are exchanged.</li>
  <li>The connection is <strong>direct, peer-to-peer</strong> — no third-party server ever sees your data.</li>
  <li>Repeated incorrect passphrases trigger <strong>automatic lockouts</strong> to stop guessing attacks.</li>
  <li>Every file received is <strong>integrity-checked</strong> to ensure it arrived without corruption or tampering.</li>
</ul>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="sharpshare" /><category term="avalonia" /><category term="file sharing" /><category term="p2p" /><category term="csharp" /><summary type="html"><![CDATA[Github page SharpShare SharpShare came from a frustration in trying to share a bunch of large files regularly with user’s who are not technically savvy and just want it to work. The project does the heavy lifting to open a port but keeps the entire process as secure and private as it can be. The process is simple. Both users start the applicaiton and select a folder to share. Nothing outside that folder is visible to the other party. One person decides to play host and the program discovers their IP and generates a secure pass phrase to give to the other party, the users are directly connected and all traffic between them is encrypted. Both users can see the contents of each other’s shared folders and can download anything from those folders they wish. The SharpShare.zip file in Releases can simply be unzipped somewhere and run as a portable program, it has no requirements and there is no installer. Security is fully detailed in Security.md The Short Version Every session is protected by a passphrase only you and your peer know. All data travels over an encrypted tunnel — nobody in the middle can read your files. Both sides verify each other’s identity before any files are exchanged. The connection is direct, peer-to-peer — no third-party server ever sees your data. Repeated incorrect passphrases trigger automatic lockouts to stop guessing attacks. Every file received is integrity-checked to ensure it arrived without corruption or tampering.]]></summary></entry><entry><title type="html">Nice Traceroute: A nice traceroute program</title><link href="https://echostorm44.github.io/2025/10/22/project-nice-traceroute.html" rel="alternate" type="text/html" title="Nice Traceroute: A nice traceroute program" /><published>2025-10-22T00:00:00+00:00</published><updated>2025-10-22T00:00:00+00:00</updated><id>https://echostorm44.github.io/2025/10/22/project-nice-traceroute</id><content type="html" xml:base="https://echostorm44.github.io/2025/10/22/project-nice-traceroute.html"><![CDATA[<p><a href="https://github.com/Echostorm44/NiceTraceRoute">Github page</a></p>

<h1 id="nicetraceroute">NiceTraceRoute</h1>

<p>I’ve had cause to use the tracert that comes with Windows over the past week and I found myself thinking, “This could be nicer”.  So I made a nice version that is better about letting you know that stuff is happening.  It is setup to AOT compile and trim so it doesn’t require any .net installed and weighs in at a flattering 3.5 MB.</p>

<p><img width="1249" height="579" alt="image" src="https://github.com/user-attachments/assets/b00b5fff-5711-47a3-b542-2833d3f2a056" /></p>

<p><img width="1074" height="592" alt="image" src="https://github.com/user-attachments/assets/10320294-9668-4821-b3a6-9484917845a1" /></p>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="nicetraceroute" /><category term="console" /><category term="csharp" /><summary type="html"><![CDATA[Github page NiceTraceRoute I’ve had cause to use the tracert that comes with Windows over the past week and I found myself thinking, “This could be nicer”. So I made a nice version that is better about letting you know that stuff is happening. It is setup to AOT compile and trim so it doesn’t require any .net installed and weighs in at a flattering 3.5 MB.]]></summary></entry><entry><title type="html">The Over Engineering Epidemic</title><link href="https://echostorm44.github.io/2025/09/07/rant.html" rel="alternate" type="text/html" title="The Over Engineering Epidemic" /><published>2025-09-07T00:00:00+00:00</published><updated>2025-09-07T00:00:00+00:00</updated><id>https://echostorm44.github.io/2025/09/07/rant</id><content type="html" xml:base="https://echostorm44.github.io/2025/09/07/rant.html"><![CDATA[<h2 id="how-interfaces-unit-tests-and-dependency-injection-are-drowning-net-applications">How Interfaces, Unit Tests, and Dependency Injection Are Drowning .NET Applications</h2>

<p>The .NET development community has fallen into a dangerous pattern of cargo cult programming. We’ve taken valuable tools—interfaces, unit tests, and dependency injection—and turned them into mandatory rituals, applied reflexively regardless of context or actual need. The result? Codebases that are bloated, performance-degraded, and paradoxically harder to understand and maintain than the “legacy” code we’re trying to improve.</p>

<h2 id="the-interface-explosion">The Interface Explosion</h2>

<p>Walk into any modern .NET codebase and you’ll find interfaces everywhere. <code class="language-plaintext highlighter-rouge">IUserService</code>, <code class="language-plaintext highlighter-rouge">IOrderRepository</code>, <code class="language-plaintext highlighter-rouge">IEmailSender</code>—each with exactly <strong>one implementation</strong>. This has become so normalized that developers create interfaces automatically, often before they’ve even written the concrete implementation.</p>

<p><strong>The stated justification:</strong> “It makes the code more testable and follows SOLID principles.”</p>

<p><strong>The reality:</strong> Most of these interfaces will never have a second implementation. They exist solely to satisfy a dependency injection container and enable mocking in unit tests. The cost is immediate and tangible:</p>

<ul>
  <li><strong>Cognitive overhead:</strong> Developers must now navigate two files instead of one for every service</li>
  <li><strong>IDE confusion:</strong> “Go to definition” lands on an empty interface instead of actual implementation</li>
  <li><strong>False abstractions:</strong> Interfaces that mirror their implementations 1:1 provide no real abstraction</li>
  <li><strong>Maintenance burden:</strong> Every method signature change requires updates in two places</li>
</ul>

<p>Consider this typical example:</p>

<p><img src="https://rumandcode.wordpress.com/wp-content/uploads/2025/07/image.png" alt="Interface Example" /></p>

<p>What value does this interface provide? If you need to swap out the user service implementation, you’ll likely need to change the method signatures anyway. The interface has become a ceremony, not a tool.</p>

<h2 id="the-unit-testing-obsession">The Unit Testing Obsession</h2>

<p>Unit testing has become a religious practice in .NET development, with coverage percentages treated as moral imperatives. Teams spend enormous effort mocking dependencies, setting up test scenarios, and maintaining test suites that often provide little real confidence in the application’s behavior.</p>

<p><strong>The stated justification:</strong> “Unit tests catch bugs early and enable refactoring.”</p>

<p><strong>The reality:</strong> Most unit tests are tightly coupled to implementation details, making them brittle and expensive to maintain. Consider this typical service test:</p>

<p><img src="https://rumandcode.wordpress.com/wp-content/uploads/2025/07/image-4.png" alt="Unit Test Example" /></p>

<p>This test requires intimate knowledge of the internal implementation. If you refactor the service to process payment and notification in parallel, or decide to update the order status before sending the notification, this test breaks—even though the external behavior is identical.</p>

<p>The test setup is complex, brittle, and tells us nothing about whether orders actually get processed correctly. It’s testing the orchestration of method calls, not the business logic. When this test passes, you still don’t know if:</p>

<ul>
  <li>The payment actually processes correctly</li>
  <li>The database transaction completes</li>
  <li>The email notification reaches the customer</li>
  <li>The order status is persisted correctly</li>
</ul>

<p>Meanwhile, integration tests that actually exercise the full stack and catch real bugs get dismissed as “too slow” or “too complicated.” The result is test suites with high coverage numbers but low bug-detection rates, and developers who spend more time maintaining test mocks than fixing actual issues.</p>

<h2 id="the-dependency-injection-maze">The Dependency Injection Maze</h2>

<p>Dependency injection has become the default approach for any object creation in .NET applications. Every service, repository, and helper class gets registered in the DI container, creating a web of dependencies that’s difficult to understand and debug.</p>

<p><strong>The stated justification:</strong> “It enables loose coupling and makes testing easier.”</p>

<p><strong>The reality:</strong> DI containers have become a black box where object creation happens magically, making it harder to understand what’s actually running. The performance cost is real:</p>

<ul>
  <li><strong>Memory overhead:</strong> The container must track all registrations and their lifetimes</li>
  <li><strong>CPU overhead:</strong> Runtime resolution is slower than compile-time instantiation</li>
  <li><strong>Startup time:</strong> Complex dependency graphs increase application startup time</li>
  <li><strong>Debugging complexity:</strong> Stack traces become cluttered with container resolution code</li>
</ul>

<p>Consider a simple controller that now requires half a dozen injected dependencies:</p>

<p><img src="https://rumandcode.wordpress.com/wp-content/uploads/2025/07/image-2.png" alt="Dependency Injection Example" /></p>

<p>This constructor is a maintenance nightmare. Adding a new dependency requires touching multiple files, and understanding what this controller actually does requires tracing through multiple service layers.</p>

<h2 id="the-performance-tax">The Performance Tax</h2>

<p>Each of these patterns carries a performance cost that accumulates across the application:</p>

<ul>
  <li><strong>Interface calls:</strong> Virtual method calls are slower than direct calls</li>
  <li><strong>DI resolution:</strong> Runtime object creation is slower than compile-time instantiation</li>
  <li><strong>Excessive abstraction:</strong> Multiple layers of services calling other services increase call stack depth</li>
  <li><strong>Memory allocation:</strong> More objects, more interfaces, more garbage collection pressure</li>
</ul>

<p>In a typical web application, these costs might seem negligible for individual requests, but they compound. A simple operation that could be a direct database call becomes a journey through multiple service layers, each with its own overhead.</p>

<h2 id="the-maintainability-paradox">The Maintainability Paradox</h2>

<p>The cruelest irony is that these patterns, adopted in the name of maintainability, often make code harder to maintain:</p>

<ul>
  <li><strong>Onboarding complexity:</strong> New developers must learn the DI container configuration, understand the interface hierarchy, and navigate the service layer maze</li>
  <li><strong>Debugging difficulty:</strong> Following code execution requires jumping between interfaces and implementations</li>
  <li><strong>Change resistance:</strong> Simple changes require updates to multiple interfaces, services, and tests</li>
  <li><strong>Analysis paralysis:</strong> Developers spend more time designing abstractions than solving business problems</li>
</ul>

<h2 id="a-more-pragmatic-approach">A More Pragmatic Approach</h2>

<p>This isn’t an argument against interfaces, testing, or dependency injection entirely—it’s a call for pragmatic application:</p>

<p><strong>Use interfaces when you actually need them:</strong></p>

<ul>
  <li>When you genuinely have multiple implementations</li>
  <li>When you’re defining a contract for external consumers</li>
  <li>When you need to isolate external dependencies for testing</li>
</ul>

<p><strong>Focus on integration tests:</strong></p>

<ul>
  <li>Test the behavior users actually care about</li>
  <li>Use unit tests sparingly for complex business logic</li>
  <li>Don’t test implementation details</li>
</ul>

<p><strong>Apply dependency injection judiciously:</strong></p>

<ul>
  <li>Use it for cross-cutting concerns (logging, configuration)</li>
  <li>Consider direct instantiation for simple object graphs</li>
  <li>Don’t inject everything just because you can</li>
</ul>

<p><strong>Embrace simplicity:</strong></p>

<ul>
  <li>Static classes aren’t evil for stateless operations</li>
  <li>Direct database access isn’t always wrong</li>
  <li>Simple, straightforward code is often the best code</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>The .NET community has created a culture where complexity is mistaken for sophistication, and over-engineering is rewarded as “best practice.” We’ve lost sight of the primary goal: building software that works reliably and can be understood and modified by the developers who come after us.</p>

<p>It’s time to step back and ask: Does this interface actually provide value? Does this test actually catch bugs? Does this abstraction actually make the code easier to change?</p>

<p>More often than not, the answer is no. It’s time to build simpler, more maintainable .NET applications by using the right tool for the job—not every tool we have available.</p>]]></content><author><name>Echostorm</name></author><summary type="html"><![CDATA[How Interfaces, Unit Tests, and Dependency Injection Are Drowning .NET Applications The .NET development community has fallen into a dangerous pattern of cargo cult programming. We’ve taken valuable tools—interfaces, unit tests, and dependency injection—and turned them into mandatory rituals, applied reflexively regardless of context or actual need. The result? Codebases that are bloated, performance-degraded, and paradoxically harder to understand and maintain than the “legacy” code we’re trying to improve. The Interface Explosion Walk into any modern .NET codebase and you’ll find interfaces everywhere. IUserService, IOrderRepository, IEmailSender—each with exactly one implementation. This has become so normalized that developers create interfaces automatically, often before they’ve even written the concrete implementation. The stated justification: “It makes the code more testable and follows SOLID principles.” The reality: Most of these interfaces will never have a second implementation. They exist solely to satisfy a dependency injection container and enable mocking in unit tests. The cost is immediate and tangible: Cognitive overhead: Developers must now navigate two files instead of one for every service IDE confusion: “Go to definition” lands on an empty interface instead of actual implementation False abstractions: Interfaces that mirror their implementations 1:1 provide no real abstraction Maintenance burden: Every method signature change requires updates in two places Consider this typical example: What value does this interface provide? If you need to swap out the user service implementation, you’ll likely need to change the method signatures anyway. The interface has become a ceremony, not a tool. The Unit Testing Obsession Unit testing has become a religious practice in .NET development, with coverage percentages treated as moral imperatives. Teams spend enormous effort mocking dependencies, setting up test scenarios, and maintaining test suites that often provide little real confidence in the application’s behavior. The stated justification: “Unit tests catch bugs early and enable refactoring.” The reality: Most unit tests are tightly coupled to implementation details, making them brittle and expensive to maintain. Consider this typical service test: This test requires intimate knowledge of the internal implementation. If you refactor the service to process payment and notification in parallel, or decide to update the order status before sending the notification, this test breaks—even though the external behavior is identical. The test setup is complex, brittle, and tells us nothing about whether orders actually get processed correctly. It’s testing the orchestration of method calls, not the business logic. When this test passes, you still don’t know if: The payment actually processes correctly The database transaction completes The email notification reaches the customer The order status is persisted correctly Meanwhile, integration tests that actually exercise the full stack and catch real bugs get dismissed as “too slow” or “too complicated.” The result is test suites with high coverage numbers but low bug-detection rates, and developers who spend more time maintaining test mocks than fixing actual issues. The Dependency Injection Maze Dependency injection has become the default approach for any object creation in .NET applications. Every service, repository, and helper class gets registered in the DI container, creating a web of dependencies that’s difficult to understand and debug. The stated justification: “It enables loose coupling and makes testing easier.” The reality: DI containers have become a black box where object creation happens magically, making it harder to understand what’s actually running. The performance cost is real: Memory overhead: The container must track all registrations and their lifetimes CPU overhead: Runtime resolution is slower than compile-time instantiation Startup time: Complex dependency graphs increase application startup time Debugging complexity: Stack traces become cluttered with container resolution code Consider a simple controller that now requires half a dozen injected dependencies: This constructor is a maintenance nightmare. Adding a new dependency requires touching multiple files, and understanding what this controller actually does requires tracing through multiple service layers. The Performance Tax Each of these patterns carries a performance cost that accumulates across the application: Interface calls: Virtual method calls are slower than direct calls DI resolution: Runtime object creation is slower than compile-time instantiation Excessive abstraction: Multiple layers of services calling other services increase call stack depth Memory allocation: More objects, more interfaces, more garbage collection pressure In a typical web application, these costs might seem negligible for individual requests, but they compound. A simple operation that could be a direct database call becomes a journey through multiple service layers, each with its own overhead. The Maintainability Paradox The cruelest irony is that these patterns, adopted in the name of maintainability, often make code harder to maintain: Onboarding complexity: New developers must learn the DI container configuration, understand the interface hierarchy, and navigate the service layer maze Debugging difficulty: Following code execution requires jumping between interfaces and implementations Change resistance: Simple changes require updates to multiple interfaces, services, and tests Analysis paralysis: Developers spend more time designing abstractions than solving business problems A More Pragmatic Approach This isn’t an argument against interfaces, testing, or dependency injection entirely—it’s a call for pragmatic application: Use interfaces when you actually need them: When you genuinely have multiple implementations When you’re defining a contract for external consumers When you need to isolate external dependencies for testing Focus on integration tests: Test the behavior users actually care about Use unit tests sparingly for complex business logic Don’t test implementation details Apply dependency injection judiciously: Use it for cross-cutting concerns (logging, configuration) Consider direct instantiation for simple object graphs Don’t inject everything just because you can Embrace simplicity: Static classes aren’t evil for stateless operations Direct database access isn’t always wrong Simple, straightforward code is often the best code Conclusion The .NET community has created a culture where complexity is mistaken for sophistication, and over-engineering is rewarded as “best practice.” We’ve lost sight of the primary goal: building software that works reliably and can be understood and modified by the developers who come after us. It’s time to step back and ask: Does this interface actually provide value? Does this test actually catch bugs? Does this abstraction actually make the code easier to change? More often than not, the answer is no. It’s time to build simpler, more maintainable .NET applications by using the right tool for the job—not every tool we have available.]]></summary></entry><entry><title type="html">Ripshout: Download Shoutcast Radio While You Listen</title><link href="https://echostorm44.github.io/2024/10/17/project-ripshout.html" rel="alternate" type="text/html" title="Ripshout: Download Shoutcast Radio While You Listen" /><published>2024-10-17T00:00:00+00:00</published><updated>2025-10-17T19:25:52+00:00</updated><id>https://echostorm44.github.io/2024/10/17/project-ripshout</id><content type="html" xml:base="https://echostorm44.github.io/2024/10/17/project-ripshout.html"><![CDATA[<p><a href="https://github.com/Echostorm44/RipShout?tab=readme-ov-file#ripshout">Github page</a></p>

<h1 id="ripshout">RipShout</h1>
<p><a href="https://github.com/Echostorm44/RipShout/releases"><img src="https://img.shields.io/badge/release-1.0.7-blue" /></a></p>

<p>A ShoutCast Internet Radio Stream Player, Ripper, Best Friend.</p>

<p>I love listening to Digitally Imported and RadioTunes radio while I’m working but like normal radio, I don’t always catch the name of a song that I like and am left with great sadness hoping it comes on again.  RipShout helps with this by recording all the music you hear all day, breaking it up in to nice MP3s with album art (if possible) and artist, album tags so you can play through the songs and find the one that you were rocking out to to buy on iTunes or whatever.</p>

<p>It also fetches fan art and album art and rotates it Zune style thereby making your music listening experience much more awesome. I hope you enjoy this as much as I do.</p>

<p><img src="https://user-images.githubusercontent.com/107306362/194548274-41ec723d-4e85-4d5f-afbd-6990cd88cd43.png" alt="MlaHA9ogrX" /></p>

<p><img src="https://user-images.githubusercontent.com/107306362/194548253-4218e54b-9f98-473e-ae4f-664a11b67ad2.png" alt="Ob1vDNLP9D" /></p>

<h1 id="installing">Installing</h1>

<p>You can install by simply <a href="https://github.com/Echostorm44/RipShout/releases/download/1.0.7/SetupRipShout.msi">downloading the msi</a> from the release page</p>

<p>Via <a href="https://github.com/microsoft/winget-cli">WinGet</a>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">winget</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">FleetingClarityStudios.RipShout</span><span class="w">
</span></code></pre></div></div>
<p><img src="https://user-images.githubusercontent.com/107306362/195997541-f801b404-5a84-4b76-8c86-1224b15c5b0d.png" alt="image" /></p>

<p><img src="https://user-images.githubusercontent.com/107306362/194763721-8dfd5f62-d999-4d55-a84c-d6b59fc34f81.png" alt="image" /></p>

<p><img src="https://user-images.githubusercontent.com/107306362/194763794-1b2580d3-bcab-440e-9fda-8fc8ad1538a9.png" alt="image" /></p>

<p><img src="https://user-images.githubusercontent.com/107306362/194763859-94ff63d7-cc42-4870-a163-64225f521cf7.png" alt="image" /></p>

<p><img src="https://user-images.githubusercontent.com/107306362/195997521-4b050dae-aaa2-48ff-9aa9-85e823a3b6ad.png" alt="image" /></p>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="ripshout" /><category term="wpf" /><category term="csharp" /><summary type="html"><![CDATA[Github page RipShout A ShoutCast Internet Radio Stream Player, Ripper, Best Friend. I love listening to Digitally Imported and RadioTunes radio while I’m working but like normal radio, I don’t always catch the name of a song that I like and am left with great sadness hoping it comes on again. RipShout helps with this by recording all the music you hear all day, breaking it up in to nice MP3s with album art (if possible) and artist, album tags so you can play through the songs and find the one that you were rocking out to to buy on iTunes or whatever. It also fetches fan art and album art and rotates it Zune style thereby making your music listening experience much more awesome. I hope you enjoy this as much as I do. Installing You can install by simply downloading the msi from the release page Via WinGet: winget install FleetingClarityStudios.RipShout]]></summary></entry><entry><title type="html">Peepr: A simple, lightweight image and video viewer</title><link href="https://echostorm44.github.io/2024/05/17/project-peepr.html" rel="alternate" type="text/html" title="Peepr: A simple, lightweight image and video viewer" /><published>2024-05-17T00:00:00+00:00</published><updated>2024-05-17T00:00:00+00:00</updated><id>https://echostorm44.github.io/2024/05/17/project-peepr</id><content type="html" xml:base="https://echostorm44.github.io/2024/05/17/project-peepr.html"><![CDATA[<p><a href="https://github.com/Echostorm44/Peepr">Github page</a></p>

<h1 id="peepr">Peepr</h1>
<p>Peepr is a simple image and video viewer for windows.  I created Peepr because I often have folders with a mish mash of images and videos of all formats and if I want to browse through them all at once I have to use an image viewer like ImageGlass and then open the videos with VLC and forget doing a slideshow with everything.</p>

<p>So I made Peepr to handle everything itself. It was also a good excuse to try building with AvaloniaUI which I recommend.</p>

<p><img src="https://github.com/user-attachments/assets/de3e7c0e-5ff5-4578-9b42-d063ec5e6dbf" alt="image" />
<img src="https://github.com/user-attachments/assets/ade44b13-ae94-4f76-b9d9-4cc2e7b062b4" alt="image" /></p>

<h2 id="features">Features</h2>
<ul>
  <li>Supports all common image formats</li>
  <li>Supports all common video formats (It uses VLC’s library)</li>
  <li>Navigate using arrow buttons, keyboard arrow keys or forward / back buttons on your mouse</li>
  <li>Slideshow! (Delay configurable)</li>
  <li>Escape to close</li>
  <li>Delete to delete file</li>
  <li>Optional associate Images and or Videos with Peepr</li>
</ul>

<h2 id="simplicity">Simplicity</h2>
<p>Peepr is designed to be simple. If you want to edit your images, use an image editor that is built for that.  Peepr is just for peeping. ;)</p>

<h2 id="installation">Installation</h2>
<p>In the release section either download the install.exe or the Peepr.zip
Both run as portable but the installer is just a little simpler and puts a shortcut on your desktop.
If you download the zip, extract it to a folder of your choice and run Peepr.exe</p>

<p>Download From <a href="https://github.com/Echostorm44/Peepr/releases">Releases</a></p>

<p><img src="https://github.com/user-attachments/assets/c4e24077-9de6-4227-9996-68ad9dfa3637" alt="image" /></p>

<p><img src="https://github.com/user-attachments/assets/941c1669-f215-496f-978f-a2151676e86a" alt="image" /></p>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="peepr" /><category term="avalonia" /><category term="csharp" /><summary type="html"><![CDATA[Github page Peepr Peepr is a simple image and video viewer for windows. I created Peepr because I often have folders with a mish mash of images and videos of all formats and if I want to browse through them all at once I have to use an image viewer like ImageGlass and then open the videos with VLC and forget doing a slideshow with everything. So I made Peepr to handle everything itself. It was also a good excuse to try building with AvaloniaUI which I recommend. Features Supports all common image formats Supports all common video formats (It uses VLC’s library) Navigate using arrow buttons, keyboard arrow keys or forward / back buttons on your mouse Slideshow! (Delay configurable) Escape to close Delete to delete file Optional associate Images and or Videos with Peepr Simplicity Peepr is designed to be simple. If you want to edit your images, use an image editor that is built for that. Peepr is just for peeping. ;) Installation In the release section either download the install.exe or the Peepr.zip Both run as portable but the installer is just a little simpler and puts a shortcut on your desktop. If you download the zip, extract it to a folder of your choice and run Peepr.exe Download From Releases]]></summary></entry><entry><title type="html">ClipClop: A clean and simple clipboard manager</title><link href="https://echostorm44.github.io/2024/03/17/project-clipclop.html" rel="alternate" type="text/html" title="ClipClop: A clean and simple clipboard manager" /><published>2024-03-17T00:00:00+00:00</published><updated>2024-03-17T00:00:00+00:00</updated><id>https://echostorm44.github.io/2024/03/17/project-clipclop</id><content type="html" xml:base="https://echostorm44.github.io/2024/03/17/project-clipclop.html"><![CDATA[<p><a href="https://github.com/Echostorm44/ClipClop">Github page</a></p>

<h1 id="clipclop">ClipClop</h1>
<h3 id="a-simple-and-clean-clipboard-manager">A Simple and Clean Clipboard Manager</h3>

<p><img src="https://github.com/user-attachments/assets/57aae39b-e337-4b7d-9d67-ae67d7834182" alt="image" /></p>

<p>ClipClop comes from a frustration that none of the available clipboard managers are exactly what I’d like.</p>

<p>Some are old and look dated or have networking and sharing options that make me nervous. 
Others don’t keep your pinned items at the top, or don’t let you resize them or don’t remember where you want them to open or don’t follow your mouse cursor and open on the monitor you’re looking at.</p>

<p>So I made my own, I hope you find it useful.</p>

<hr />

<ul>
  <li>ClipClop only saves your pinned items to disk and it encrypts the shit out of them when it does.</li>
  <li>ClipClop has a quick search to find that thing you know you copied earlier.  Just hit ‘s’ to jump to the search box and start typing.</li>
  <li>ClipClop it open source, free and doesn’t want you data, it stays local and encrypted so you don’t have to worry.</li>
  <li>ClipClop is configurable but simple. It will remember how you resized it and will open either in the same spot all the time or wherever your mouse is, you pick.</li>
  <li>ClipClop will keep your pinned items at the top, I can’t believe that is something we can brag about.</li>
  <li>ClipClop allows you to configure your own hotkey combination to open it.</li>
  <li>You can hit ‘escape’ to close the window if you forget why you opened it.</li>
  <li>Hitting ‘Clear All’ won’t delete your pined items, that wouldn’t be cool.</li>
</ul>

<hr />

<p><img src="https://github.com/user-attachments/assets/dfd9811a-9e48-4e60-9c81-dd8da70ce17b" alt="image" /></p>

<p>Download the latest from <a href="https://github.com/Echostorm44/ClipClop/releases">Releases</a></p>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="clipclop" /><category term="avalonia" /><category term="csharp" /><summary type="html"><![CDATA[Github page ClipClop A Simple and Clean Clipboard Manager ClipClop comes from a frustration that none of the available clipboard managers are exactly what I’d like. Some are old and look dated or have networking and sharing options that make me nervous. Others don’t keep your pinned items at the top, or don’t let you resize them or don’t remember where you want them to open or don’t follow your mouse cursor and open on the monitor you’re looking at. So I made my own, I hope you find it useful. ClipClop only saves your pinned items to disk and it encrypts the shit out of them when it does. ClipClop has a quick search to find that thing you know you copied earlier. Just hit ‘s’ to jump to the search box and start typing. ClipClop it open source, free and doesn’t want you data, it stays local and encrypted so you don’t have to worry. ClipClop is configurable but simple. It will remember how you resized it and will open either in the same spot all the time or wherever your mouse is, you pick. ClipClop will keep your pinned items at the top, I can’t believe that is something we can brag about. ClipClop allows you to configure your own hotkey combination to open it. You can hit ‘escape’ to close the window if you forget why you opened it. Hitting ‘Clear All’ won’t delete your pined items, that wouldn’t be cool. Download the latest from Releases]]></summary></entry><entry><title type="html">Ship That Code: .Net Installer Updater</title><link href="https://echostorm44.github.io/2024/02/17/project-shipthatcode.html" rel="alternate" type="text/html" title="Ship That Code: .Net Installer Updater" /><published>2024-02-17T00:00:00+00:00</published><updated>2025-10-17T19:25:52+00:00</updated><id>https://echostorm44.github.io/2024/02/17/project-shipthatcode</id><content type="html" xml:base="https://echostorm44.github.io/2024/02/17/project-shipthatcode.html"><![CDATA[<p><a href="https://github.com/Echostorm44/ShipThatCode">Github page</a></p>

<h1 id="shipthatcode">ShipThatCode</h1>
<p>A simple utility to create an installer and updater for .net projects without code changes using Github to host updates.</p>

<p><img src="https://github.com/Echostorm44/ShipThatCode/assets/107306362/949b09ca-1f17-4148-9a36-bb9766e8290a" alt="image" /></p>

<p>ShipThatCode is my version of ClickOnce.  It is crazy to me that all the options for creating installers for and keeping windows programs up to date are such a convoluted time consuming mess that are sometimes more work than the product itself.</p>

<p>Here is the use case. I have an application that runs on Windows that I want to create a simple but attractive installer for and to have it check for updates each time it launches without having to add any code to my application.</p>

<p>ShipThatCode builds 2 files.  An installer.exe and a zip that the launcher uses to update users on older versions.</p>

<p>installer.exe is a sort of self extrtracting archive that will</p>
<ol>
  <li>Extract your program to a temp folder</li>
  <li>Run the setup.exe which is included</li>
  <li>Show the user your EULA</li>
  <li>Ask them where they want to install</li>
  <li>Copy the files</li>
  <li>Create a proper shortcut on the desktop that points to an updater program that will look in your Github releases for the most recent version of the zip file which it will download and unzip over the local version.</li>
</ol>

<p>This is as simple for you the developer and the end user as I can make it.
All you have to do is fill out a couple fields and click “Generate Installer / Update Zip” and it will spit out 2 files</p>

<p><img src="https://github.com/Echostorm44/ShipThatCode/assets/107306362/c0996fe1-4706-4b83-8046-adf49a011dcf" alt="image" /></p>

<p>for you to upload to a Github Release</p>

<p><img src="https://github.com/Echostorm44/ShipThatCode/assets/107306362/e157eb54-7573-434b-b6ed-af39e192c44b" alt="image" /></p>

<p>The install process looks like this:</p>

<p><img src="https://github.com/Echostorm44/ShipThatCode/assets/107306362/9cbc45e1-5eb7-4a85-9c9d-1168a91c1574" alt="explorer_rw6dIxvlyC" /></p>

<p>It uses the icon you pointed out during setup to create a nice branded shortcut on the desktop that points to the launcher file that checks for updates before launching your exe
The launcher will delete all the files previously installed except any in the config.json in the semicolon delmited string property DoNotDeleteTheseFiles</p>

<p><img src="https://github.com/Echostorm44/ShipThatCode/assets/107306362/3e9b6c48-2f99-4241-8e14-308b1c0509ce" alt="image" /></p>

<p>The EULA.txt file it uses is in the SetupFiles folder, you can just paste your custom EULA in there or use the default.</p>

<p>I hope that this helps other devs with their projects and allows them to focus on writing cool apps instad of spending ages making installers and updaters.</p>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="shipthatcode" /><category term="wpf" /><category term="csharp" /><summary type="html"><![CDATA[Github page ShipThatCode A simple utility to create an installer and updater for .net projects without code changes using Github to host updates. ShipThatCode is my version of ClickOnce. It is crazy to me that all the options for creating installers for and keeping windows programs up to date are such a convoluted time consuming mess that are sometimes more work than the product itself. Here is the use case. I have an application that runs on Windows that I want to create a simple but attractive installer for and to have it check for updates each time it launches without having to add any code to my application. ShipThatCode builds 2 files. An installer.exe and a zip that the launcher uses to update users on older versions. installer.exe is a sort of self extrtracting archive that will Extract your program to a temp folder Run the setup.exe which is included Show the user your EULA Ask them where they want to install Copy the files Create a proper shortcut on the desktop that points to an updater program that will look in your Github releases for the most recent version of the zip file which it will download and unzip over the local version. This is as simple for you the developer and the end user as I can make it. All you have to do is fill out a couple fields and click “Generate Installer / Update Zip” and it will spit out 2 files for you to upload to a Github Release The install process looks like this: It uses the icon you pointed out during setup to create a nice branded shortcut on the desktop that points to the launcher file that checks for updates before launching your exe The launcher will delete all the files previously installed except any in the config.json in the semicolon delmited string property DoNotDeleteTheseFiles The EULA.txt file it uses is in the SetupFiles folder, you can just paste your custom EULA in there or use the default. I hope that this helps other devs with their projects and allows them to focus on writing cool apps instad of spending ages making installers and updaters.]]></summary></entry><entry><title type="html">Quick Fix My Pic: A simple image converter</title><link href="https://echostorm44.github.io/2024/01/17/project-quickfixmypic.html" rel="alternate" type="text/html" title="Quick Fix My Pic: A simple image converter" /><published>2024-01-17T00:00:00+00:00</published><updated>2024-01-17T00:00:00+00:00</updated><id>https://echostorm44.github.io/2024/01/17/project-quickfixmypic</id><content type="html" xml:base="https://echostorm44.github.io/2024/01/17/project-quickfixmypic.html"><![CDATA[<p><a href="https://github.com/Echostorm44/QuickFixMyPic">Github page</a></p>

<h1 id="quickfixmypic">QuickFixMyPic</h1>
<p><a href="https://github.com/Echostorm44/QuickFixMyPic/releases"><img src="https://img.shields.io/badge/release-1.0.3-blue" /></a></p>

<p>A simple utility for converting and resizing your pics</p>

<p><img src="https://user-images.githubusercontent.com/107306362/191941304-6a9259a0-3384-4bba-b0ea-29f04e4b707e.png" alt="image" /></p>

<p>The two most common operations I run into with image files are resizing and converting.  There are plenty of reasons to work on images in Gimp or Paint.Net or Photoshop but they’re not built to do simple batch operations like this quickly. QuickFixMyPic is.</p>

<p>You can right click on an image file or up to 16 in windows explorer to launch the program with them already queued.
<img src="https://user-images.githubusercontent.com/107306362/191943053-4c300155-4bef-4bff-805f-29b5d48c79c6.png" alt="image" /></p>

<p>You can add more to the queue by clicking browse or by going to File &gt; Browse For File(s) or Select Folder or simple the Browse button next to the Convert To dropdown list.</p>

<p><img src="https://user-images.githubusercontent.com/107306362/191943268-a8cb6116-cd07-4885-97b7-e8b115bf24f5.png" alt="image" /></p>

<p>When you are satified with your choices simple click Engage and your new files will appear in the same folder as the source files they are reading.</p>

<h1 id="installing">Installing</h1>

<p>You can install by simply <a href="https://github.com/Echostorm44/QuickFixMyPic/releases/download/1.0.3.0/SetupQuickFixMyPic.msi">downloading the msi</a> from the release page</p>

<p>You can download and unzip <a href="https://github.com/Echostorm44/QuickFixMyPic/releases/download/1.0.3.0/QuckFixMyPic.zip">this pack</a> and use is as portable but you won’t have the shell right click integration the installer adds</p>

<p>You could be all fancy and use a cli packager like the l33t haxx0r you are…</p>

<p>Via <a href="https://github.com/microsoft/winget-cli">WinGet</a>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">winget</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">FleetingClarityStudios.QuickFixMyPic</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="quickfixmypic" /><category term="wpf" /><category term="csharp" /><summary type="html"><![CDATA[Github page QuickFixMyPic A simple utility for converting and resizing your pics The two most common operations I run into with image files are resizing and converting. There are plenty of reasons to work on images in Gimp or Paint.Net or Photoshop but they’re not built to do simple batch operations like this quickly. QuickFixMyPic is. You can right click on an image file or up to 16 in windows explorer to launch the program with them already queued. You can add more to the queue by clicking browse or by going to File &gt; Browse For File(s) or Select Folder or simple the Browse button next to the Convert To dropdown list. When you are satified with your choices simple click Engage and your new files will appear in the same folder as the source files they are reading. Installing You can install by simply downloading the msi from the release page You can download and unzip this pack and use is as portable but you won’t have the shell right click integration the installer adds You could be all fancy and use a cli packager like the l33t haxx0r you are… Via WinGet: winget install FleetingClarityStudios.QuickFixMyPic]]></summary></entry><entry><title type="html">WhatHuh: Create subtitles for video files</title><link href="https://echostorm44.github.io/2023/02/17/project-wath.html" rel="alternate" type="text/html" title="WhatHuh: Create subtitles for video files" /><published>2023-02-17T00:00:00+00:00</published><updated>2023-02-17T00:00:00+00:00</updated><id>https://echostorm44.github.io/2023/02/17/project-wath</id><content type="html" xml:base="https://echostorm44.github.io/2023/02/17/project-wath.html"><![CDATA[<p><a href="https://github.com/Echostorm44/WhatHuh">Github page</a></p>

<h1 id="whathuh">WhatHuh</h1>

<p>Automatically Create Subtitles For Video Files</p>

<p>So if you have older movies or tv shows that are impossible to get subtitles for or you want to easily sub movies you create this is about as easy as it can be.</p>

<p>WhatHuh comes from the frustration of there not being an easy way to get or create subs so that I can still enjoy watching things despite my hearing loss.</p>

<p><img width="1310" height="585" alt="image" src="https://github.com/user-attachments/assets/3b41b37e-43b6-4b94-a481-4c0e60f6153b" /></p>

<p><img width="1504" height="1127" alt="image" src="https://github.com/user-attachments/assets/9cfd7da8-a8e4-4a7b-8a84-689847fbe63b" /></p>

<h2 id="features">Features</h2>

<ul>
  <li><strong>Cross-platform</strong> - AvaloniaUI GUI works on Windows, macOS, and Linux</li>
  <li><strong>CLI Support</strong> - Spectre.Console-powered command line interface for scripting</li>
  <li><strong>Voice Activity Detection (VAD)</strong> - Silero VAD filters out silence to reduce hallucinations</li>
  <li><strong>Multiple Whisper Backends</strong> - Automatically uses CUDA, Vulkan, CoreML, OpenVINO, or CPU</li>
  <li><strong>LLM Refinement</strong> - Optional Ollama integration to clean up transcripts</li>
  <li><strong>Advanced Audio Preprocessing</strong> - FFmpeg filters for noise reduction and normalization</li>
  <li><strong>Native AOT</strong> - Self-contained executables with no runtime dependency</li>
</ul>

<h2 id="requirements">Requirements</h2>

<ul>
  <li><strong><a href="https://ffmpeg.org/">FFmpeg</a></strong> - Must be installed and available in PATH</li>
  <li><strong><a href="https://ollama.com/">Ollama</a></strong> (optional) - For LLM transcript refinement</li>
</ul>

<h2 id="cli-usage">CLI Usage</h2>

<h3 id="commands">Commands</h3>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>Alias</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">transcribe &lt;input&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">-t</code></td>
      <td>Transcribe video files to SRT subtitles</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">setup</code></td>
      <td><code class="language-plaintext highlighter-rouge">-s</code></td>
      <td>Download and configure dependencies</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">info</code></td>
      <td><code class="language-plaintext highlighter-rouge">-i</code></td>
      <td>Display system and configuration info</td>
    </tr>
  </tbody>
</table>

<h3 id="transcribe-options">Transcribe Options</h3>

<table>
  <thead>
    <tr>
      <th>Option</th>
      <th>Alias</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--output</code></td>
      <td><code class="language-plaintext highlighter-rouge">-o</code></td>
      <td>(input).srt</td>
      <td>Output SRT file path</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--model</code></td>
      <td><code class="language-plaintext highlighter-rouge">-m</code></td>
      <td>base</td>
      <td>Whisper model size</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--language</code></td>
      <td><code class="language-plaintext highlighter-rouge">-l</code></td>
      <td>auto</td>
      <td>Language code or ‘auto’ for detection</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--no-vad</code></td>
      <td><code class="language-plaintext highlighter-rouge">-n</code></td>
      <td>false</td>
      <td>Disable Voice Activity Detection</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--refine</code></td>
      <td><code class="language-plaintext highlighter-rouge">-r</code></td>
      <td>false</td>
      <td>Enable LLM refinement via Ollama</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--llm-model</code></td>
      <td> </td>
      <td>phi3:mini</td>
      <td>LLM model for refinement</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--beam-size</code></td>
      <td><code class="language-plaintext highlighter-rouge">-b</code></td>
      <td>5</td>
      <td>Beam size for decoding (higher = better quality, slower)</td>
    </tr>
  </tbody>
</table>

<h3 id="setup-options">Setup Options</h3>

<table>
  <thead>
    <tr>
      <th>Option</th>
      <th>Alias</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--all</code></td>
      <td><code class="language-plaintext highlighter-rouge">-a</code></td>
      <td>Setup all dependencies</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--ffmpeg</code></td>
      <td><code class="language-plaintext highlighter-rouge">-f</code></td>
      <td>Check FFmpeg availability</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--vad</code></td>
      <td><code class="language-plaintext highlighter-rouge">-v</code></td>
      <td>Download Silero VAD model</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--whisper</code></td>
      <td><code class="language-plaintext highlighter-rouge">-w</code></td>
      <td>Download specified Whisper model</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--ollama</code></td>
      <td> </td>
      <td>Check Ollama availability</td>
    </tr>
  </tbody>
</table>

<h3 id="examples">Examples</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Transcribe a single file</span>
whathuh <span class="nt">-t</span> video.mp4

<span class="c"># Use large-v3 model for best accuracy</span>
whathuh <span class="nt">-t</span> video.mp4 <span class="nt">-m</span> large-v3

<span class="c"># English only, disable VAD</span>
whathuh <span class="nt">-t</span> video.mp4 <span class="nt">-l</span> en <span class="nt">-n</span>

<span class="c"># Transcribe entire folder with medium model</span>
whathuh <span class="nt">-t</span> <span class="s2">"C:</span><span class="se">\V</span><span class="s2">ideos"</span> <span class="nt">-m</span> medium

<span class="c"># Transcribe MKV files with LLM refinement</span>
whathuh <span class="nt">-t</span> <span class="k">*</span>.mkv <span class="nt">-r</span>

<span class="c"># Transcribe entire folder, use large-v3 model, English language, and LLM refinement with llama3.2</span>
whathuh <span class="nt">-t</span> F:<span class="se">\T</span>emp<span class="se">\ </span><span class="nt">-m</span> large-v3 <span class="nt">-l</span> en <span class="nt">-r</span> <span class="nt">--llm-model</span> llama3.2:latest

<span class="c"># Setup all dependencies</span>
whathuh <span class="nt">-s</span> <span class="nt">-a</span>

<span class="c"># Download large-v3 model</span>
whathuh <span class="nt">-s</span> <span class="nt">-w</span> large-v3

<span class="c"># Show system info</span>
whathuh <span class="nt">-i</span>
</code></pre></div></div>

<h2 id="whisper-models">Whisper Models</h2>

<p>This project uses OpenAI’s Whisper model to create the subtitles. There are several model options:</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Base</td>
      <td>Fast and surprisingly accurate for most use cases</td>
    </tr>
    <tr>
      <td>Base-en</td>
      <td>Base with special English training</td>
    </tr>
    <tr>
      <td>Small</td>
      <td>Better accuracy, more resources</td>
    </tr>
    <tr>
      <td>Medium</td>
      <td>High accuracy, significant resources</td>
    </tr>
    <tr>
      <td>Large-v3</td>
      <td>Best accuracy, requires significant VRAM</td>
    </tr>
  </tbody>
</table>

<p><strong>Use Large v3 If You Can</strong> - Models are downloaded automatically on first use.</p>

<h2 id="runtime-selection">Runtime Selection</h2>

<p>Whisper.net automatically selects the best available runtime:</p>

<ol>
  <li><strong><a href="https://developer.nvidia.com/cuda-13-0-0-download-archive">CUDA</a></strong> - NVidia GPUs with CUDA drivers
 Requires <a href="https://developer.nvidia.com/cuda-13-0-0-download-archive">CUDA</a> 13.1 or later and updated NVIDIA drivers!</li>
  <li><strong>Vulkan</strong> - Vulkan support</li>
  <li><strong>CoreML</strong> - Apple Silicon Macs</li>
  <li><strong>OpenVINO</strong> - Intel hardware acceleration</li>
  <li><strong>CPU</strong> - Universal fallback</li>
</ol>

<h2 id="voice-activity-detection">Voice Activity Detection</h2>

<p>The Silero VAD integration filters out silence and non-speech audio before transcription. This:</p>
<ul>
  <li>Reduces Whisper hallucinations during silence</li>
  <li>Speeds up processing by skipping non-speech segments</li>
  <li>Improves overall transcription quality</li>
</ul>

<h2 id="llm-refinement-optional">LLM Refinement (Optional)</h2>

<p>For possibly better results, enable LLM refinement with Ollama.
I really need to stress that this isn’t always perfect and results vary widely based on model and on the text it is working with.</p>

<p>With that said:</p>

<ol>
  <li>Install <a href="https://ollama.ai">Ollama</a></li>
  <li>Pull a model: <code class="language-plaintext highlighter-rouge">ollama pull phi3:mini</code></li>
  <li>Enable refinement in GUI or use <code class="language-plaintext highlighter-rouge">--refine</code> in CLI</li>
</ol>

<p>The LLM corrects:</p>
<ul>
  <li>Spelling and grammar errors</li>
  <li>Disfluencies (um, uh, you know)</li>
  <li>Acronym capitalization</li>
  <li>Homophones</li>
</ul>

<h2 id="building-from-source">Building from Source</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Clone the repository</span>
git clone https://github.com/Echostorm44/WhatHuh.git
<span class="nb">cd </span>WhatHuh

<span class="c"># Build all projects</span>
dotnet build

<span class="c"># Run GUI</span>
dotnet run <span class="nt">--project</span> src/WhatHuh.Gui

<span class="c"># Run CLI</span>
dotnet run <span class="nt">--project</span> src/WhatHuh.Cli <span class="nt">--</span> <span class="nt">--help</span>
</code></pre></div></div>

<h2 id="credits">Credits</h2>

<ul>
  <li><a href="https://github.com/sandrohanea/whisper.net">Whisper.net</a> by Sandro Hanea</li>
  <li><a href="https://github.com/snakers4/silero-vad">Silero VAD</a> by Silero Team</li>
  <li><a href="https://spectreconsole.net/">Spectre.Console</a></li>
  <li><a href="https://avaloniaui.net/">AvaloniaUI</a></li>
  <li>Image icons by rsetiawan - Flaticon</li>
</ul>

<h2 id="license">License</h2>

<p>MIT License - See LICENSE for details</p>

<p>Download it from <a href="https://github.com/Echostorm44/WhatHuh/releases">Releases</a></p>]]></content><author><name>Echostorm</name></author><category term="Projects" /><category term="whathuh" /><category term="wpf" /><category term="csharp" /><summary type="html"><![CDATA[Github page WhatHuh Automatically Create Subtitles For Video Files So if you have older movies or tv shows that are impossible to get subtitles for or you want to easily sub movies you create this is about as easy as it can be. WhatHuh comes from the frustration of there not being an easy way to get or create subs so that I can still enjoy watching things despite my hearing loss. Features Cross-platform - AvaloniaUI GUI works on Windows, macOS, and Linux CLI Support - Spectre.Console-powered command line interface for scripting Voice Activity Detection (VAD) - Silero VAD filters out silence to reduce hallucinations Multiple Whisper Backends - Automatically uses CUDA, Vulkan, CoreML, OpenVINO, or CPU LLM Refinement - Optional Ollama integration to clean up transcripts Advanced Audio Preprocessing - FFmpeg filters for noise reduction and normalization Native AOT - Self-contained executables with no runtime dependency Requirements FFmpeg - Must be installed and available in PATH Ollama (optional) - For LLM transcript refinement CLI Usage Commands Command Alias Description transcribe &lt;input&gt; -t Transcribe video files to SRT subtitles setup -s Download and configure dependencies info -i Display system and configuration info Transcribe Options Option Alias Default Description --output -o (input).srt Output SRT file path --model -m base Whisper model size --language -l auto Language code or ‘auto’ for detection --no-vad -n false Disable Voice Activity Detection --refine -r false Enable LLM refinement via Ollama --llm-model   phi3:mini LLM model for refinement --beam-size -b 5 Beam size for decoding (higher = better quality, slower) Setup Options Option Alias Description --all -a Setup all dependencies --ffmpeg -f Check FFmpeg availability --vad -v Download Silero VAD model --whisper -w Download specified Whisper model --ollama   Check Ollama availability Examples # Transcribe a single file whathuh -t video.mp4 # Use large-v3 model for best accuracy whathuh -t video.mp4 -m large-v3 # English only, disable VAD whathuh -t video.mp4 -l en -n # Transcribe entire folder with medium model whathuh -t "C:\Videos" -m medium # Transcribe MKV files with LLM refinement whathuh -t *.mkv -r # Transcribe entire folder, use large-v3 model, English language, and LLM refinement with llama3.2 whathuh -t F:\Temp\ -m large-v3 -l en -r --llm-model llama3.2:latest # Setup all dependencies whathuh -s -a # Download large-v3 model whathuh -s -w large-v3 # Show system info whathuh -i Whisper Models This project uses OpenAI’s Whisper model to create the subtitles. There are several model options: Model Description Base Fast and surprisingly accurate for most use cases Base-en Base with special English training Small Better accuracy, more resources Medium High accuracy, significant resources Large-v3 Best accuracy, requires significant VRAM Use Large v3 If You Can - Models are downloaded automatically on first use. Runtime Selection Whisper.net automatically selects the best available runtime: CUDA - NVidia GPUs with CUDA drivers Requires CUDA 13.1 or later and updated NVIDIA drivers! Vulkan - Vulkan support CoreML - Apple Silicon Macs OpenVINO - Intel hardware acceleration CPU - Universal fallback Voice Activity Detection The Silero VAD integration filters out silence and non-speech audio before transcription. This: Reduces Whisper hallucinations during silence Speeds up processing by skipping non-speech segments Improves overall transcription quality LLM Refinement (Optional) For possibly better results, enable LLM refinement with Ollama. I really need to stress that this isn’t always perfect and results vary widely based on model and on the text it is working with. With that said: Install Ollama Pull a model: ollama pull phi3:mini Enable refinement in GUI or use --refine in CLI The LLM corrects: Spelling and grammar errors Disfluencies (um, uh, you know) Acronym capitalization Homophones Building from Source # Clone the repository git clone https://github.com/Echostorm44/WhatHuh.git cd WhatHuh # Build all projects dotnet build # Run GUI dotnet run --project src/WhatHuh.Gui # Run CLI dotnet run --project src/WhatHuh.Cli -- --help Credits Whisper.net by Sandro Hanea Silero VAD by Silero Team Spectre.Console AvaloniaUI Image icons by rsetiawan - Flaticon License MIT License - See LICENSE for details Download it from Releases]]></summary></entry></feed>