Automatic counter

For my YouTube fitness channel, I make calisthenics videos. I’ve recently started adding a counter to the videos to show my rep count. I did this rather painstakingly by using the Timer filter and editing the speed and duration multiple times to get it to fit the time fluctuations of my exercising. Example:

To do that, I end up having to slice the video multiple times and adjust the timer speed. I would like to figure out a way to automate the counter. I can imagine triggering it off of motion in the video. E.g., every time the top of my head hits its high point in the video, increment the counter. Can anyone think of a way to do this?


1 Like

The answer would most likely include some type of OpenCV, Motion-Detection or Face-Detection code.

In say Python code with Opencv, you can track a feature then as you say, trigger a counter at a certain location.

It seems very specialist code. Big companies pay for Programmers to write this type of thing all the time to watch things in Manufacturing environments. So it’s a fairly common programming task.

For ideas:

Head Pose Estimation with MediaPipe and OpenCV in Python - OVER 100 FPS!!! - YouTube

A Github code search for what I think might help: Search · head pose estimation · GitHub

@david.lyon wrote:

The answer would most likely include some type of OpenCV, Motion-Detection or Face-Detection code.

That’s both very interesting and, unfortunately, probably beyond my skill level to get it done. I appreciate your bringing it to my attention, though. What actually prompted my question was a Shotcut tutorial I watched last week where I learned how to introduce a “shake” effect based on beats in the audio track:

That relies on the Audio Dance Visualization filter. So I figured, hmm, if we can automate the “shake” or “earthquake” effect based on sound, maybe we can automate a counter based on motion. It seems not that far removed conceptually. I also had the thought that if need be, I could just add an audio cue and use that to increment the counter similarly to how the audio-dance visualization filter works. It would be much easier for me to either count out loud while I’m filming these videos or just add an audio-trigger track afterward than to continue to slice up my videos every few reps of exercising and change the timer speed-interval, duration, and offset over and over again.

Do these ideas add to any lightbulbs going off for those looking in here?


I like the idea of a counter triggered by sound.

In the meanwhile…

I don’t know how tedious it is to do this using your method, but maybe this one would be a bit faster:

Use a text filter that you cut and edit


That’s kinda cool. Thanks! With the timer method I’ve been using, I don’t have to edit every single rep-clip (or, in the case of your demo video, every swing). I can go up to about 10-12 reps on the same setting if I’m strong and steady enough, but after a while I slow down dramatically. That’s when I have to start slicing the video every couple of reps or so.

I could probably save some effort and annoyance with your method anyway, though. I could keep the counter track saved and pull it in at will, and then just adjust the length of the individual cuts. Yeah, that might work decently. Oh, gee, I could just let the timer run in its own track to create the numbers track. Good, I think I’ll do it that way until a better solution comes along.

Next trick: a Roman-numeral counter. :upside_down_face:


1 Like

I have 2 ideas that might work:

  1. Use a slide program like Powerpoint, Keynote, or Libre office Impress.
    Make each slide increment the numbers.
    Click to show each number at the time you want it to appear.
    Screen-capture this using ShareX or OBS.
    Save as an MP4 video.
    Import into Shotcut on V2.
    If you make the slides with a black background and white text, use Blend Mode ADD to remove the black background (or Chromakey filter).

  2. Devise a HTML/CSS file which increments the numbers if you click on the screen, and screen-capture it, as in idea 1 above.
    I personally don’t quite yet have the skills to do this, but I know a man who does - I bet @elusien could knock this up in no time…


Your ideas are excellent @jonray.
There are also online tools called Tally counters, or Click counters that @spamless could use.
There are probably more, but I found these 3 that could easily be captured while watching the video and then blended into a project in Shotcut.

Click counter #1
This one have red numbers on a black background. The numbers can be set to different sizes.

Click counter #2
Black numbers on a green background.

Click counter #3
Black numbers on a white background.


Brilliant. These would work very well. Thank you for pointing out these - I never knew about them…

Your wish is my command. Below is the HTML file. Just click in the green-screen area. If you want e,g, 3 digits displayed change the <span>00</span> to <span>000</span>

<!DOCTYPE html>
    <title>Click counter</title>
    You may change any of the ":root" parameters, but do not change anything else.
    If you want the the counter background to be transparent set the following:

    --background-color: transparent;

    Then in Shotcut chroma-key the --screen-color value
:root {
    --width           : 600px;
    --height          : 500px;
    --compass-position: SE; /* NW, N, NE, W, C, E, SW, S, SE */
    --screen-color    : #00ff00; /* or e.g. black or white; */
    --background-color: #666666; /* or e.g. transparent; */
    --font-color      : yellow;
    --font-family     : verdana, arial, sans-serif;
    --font-size       : 32px;
    --font-weight     : normal; /* or bold, bolder, lighter, 100, 200, ... 900 */
    --font-style      : normal; /* or italic or oblique */
    --margin-vertical : 8px;
    --margin-horiz    : 8px;
    --padding-vertical: 6px;
    --padding-horiz   : 6px;
    --beep-on-click   : yes; /* or no */

#screen {
    display         : grid;
    width           : var(--width);
    height          : var(--height);
	background-color: var(--screen-color);
    cursor          : none;

span::selection { background: transparent; }

.NW {justify-content: start ; align-content: start ;}
.N  {justify-content: center; align-content: start ;}
.NE {justify-content: end   ; align-content: start ;}
.W  {justify-content: start ; align-content: center;}
.C  {justify-content: center; align-content: center;}
.E  {justify-content: end   ; align-content: center;}
.SW {justify-content: start ; align-content: end   ;}
.S  {justify-content: center; align-content: end   ;}
.SE {justify-content: end   ; align-content: end   ;}

span {
    display         : none;
    margin          : var(--margin-vertical)  var(--margin-horiz);
    padding         : var(--padding-vertical) var(--padding-horiz);
    background-color: var(--background-color);
    color           : var(--font-color);
    font-weight     : var(--font-weight);
    font-style      : var(--font-style);
    font-size       : var(--font-size);
    font-family     : var(--font-family);
    width           : fit-content;
    height          : 1em;
    line-height     : 1em;
    <main id="screen"><span>00</span></main>
e.g. to get 3 digits displayed with leading zeros change the line above to:

    <main id="screen"><span>000</span></main>

    e.g. to get no leading zeros change the line above to:

    <main id="screen"><span>0</span></main>

    e.g. to start with a blank until the count = 1 change the line above to:

    <main id="screen"><span></span></main>
document.addEventListener('DOMContentLoaded', function(event) {
    const root_style       = getComputedStyle(document.documentElement);
    const compass_position = root_style.getPropertyValue("--compass-position").trim().toUpperCase();
    const beep_on_click    = root_style.getPropertyValue("--beep-on-click"   ).trim().toLowerCase() === "yes";

    const main             = document.getElementsByTagName('MAIN')[0];

    const span         = document.getElementsByTagName('SPAN')[0];
    const span_style   =;
    let   count_string = span.textContent.trim();
    let   ndigits;

    if (count_string === '') {
        ndigits = 1;
    } else {
        ndigits = count_string.length;
        span_style.display = 'inline';

    ndigits     = (count_string === '') ? 1 : count_string.length;
    let   count = -(-count_string);

    const beep_sound = new Audio('data:audio/wav;base64,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');

    main.onclick = function(e){
        span.textContent = (++count).toString().padStart(ndigits, '0');
        if (count === 1) { span_style.display = 'inline'; }
        if (beep_on_click) {;}


@Elusien, this is fantastic.

The only small problem I see, is if we click too fast, the background-color area briefly turns blue, as if it was selected for a short moment. Then to stop it from turning blue on each click, we need to click outside of the screen-color area. But I don’t think this will be an issue for @spamless.


Also would it be possible to add a variable for the font weight?


@MusicalBox , I’ve updated the HTML listing above to:

  • Fix the flash of colour;
  • Fix the problem of the first click not updating the counter;
  • Add a new parameter: --font-weight : normal; /* or bold, bolder, lighter, 100, 200, ... 900 */
  • Add a new parameter: --font-style : normal; /* or italic or oblique */

Perfect! :+1:

Another great and useful Elusien tool for the Shotcut community.


In my experiments, I found that it works best to set Elusien’s Click Counter tool to white text on a black background.

I have also set the text size to 100px and the vertical and horizontal margins to 80px.


Then after the capture, drag the video file in Shotcut, sync it with the video on track V1 and apply these filters in this order:

  • Chroma Key: Simple. Key color is set to black. Distance is set to 70.
  • Color Grading. Use the Highlight (Gain) parameters to change the color of the white text to any other color.
  • Size, Position & Rotate to position the text on the screen.


Difference between removing the green and the black background with the Chroma Key filter.

Green background zoomed at 200%

Black background zoomed at 200%


C’est effectivement un outil qui pourrait être très utile.

Question pratique: Comment fait-on pour enregistrer avec OBS en même temps que l’on visualise le film lorsqu’on a qu’un seul écran?

Un autre question pour @brian : Le filtre texte simple permet de saisir des paramètres dans la zone de texte (ex: #timecode#). Existe t’il un paramètre qui donnerait le rang du clip sur sa piste?
Dans ce cas, on mettrait un clip de la longueur de la vidéo avec un filtre texte simple et il suffirait de le scinder à chaque fois que l’on veut incrémenter le compteur.

It is indeed a tool that could be very useful.

Practical question: How do I record with OBS while viewing the movie when I have only one screen?

Another question for @brian: The simple text filter allows to enter parameters in the text field (e.g. #timecode#). Is there a parameter that would give the rank of the clip on its track?
In this case, we would put a clip of the length of the video with a simple text filter and just split it each time we want to increment the counter.

Translated with DeepL Translate: The world's most accurate translator (free version)


1 Like

Using OBS there is a source called Image Slide Show.
It can run on its own or with hotkeys to get to the next slide.



@elusien, you deserve a medal from our new King Charles. :smile: Fantastic devotion to duty. :+1:
This is utterly brilliant and I made this demo in no time.
I tweaked the HTML a little: larger canvas size, Half grey background (#808080) 200 pixel font size, black text on white background. Then applied a Crop rectangle filter, Blend Mode: Hard Light filter, and a SPR filter to bring the box into the middle a bit.
My adapted HTML file (remove the fake TXT extension) is here:
Elusien click counter LARGER with grey background.html.txt (3.4 KB)
Demo video:


Looks excellent, @Hudson555x , I will check it out.

1 Like

In addition to @MusicalBox’s slick demo, let me add a recommendation. I haven’t used OBS before, but I use Techsmith’s free brilliant “Capture” app for Windows or Mac. It will also work well for all these screen-recording needs, and it’s only 14.7 MB. Check it out if you’re on a platform that runs it. On the other hand, I am a big fan of cross-platform and open-source stuff, and I will sure check out OBS Studio soon.

This thread had generated some terrific solutions and ideas, and I thank you guys. I’ll be studying the other follow-ups and acknowledging (more like crowing about excitedly) various of them that look excellent on a skim of what’s here since I last looked 24 hours ago!


There are also online tools called Tally counters, or Click counters that @spamless could use.
There are probably more, but I found these 3 that could easily be captured while watching the video and then blended into a project in Shotcut.

Click counter #1
This one have red numbers on a black background. The numbers can be set to different sizes.

Click counter #2
Black numbers on a green background.

Click counter #3
Black numbers on a white background.

These counters you found are all great, @MusicalBox! I’ve bookmarked all three. I like the bold first font a lot. I like the discreeter operation of the second. And I like the third’s option to change the increment interval if desired!

Great idea.