{"id":3550,"date":"2026-07-12T04:06:15","date_gmt":"2026-07-12T04:06:15","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/07\/12\/shipping-async-video-background-removal-at-0-10-sec\/"},"modified":"2026-07-12T04:06:15","modified_gmt":"2026-07-12T04:06:15","slug":"shipping-async-video-background-removal-at-0-10-sec","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/07\/12\/shipping-async-video-background-removal-at-0-10-sec\/","title":{"rendered":"Shipping Async Video Background Removal at $0.10\/sec"},"content":{"rendered":"<div>\n<div>\n<div data-article-id=\"4122408\" id=\"article-body\">\n<h2> <a name=\"why-async-matters-for-video\" href=\"#why-async-matters-for-video\"> <\/a> Why async matters for video <\/h2>\n<p>I&#8217;ve been running useKnockout &#8211; a background removal API that processes images in ~200ms &#8211; for a few months. Images are fast enough to handle synchronously: POST a file, wait 200ms, get a PNG back.<\/p>\n<p>Video is different. Even a 5-second clip at 30fps is 150 frames. At 200ms per frame, that&#8217;s 30 seconds of processing. You can&#8217;t hold an HTTP connection open for 30 seconds and call it a good API.<\/p>\n<p>So today I shipped <code>POST \/video\/remove<\/code> &#8211; async video background removal that returns a job ID immediately, processes in the background, and gives you ProRes 4444 (RGB+alpha) when it&#8217;s done.<\/p>\n<h2> <a name=\"what-shipped\" href=\"#what-shipped\"> <\/a> What shipped <\/h2>\n<p>As of v0.11.0 (July 10, 2026):<\/p>\n<ul>\n<li> <code>POST \/video\/remove<\/code> &#8211; upload a video, get a job ID back<\/li>\n<li> <code>GET \/jobs\/{job_id}<\/code> &#8211; poll for status, download the result when ready<\/li>\n<li>ProRes 4444 output &#8211; RGB with full alpha channel, ready to drop into Premiere\/Final Cut\/DaVinci<\/li>\n<li>Node SDK <code>videoRemove()<\/code> and <code>getJob()<\/code> in v0.7.0<\/li>\n<li>Python SDK <code>video_remove()<\/code> and <code>get_job()<\/code> in v0.7.0<\/li>\n<\/ul>\n<p>Billing is a dedicated <code>video.seconds<\/code> meter at <strong>$0.10\/sec<\/strong> (different from the per-image rate), with a 15-second cap to keep costs predictable.<\/p>\n<h2> <a name=\"how-to-use-it-node-sdk\" href=\"#how-to-use-it-node-sdk\"> <\/a> How to use it (Node SDK) <\/h2>\n<div>\n<pre><code><span>import<\/span> <span>{<\/span> <span>useKnockout<\/span> <span>}<\/span> <span>from<\/span> <span>'<\/span><span>useknockout-node<\/span><span>'<\/span><span>;<\/span> <span>import<\/span> <span>fs<\/span> <span>from<\/span> <span>'<\/span><span>fs<\/span><span>'<\/span><span>;<\/span> <span>const<\/span> <span>client<\/span> <span>=<\/span> <span>useKnockout<\/span><span>({<\/span> <span>apiKey<\/span><span>:<\/span> <span>process<\/span><span>.<\/span><span>env<\/span><span>.<\/span><span>KNOCKOUT_API_KEY<\/span> <span>});<\/span> <span>\/\/ Submit the video<\/span> <span>const<\/span> <span>job<\/span> <span>=<\/span> <span>await<\/span> <span>client<\/span><span>.<\/span><span>videoRemove<\/span><span>({<\/span> <span>file<\/span><span>:<\/span> <span>fs<\/span><span>.<\/span><span>createReadStream<\/span><span>(<\/span><span>'<\/span><span>.\/input.mp4<\/span><span>'<\/span><span>)<\/span> <span>});<\/span> <span>console<\/span><span>.<\/span><span>log<\/span><span>(<\/span><span>'<\/span><span>Job ID:<\/span><span>'<\/span><span>,<\/span> <span>job<\/span><span>.<\/span><span>id<\/span><span>);<\/span> <span>\/\/ Poll until done<\/span> <span>let<\/span> <span>status<\/span> <span>=<\/span> <span>await<\/span> <span>client<\/span><span>.<\/span><span>getJob<\/span><span>(<\/span><span>job<\/span><span>.<\/span><span>id<\/span><span>);<\/span> <span>while <\/span><span>(<\/span><span>status<\/span><span>.<\/span><span>status<\/span> <span>===<\/span> <span>'<\/span><span>processing<\/span><span>'<\/span><span>)<\/span> <span>{<\/span> <span>await<\/span> <span>new<\/span> <span>Promise<\/span><span>(<\/span><span>resolve<\/span> <span>=&gt;<\/span> <span>setTimeout<\/span><span>(<\/span><span>resolve<\/span><span>,<\/span> <span>2000<\/span><span>));<\/span> <span>status<\/span> <span>=<\/span> <span>await<\/span> <span>client<\/span><span>.<\/span><span>getJob<\/span><span>(<\/span><span>job<\/span><span>.<\/span><span>id<\/span><span>);<\/span> <span>}<\/span> <span>if <\/span><span>(<\/span><span>status<\/span><span>.<\/span><span>status<\/span> <span>===<\/span> <span>'<\/span><span>completed<\/span><span>'<\/span><span>)<\/span> <span>{<\/span> <span>\/\/ Download the ProRes 4444 result<\/span> <span>const<\/span> <span>video<\/span> <span>=<\/span> <span>await<\/span> <span>fetch<\/span><span>(<\/span><span>status<\/span><span>.<\/span><span>result_url<\/span><span>);<\/span> <span>const<\/span> <span>buffer<\/span> <span>=<\/span> <span>await<\/span> <span>video<\/span><span>.<\/span><span>arrayBuffer<\/span><span>();<\/span> <span>fs<\/span><span>.<\/span><span>writeFileSync<\/span><span>(<\/span><span>'<\/span><span>.\/output.mov<\/span><span>'<\/span><span>,<\/span> <span>Buffer<\/span><span>.<\/span><span>from<\/span><span>(<\/span><span>buffer<\/span><span>));<\/span> <span>}<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>The job object includes <code>duration_seconds<\/code> (billed amount), <code>status<\/code> (<code>processing<\/code>\/<code>completed<\/code>\/<code>failed<\/code>), and <code>result_url<\/code> when done.<\/p>\n<h2> <a name=\"billing-010sec-15s-cap\" href=\"#billing-010sec-15s-cap\"> <\/a> Billing: $0.10\/sec, 15s cap <\/h2>\n<p>Video uses its own meter (<code>video.seconds<\/code>) 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).<\/p>\n<p>For context, useKnockout&#8217;s image endpoint is ~$0.005\/image on the starter tier &#8211; about 40x cheaper than remove.bg&#8217;s $0.11-$0.23\/image. Video is more expensive per second of processing, but still priced for solo builders.<\/p>\n<h2> <a name=\"other-recent-additions\" href=\"#other-recent-additions\"> <\/a> Other recent additions <\/h2>\n<p>Before video, I shipped:<\/p>\n<ul>\n<li> <strong>POST \/collage<\/strong> (July 3) &#8211; N-photo product collages, billed N units. Pass 4 product shots, get a 2\u00d72 grid back.<\/li>\n<li> <strong>RealESRGAN upscaling<\/strong> (July 2) &#8211; default upscale switched to RealESRGAN with refined alpha matting and linear compositing.<\/li>\n<li> <strong>studio-shot enhance<\/strong> (June 22) &#8211; GFPGAN face restoration now available on <code>\/studio-shot<\/code> with <code>enhance=true<\/code>.<\/li>\n<\/ul>\n<p>All of these shipped in the last month because the API is self-hostable (MIT-licensed) and I&#8217;m the only one shipping it &#8211; no committee, no quarters, just daily commits.<\/p>\n<h2> <a name=\"why-this-matters\" href=\"#why-this-matters\"> <\/a> Why this matters <\/h2>\n<p>Most indie devs I talk to are either:<\/p>\n<ol>\n<li>Paying remove.bg [placeholder: $X\/mo] and wincing at the bill, or<\/li>\n<li>Avoiding background removal features entirely because the APIs are too expensive.<\/li>\n<\/ol>\n<p>Video makes it worse &#8211; remove.bg doesn&#8217;t do video at all. Other video APIs charge per frame (30fps \u00d7 5sec = 150 frames \u00d7 $0.10 = $15 for 5 seconds).<\/p>\n<p>At $0.10\/sec, that same 5-second clip costs $0.50. Still not free, but it&#8217;s in &#8220;I can actually ship this feature&#8221; territory.<\/p>\n<h2> <a name=\"whats-next\" href=\"#whats-next\"> <\/a> What&#8217;s next <\/h2>\n<p>I&#8217;m working on batch video (submit 10 clips, get 10 job IDs back) and a webhook callback option so you don&#8217;t have to poll. The async job pattern is solid now &#8211; those are just API surface.<\/p>\n<p>If you&#8217;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 <strong>useknockout.com<\/strong>. Free tier includes 10 images\/month across 5 endpoints. Video is pay-as-you-go.<\/p>\n<p>No lock-in &#8211; it&#8217;s MIT-licensed if you want to self-host. But the hosted version is live and you can start with a curl: <\/p>\n<div>\n<pre><code>curl <span>-X<\/span> POST https:\/\/useknockout--api.modal.run\/video\/remove <span>\\<\/span> <span>-H<\/span> <span>\"Authorization: Bearer YOUR_API_KEY\"<\/span> <span>\\<\/span> <span>-F<\/span> <span>\"file=@input.mp4\"<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>You&#8217;ll get a job ID back. Poll <code>\/jobs\/{id}<\/code> until it&#8217;s done, then download the ProRes file. That&#8217;s it.<\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/troyjl_\/shipping-async-video-background-removal-at-010sec-1pa4\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Why async matters for video I&#8217;ve been running useKnockout &#8211; a background removal API that processes images in ~200ms &#8211; for a few months. Images are fast enough to handle synchronously: POST a file, wait 200ms, get a PNG back. Video is different. Even a 5-second clip at 30fps is 150 frames. At 200ms per [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2648,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[41],"tags":[],"class_list":["post-3550","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/3550","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=3550"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/3550\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/2648"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=3550"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=3550"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=3550"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}