<?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: Chống Chí Dinh</title>
    <description>The latest articles on DEV Community by Chống Chí Dinh (@ddinhcchi).</description>
    <link>https://dev.to/ddinhcchi</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%2F3986594%2F530ae244-5c83-4eb5-9ad2-af573dd5c815.png</url>
      <title>DEV Community: Chống Chí Dinh</title>
      <link>https://dev.to/ddinhcchi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ddinhcchi"/>
    <language>en</language>
    <item>
      <title>Why Every Computer Vision Team Ends Up Rewriting the Same Video Clip Pipeline</title>
      <dc:creator>Chống Chí Dinh</dc:creator>
      <pubDate>Tue, 16 Jun 2026 04:42:23 +0000</pubDate>
      <link>https://dev.to/ddinhcchi/why-every-computer-vision-team-ends-up-rewriting-the-same-video-clip-pipeline-1pb1</link>
      <guid>https://dev.to/ddinhcchi/why-every-computer-vision-team-ends-up-rewriting-the-same-video-clip-pipeline-1pb1</guid>
      <description>&lt;h2&gt;
  
  
  Shipping Evidence Clips for Computer Vision Events
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;If you've shipped a computer vision system to production, you know the moment.&lt;/p&gt;

&lt;p&gt;The detector fires. The alert fires. And then someone on ops opens the alert, sees:&lt;/p&gt;


&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"violation_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1716530001.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;and replies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"OK, where's the video?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the gap this post is about.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Actual Problem
&lt;/h2&gt;

&lt;p&gt;What ops wants is a short MP4 — typically 10–30 seconds — with the bounding box drawn on top of the relevant footage, so they can open it in QuickTime or VLC and forward it to whoever needs to see it.&lt;/p&gt;

&lt;p&gt;Not a JSON sidecar.&lt;/p&gt;

&lt;p&gt;Not a frame extraction.&lt;/p&gt;

&lt;p&gt;A short video with the box visibly on the suspect.&lt;/p&gt;

&lt;p&gt;Building this turns out to be a chain of small problems, each of which is "fine, I'll just do it myself":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the source video (RTSP feed or saved file).&lt;/li&gt;
&lt;li&gt;Seek to the event window.&lt;/li&gt;
&lt;li&gt;Decode the frames.&lt;/li&gt;
&lt;li&gt;Look up the detection for each frame.&lt;/li&gt;
&lt;li&gt;Draw the box and label cleanly.&lt;/li&gt;
&lt;li&gt;Encode the result to MP4.&lt;/li&gt;
&lt;li&gt;Handle the case where the event spans two files because your NVR cuts recordings at the hour boundary.&lt;/li&gt;
&lt;li&gt;Handle the case where your operator wants ten events from a half-day recording without waiting half a day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every CV team I've worked with hand-rolls this pipeline once, ships the off-by-one to production, then writes it again on the next project.&lt;/p&gt;

&lt;p&gt;Three months later, someone breaks the FFmpeg subprocess invocation and nobody notices for two weeks because the smoke test only checks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Did a file get written?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What's Actually Available
&lt;/h2&gt;

&lt;p&gt;Three things come close.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supervision
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/roboflow/supervision" rel="noopener noreferrer"&gt;supervision&lt;/a&gt; is an excellent drawing-utilities library (39k+ GitHub stars).&lt;/p&gt;

&lt;p&gt;But its &lt;code&gt;VideoSink&lt;/code&gt; is essentially &lt;code&gt;cv2.VideoWriter&lt;/code&gt; with &lt;code&gt;mp4v&lt;/code&gt; hard-coded.&lt;/p&gt;

&lt;p&gt;It has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No event-window trimming&lt;/li&gt;
&lt;li&gt;No codec selection&lt;/li&gt;
&lt;li&gt;No concept of events spanning multiple files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a tool for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Annotate every frame and write the whole thing back out."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  DeepStream Smart Record
&lt;/h3&gt;

&lt;p&gt;DeepStream Smart Record is NVIDIA's official solution.&lt;/p&gt;

&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;In C.&lt;/p&gt;

&lt;p&gt;The official Python bindings (&lt;code&gt;pyds&lt;/code&gt;) still don't expose Smart Record functionality. NVIDIA staff have confirmed this on their forums, and that situation remained unchanged through DeepStream 7.1.&lt;/p&gt;

&lt;p&gt;There are community forks that provide custom wheels as a workaround.&lt;/p&gt;

&lt;p&gt;Smart Record itself also has open reports involving multi-stream crashes on DS 7.1.&lt;/p&gt;

&lt;p&gt;If you're already inside a DeepStream pipeline, it can be a good option.&lt;/p&gt;

&lt;p&gt;If you're not, you may end up learning DeepStream just to produce an MP4 clip.&lt;/p&gt;




&lt;h3&gt;
  
  
  KeyClipWriter
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://pyimagesearch.com/2016/02/29/saving-key-event-video-clips-with-opencv/" rel="noopener noreferrer"&gt;KeyClipWriter from PyImageSearch&lt;/a&gt; is the ring-buffer pattern everyone copies.&lt;/p&gt;

&lt;p&gt;It's a tutorial, not a maintained library.&lt;/p&gt;

&lt;p&gt;It's detection-agnostic, so you wire up all overlay logic yourself.&lt;/p&gt;

&lt;p&gt;The trim semantics are roughly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Whatever OpenCV ends up doing."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;So the landscape looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A popular drawing library that doesn't ship clips&lt;/li&gt;
&lt;li&gt;A vendor SDK with limited Python support&lt;/li&gt;
&lt;li&gt;A fifteen-year-old tutorial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap is real.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Library Version of That Pattern
&lt;/h2&gt;

&lt;p&gt;I wrote &lt;strong&gt;cv-evidence-renderer&lt;/strong&gt; to be the library version of what every team eventually hand-rolls.&lt;/p&gt;

&lt;p&gt;MIT licensed.&lt;/p&gt;

&lt;p&gt;Pure Python install.&lt;/p&gt;

&lt;p&gt;The simplest usage looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cv_evidence_renderer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render_from_jsonl&lt;/span&gt;

&lt;span class="nf"&gt;render_from_jsonl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incidents/raw_001.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;detections_jsonl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incidents/raw_001.detections.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;12.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;22.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence/event_001.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&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;h3&gt;
  
  
  Events That Span NVR File Boundaries
&lt;/h3&gt;

&lt;p&gt;Hour-segmented NVR recordings are common.&lt;/p&gt;

&lt;p&gt;If an event starts near the end of one file and continues into the next, you can render it as a single continuous clip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cv_evidence_renderer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render_clip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClipSource&lt;/span&gt;

&lt;span class="nf"&gt;render_clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;ClipSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cam_22-00.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;detections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d_22.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;from_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1770&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;to_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;ClipSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cam_23-00.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;detections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d_23.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;from_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;to_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence_cross_file.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&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 output is one continuous MP4.&lt;/p&gt;

&lt;p&gt;Each detection JSONL remains keyed to its own local file timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frame 0 = first frame of that file&lt;/li&gt;
&lt;li&gt;No global concatenated timeline required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All sources must share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Width&lt;/li&gt;
&lt;li&gt;Height&lt;/li&gt;
&lt;li&gt;FPS (within 1%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resampling is intentionally out of scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  Batch Rendering Shared Sources
&lt;/h2&gt;

&lt;p&gt;Things get more interesting when you have many events from one long recording.&lt;/p&gt;

&lt;p&gt;The naïve approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;render_clip&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each render starts decoding from the beginning again.&lt;/p&gt;

&lt;p&gt;That's a lot of duplicated work.&lt;/p&gt;

&lt;p&gt;So the library includes a batch API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cv_evidence_renderer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Clip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClipSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_clips&lt;/span&gt;

&lt;span class="nf"&gt;render_clips&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clips&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;Clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nc"&gt;ClipSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;detections&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;from_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;to_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence/event_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_duration_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;duration_strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timelapse&lt;/span&gt;&lt;span class="sh"&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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&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;When multiple clips reference the same source file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The file is opened once&lt;/li&gt;
&lt;li&gt;Frames are decoded once&lt;/li&gt;
&lt;li&gt;Each decoded frame is dispatched to all interested clip encoders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each clip can still have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different overlays&lt;/li&gt;
&lt;li&gt;Different frame strides&lt;/li&gt;
&lt;li&gt;Different duration strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unique-source clips fall back to the standard per-clip path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where This Fits in the Pipeline
&lt;/h2&gt;

&lt;p&gt;The scope is intentionally small.&lt;/p&gt;

&lt;p&gt;The library does &lt;strong&gt;not&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perform detection&lt;/li&gt;
&lt;li&gt;Perform tracking&lt;/li&gt;
&lt;li&gt;Handle alerting&lt;/li&gt;
&lt;li&gt;Handle live streaming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bring your own detector:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;YOLO&lt;/li&gt;
&lt;li&gt;Detectron2&lt;/li&gt;
&lt;li&gt;Anything that can produce bounding boxes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bring your own tracker.&lt;/p&gt;

&lt;p&gt;The library does one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take video + detections and produce the evidence MP4 your ops team actually wanted.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  YOLO Integration Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ultralytics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;YOLO&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cv_evidence_renderer.adapters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;from_yolo_results&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yolov8n.pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incidents/raw_001.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;frame_detections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;from_yolo_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame_idx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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;h2&gt;
  
  
  Benchmark
&lt;/h2&gt;

&lt;p&gt;Measured end-to-end:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decode → Overlay → Encode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hardware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apple M4 CPU&lt;/li&gt;
&lt;li&gt;libx264 encoder&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;th&gt;Render Time (5s @ 30fps)&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;480p&lt;/td&gt;
&lt;td&gt;0.53 s&lt;/td&gt;
&lt;td&gt;282 fps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;720p&lt;/td&gt;
&lt;td&gt;0.89 s&lt;/td&gt;
&lt;td&gt;168 fps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1080p&lt;/td&gt;
&lt;td&gt;1.70 s&lt;/td&gt;
&lt;td&gt;88 fps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Benchmark command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python scripts/benchmark.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NVENC support is planned for v0.2.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Doesn't Do (Yet)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  NVENC GPU Encoding
&lt;/h3&gt;

&lt;p&gt;Designed and stubbed, but not wired up yet.&lt;/p&gt;

&lt;p&gt;For many offline workflows, CPU rendering is already faster than real time through 1080p.&lt;/p&gt;




&lt;h3&gt;
  
  
  Live RTSP Recording
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;EvidenceRecorder&lt;/code&gt; API exists but currently raises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ring buffers and keyframe-aware trigger logic are planned for v0.2.&lt;/p&gt;




&lt;h3&gt;
  
  
  Custom Zones, Lines, and Overlay Plugins
&lt;/h3&gt;

&lt;p&gt;Planned for v0.3.&lt;/p&gt;

&lt;p&gt;The plugin API needs real-world feedback before being frozen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;cv-evidence-renderer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional Supervision adapters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;cv-evidence-renderer[supervision]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/ddinhcchi/cv-evidence-renderer" rel="noopener noreferrer"&gt;https://github.com/ddinhcchi/cv-evidence-renderer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://ddinhcchi.github.io/cv-evidence-renderer/" rel="noopener noreferrer"&gt;https://ddinhcchi.github.io/cv-evidence-renderer/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT licensed.&lt;/p&gt;

&lt;p&gt;CI on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;macOS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10&lt;/li&gt;
&lt;li&gt;Python 3.11&lt;/li&gt;
&lt;li&gt;Python 3.12&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback is welcome — open an issue or reach out through the repository.&lt;/p&gt;

</description>
      <category>python</category>
      <category>computervision</category>
      <category>opensource</category>
      <category>ffmpeg</category>
    </item>
  </channel>
</rss>
