<?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: Troy Lorents</title>
    <description>The latest articles on DEV Community by Troy Lorents (@troyjl_).</description>
    <link>https://dev.to/troyjl_</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%2F3993380%2F184e88a6-2b36-4bbf-8f63-b370c6b8703b.jpg</url>
      <title>DEV Community: Troy Lorents</title>
      <link>https://dev.to/troyjl_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/troyjl_"/>
    <language>en</language>
    <item>
      <title>Shipping Async Video Background Removal at $0.10/sec</title>
      <dc:creator>Troy Lorents</dc:creator>
      <pubDate>Sun, 12 Jul 2026 03:55:04 +0000</pubDate>
      <link>https://dev.to/troyjl_/shipping-async-video-background-removal-at-010sec-1pa4</link>
      <guid>https://dev.to/troyjl_/shipping-async-video-background-removal-at-010sec-1pa4</guid>
      <description>&lt;h2&gt;
  
  
  Why async matters for video
&lt;/h2&gt;

&lt;p&gt;I've been running useKnockout - a background removal API that processes images in ~200ms - for a few months. Images are fast enough to handle synchronously: POST a file, wait 200ms, get a PNG back.&lt;/p&gt;

&lt;p&gt;Video is different. Even a 5-second clip at 30fps is 150 frames. At 200ms per frame, that's 30 seconds of processing. You can't hold an HTTP connection open for 30 seconds and call it a good API.&lt;/p&gt;

&lt;p&gt;So today I shipped &lt;code&gt;POST /video/remove&lt;/code&gt; - async video background removal that returns a job ID immediately, processes in the background, and gives you ProRes 4444 (RGB+alpha) when it's done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped
&lt;/h2&gt;

&lt;p&gt;As of v0.11.0 (July 10, 2026):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /video/remove&lt;/code&gt; - upload a video, get a job ID back&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /jobs/{job_id}&lt;/code&gt; - poll for status, download the result when ready&lt;/li&gt;
&lt;li&gt;ProRes 4444 output - RGB with full alpha channel, ready to drop into Premiere/Final Cut/DaVinci&lt;/li&gt;
&lt;li&gt;Node SDK &lt;code&gt;videoRemove()&lt;/code&gt; and &lt;code&gt;getJob()&lt;/code&gt; in v0.7.0&lt;/li&gt;
&lt;li&gt;Python SDK &lt;code&gt;video_remove()&lt;/code&gt; and &lt;code&gt;get_job()&lt;/code&gt; in v0.7.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Billing is a dedicated &lt;code&gt;video.seconds&lt;/code&gt; meter at &lt;strong&gt;$0.10/sec&lt;/strong&gt; (different from the per-image rate), with a 15-second cap to keep costs predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it (Node SDK)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useKnockout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;useknockout-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useKnockout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KNOCKOUT_API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Submit the video&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;videoRemove&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./input.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job ID:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Poll until done&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Download the ProRes 4444 result&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result_url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./output.mov&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job object includes &lt;code&gt;duration_seconds&lt;/code&gt; (billed amount), &lt;code&gt;status&lt;/code&gt; (&lt;code&gt;processing&lt;/code&gt;/&lt;code&gt;completed&lt;/code&gt;/&lt;code&gt;failed&lt;/code&gt;), and &lt;code&gt;result_url&lt;/code&gt; when done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Billing: $0.10/sec, 15s cap
&lt;/h2&gt;

&lt;p&gt;Video uses its own meter (&lt;code&gt;video.seconds&lt;/code&gt;) so you can track image vs. video usage separately. A 7-second clip costs $0.70. Clips longer than 15 seconds are trimmed (the 15s cap keeps runaway bills from killing indie projects).&lt;/p&gt;

&lt;p&gt;For context, useKnockout's image endpoint is ~$0.005/image on the starter tier - about 40x cheaper than remove.bg's $0.11-$0.23/image. Video is more expensive per second of processing, but still priced for solo builders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other recent additions
&lt;/h2&gt;

&lt;p&gt;Before video, I shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;POST /collage&lt;/strong&gt; (July 3) - N-photo product collages, billed N units. Pass 4 product shots, get a 2×2 grid back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RealESRGAN upscaling&lt;/strong&gt; (July 2) - default upscale switched to RealESRGAN with refined alpha matting and linear compositing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;studio-shot enhance&lt;/strong&gt; (June 22) - GFPGAN face restoration now available on &lt;code&gt;/studio-shot&lt;/code&gt; with &lt;code&gt;enhance=true&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these shipped in the last month because the API is self-hostable (MIT-licensed) and I'm the only one shipping it - no committee, no quarters, just daily commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Most indie devs I talk to are either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paying remove.bg [placeholder: $X/mo] and wincing at the bill, or&lt;/li&gt;
&lt;li&gt;Avoiding background removal features entirely because the APIs are too expensive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Video makes it worse - remove.bg doesn't do video at all. Other video APIs charge per frame (30fps × 5sec = 150 frames × $0.10 = $15 for 5 seconds).&lt;/p&gt;

&lt;p&gt;At $0.10/sec, that same 5-second clip costs $0.50. Still not free, but it's in "I can actually ship this feature" territory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm working on batch video (submit 10 clips, get 10 job IDs back) and a webhook callback option so you don't have to poll. The async job pattern is solid now - those are just API surface.&lt;/p&gt;

&lt;p&gt;If you're building something that needs video background removal (marketplace demos, avatar videos, TikTok filters, whatever), the SDKs are on npm/PyPI and the API is at &lt;strong&gt;useknockout.com&lt;/strong&gt;. Free tier includes 10 images/month across 5 endpoints. Video is pay-as-you-go.&lt;/p&gt;

&lt;p&gt;No lock-in - it's MIT-licensed if you want to self-host. But the hosted version is live and you can start with a curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://useknockout--api.modal.run/video/remove &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@input.mp4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get a job ID back. Poll &lt;code&gt;/jobs/{id}&lt;/code&gt; until it's done, then download the ProRes file. That's it.&lt;/p&gt;

</description>
      <category>api</category>
      <category>video</category>
      <category>node</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
