Part 3 of 13 · Video Tools

Rebuilding My Video Tool’s Engine With FFmpeg Scene Detection

A blueprint-style diagram of a rebuilt engine scanning a film strip and marking scene changes

My tool looked cleaner now, and it was lighter underneath after cutting the scene detection library. But I had left a hole. The tool still needed to find where scenes changed in a movie, and now nothing was doing that job. This post is about filling that hole with FFmpeg directly, which turned out to be simpler and faster than the library I removed.

FFmpeg has a scene detection filter built in. You give it a video and a sensitivity, and it flags the frames where the picture changes enough to count as a new scene. The command I settled on was this:

cmd = [
    "ffmpeg", "-i", self.input_file,
    "-filter:v", "select='gt(scene,0.3)',showinfo",
    "-f", "null", "-"
]
result = subprocess.run(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)

Two parts matter here. select='gt(scene,0.3)' tells FFmpeg to pick out frames where the scene change score is greater than 0.3, my sensitivity threshold. showinfo tells FFmpeg to print details about each of those frames. I send the output to null because I do not want a video out of this, I only want the information FFmpeg prints while it scans.

That information comes back on the error stream, not the normal one, which trips a lot of people up. FFmpeg writes its diagnostic output to stderr, so I read result.stderr, and I pull the timestamps out with a regular expression:

import re

# FFmpeg prints lines like: ... pts_time:12.512 ...
timestamp_pattern = re.compile(r"pts_time:([d.]+)")
timestamps = timestamp_pattern.findall(result.stderr)

if not timestamps:
    self.log_message("No scene changes detected. Trying a lower threshold...")

Now the scenes lived in memory as a list of numbers, not as files on disk. No temporary files, no reading them back, no cleanup. That single change removed a whole class of errors.

One real problem I hit: on some videos, a threshold of 0.3 found nothing, because the whole clip was visually similar. A tool that returns zero scenes and stops is useless, so I added a fallback that lowers the threshold and tries again, and if it still finds nothing, treats the whole video as one scene. That way the tool always produces something instead of failing silently.

A few things people ask me about this

Why does the scene detection return nothing sometimes? Because the threshold was too high for that video. A visually uniform clip never crosses a strict scene score, so you get an empty result. Lower the number in gt(scene,0.3), or add a fallback that retries lower and finally treats the whole video as one scene.

Why is FFmpeg’s output empty when I read it? You are almost certainly reading stdout. FFmpeg writes its progress and showinfo details to stderr. Capture and parse result.stderr.

What does the 0.3 threshold mean? It is how big a visual change counts as a new scene, from 0 to 1. Lower finds more, and smaller, scene changes. Higher finds only dramatic cuts. 0.3 was a good middle for movie footage.

Next

Once the tool could find scenes reliably, I hit a problem that was not technical at all, it was about choice. Letting the user pick where in each scene to cut sounded simple, until the combinations multiplied. That is the next post.

Leave a Reply

Your email address will not be published. Required fields are marked *