11/06/2026
The Filenames Lie: Sorting 91 AI Video Clips for Free
Short answer: Fingerprint every file with a SHA-256 hash to find true duplicates, transcribe the audio on your own computer with faster-whisper, tile a few frames per clip into contact sheets, and ask a vision model running in LM Studio for a one-paragraph description of each clip. For my 91 clips that cost nothing, and the transcription took 178 seconds. The commands, and the places it broke, are below.
| Step | Tool | Result on my clips |
|---|---|---|
| Fingerprint | SHA-256 of the whole file | 91 files, 1 exact duplicate |
| Transcribe | faster-whisper, small.en model, CPU |
90 distinct clips in 178 seconds, 0 failures |
| Look | Contact sheets, 3 frames per clip | 8 clips per sheet |
| Describe | Gemma 3 12B in LM Studio, on a second PC | About 16 seconds a clip |
| Keep | One line of text per clip | Searchable |
After a few weeks of making clips for a project, I opened the download folder and found 91 video files, 269 MB, a little over 15 minutes of footage, and no idea which was which.
The names were no help. Google Flow names a download after the start of its prompt, cut short, so I had files like A_locked_off_completely_stati (1).mp4 and Generate_footage_that_could_be(1).mp4. Some were named after a web address. The (1) copies might be duplicates, or might be completely different clips that started with the same words. And the files themselves didn't carry the prompt that made them.
I had two bad options: watch fifteen minutes of footage, several times, taking notes, or pay a service to do it. I took a third: I asked my AI to do the tedious part while I kept the judgment.
Let the machine listen first
The most useful thing in a video file turned out not to be the picture. It was the sound.
Every one of the 91 clips had an audio track, and 53 of them had someone speaking. What a clip says tells you far more than its name. "Oh, Brian's song gets me every time" identifies a clip instantly. A_locked_off_completely_stati (1).mp4 does not.
So the first job was transcription, done locally. The tool is faster-whisper, a reimplementation of OpenAI's Whisper speech model that its README says runs up to four times faster. The model is small.en, a 486 MB download. It ran on my PC's ordinary processor, with no upload, no subscription and no per-minute fee.
from faster_whisper import WhisperModel
model = WhisperModel("small.en", device="cpu", compute_type="int8")
segments, info = model.transcribe("clip.mp4", beam_size=1, vad_filter=True)
text = " ".join(s.text.strip() for s in segments) # segments is a generator: this line does the work
It reads the MP4 directly. vad_filter=True turns on voice-activity filtering, so stretches with no speech are skipped. A silent clip comes back empty, which my catalog records as "no speech."
Two models, two results
I had only the smallest model, tiny, on the PC at first, so the first pass used it. I then approved the 486 MB download of small.en and ran everything again.
| Model | Transcription time, 90 distinct clips | What I saw |
|---|---|---|
tiny |
75 seconds | 53 clips with speech; wrote "Ah... Ah..." for one clip |
small.en |
178 seconds (3 minutes 46 seconds for the whole scan, including hashing and loading the model) | Heard the same clip as "Oh"; 0 failures |
It wasn't perfect even so. It heard "Brandiger" for Brandager, which I've decided to take personally. That's why the transcript is a way to find a clip, not the final word on what's in it.
Find duplicates by content, not by name
Two files can have the same name and different contents, or different names and identical contents. The reliable test is a fingerprint of the file's actual bytes. Of the 91, exactly one turned out to be a true duplicate, the same clip of the coach on stage, saved under two different names. The (1) copies I'd been worried about were mostly different clips.
On Windows, Get-FileHash in PowerShell lists every file whose hash appears twice:
Get-FileHash -Algorithm SHA256 *.mp4 | Group-Object Hash | Where-Object Count -gt 1 | ForEach-Object { $_.Group.Path }
On Linux, sha256sum *.mp4 | sort | uniq -w64 -D does the same. A test with one renamed copy among four files listed exactly the two matching files with both commands. I used the first 12 characters of each hash as the clip's ID, so a renamed file keeps its identity.
What a hash cannot catch
A byte hash only matches byte-identical files. When I added more downloads later, 150 files held 124 distinct videos: 26 byte-identical copies from Flow's repeated downloads. A second pass that compared small fingerprints of three frames per clip then found 4 more 720p copies of clips I already had at 1080p, which no hash will ever match.
Look at pictures cheaply
For the visual side, instead of opening clips one at a time, we made contact sheets: a single image with frames from several clips side by side, the way photographers used to review film. A handful of sheets covered a whole group of clips in one look.
My tool took frames at 15%, 50% and 85% of each clip, shrank them to 320 pixels wide and tiled them with Pillow, eight clips per sheet. One folder of 31 clips fit on four sheets. FFmpeg can make a one-clip version in one line, using its fps and tile filters:
ffmpeg -i clip.mp4 -vf "fps=3/10,scale=320:-2,tile=3x1" -frames:v 1 -q:v 3 sheet.jpg
For a 16:9 clip of 10 seconds, which is about what Flow gave me (15 minutes over 91 files), that grabs frames from the start, a third and two thirds of the way in, and writes a 960 by 180 pixel image. That is a different spacing from the 15/50/85 my tool used.
Describe each clip with a local vision model
A vision model, running on another computer on my own network, then wrote a short description of each clip's scene. A description is a first guess, not a verdict, but it makes the library searchable.
The model was Gemma 3 12B Instruct, in bartowski's Q4_K_M build (about 9 GB), loaded with a 4,096-token context in LM Studio on a second PC with an RTX 5070 (12 GB). My main PC's graphics card has only 4 GB, so the job went to the second one. LM Studio's server accepts text and images through an OpenAI-style chat endpoint. Each request carried the clip's transcript and three frames, scaled to at most 768 pixels wide and sent as base64 JPEGs.
Two choices mattered. The file name was left out on purpose, because it is unreliable. And the system prompt demanded a JSON reply with fixed keys: summary, actions, subjects, setting, camera, mood, lighting, text_on_screen, text_looks_garbled, flaws, suggested_use, quality_hint and tags.
For more on running models like this locally, see Local LLM vs. API: Benchmarking LM Studio for Dev Tasks.
What the model got wrong
Claude checked five descriptions against frames it pulled from three of the clips.
| Field | What happened |
|---|---|
| Summary, subjects, setting, camera, mood | Matched what the frames showed. It also read a box label, "SL 930", correctly |
| Actions | Said a woman was crafting pottery; the frames show her standing and smiling in a pottery shop |
text_on_screen |
On a workflow diagram with tiny text it "read" words like "tremolize" and did not flag them as garbled |
quality_hint, suggested_use |
Formulaic: four of five came back "fixable," and all five suggested b-roll and thumbnail |
So I trust the scene and ignore the verdicts. The keeper calls are still mine.
What broke in the run
- The download failed. LM Studio's models folder on the second PC pointed at a missing drive, so the first attempt failed with "no such file or directory." I fixed the setting, and the retry downloaded.
- The full run timed out. After five clips (about 16 seconds each), the full batch stopped on its first request. The server was slow, and LM Studio's model list still said Gemma was loaded, but the model had crashed on the GPU. Ejecting it and loading it fresh fixed it. A plain text reply then took 0.4 seconds, and the remaining 83 clips ran with 0 failures. Why the GPU failed was never diagnosed.
Write it down once
The rule underneath all of this: never look at the same clip twice to answer the same question. Everything learned about a clip, what it says, what it shows, whether it's a duplicate, gets written down once and kept with the clip. The next time I need "the coach in the gym, laughing", I search for it. Writing a rule down is not the same as it being followed, which is why I also wrote about making the computer check it.
Here is a stripped-down version of the first two steps, hashing and transcribing, in about 35 lines. It was checked on a few small test clips, not on my 91.
import hashlib
import json
import sys
from pathlib import Path
from faster_whisper import WhisperModel
def sha256(path):
digest = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
digest.update(chunk)
return digest.hexdigest()
folder = Path(sys.argv[1])
seen = {} # full hash -> first file seen
model = WhisperModel("small.en", device="cpu", compute_type="int8")
with (folder / "catalog.jsonl").open("w", encoding="utf-8") as out:
for clip in sorted(folder.rglob("*.mp4")):
digest = sha256(clip)
row = {"id": digest[:12], "name_hint": clip.name} # the name is a hint, never the identity
if digest in seen:
row["duplicate_of"] = seen[digest]
else:
seen[digest] = clip.name
try:
segments, _ = model.transcribe(str(clip), beam_size=1, vad_filter=True)
row["transcript"] = " ".join(s.text.strip() for s in segments)
except Exception as exc: # one bad clip must not stop the run
row["error"] = f"{type(exc).__name__}: {exc}"
out.write(json.dumps(row) + "\n")
Run it with python catalog.py "D:\clips". It transcribes everything again each time, which is fine for a hundred clips and wasteful for thousands. The try is there because in testing, a video with no audio track at all made faster-whisper 1.2.1 raise IndexError: tuple index out of range. Mine all had audio.
My real catalog, one line per clip, looked like this:
07543b4e8438 | b-roll | 10.0s | Oh | hint (unverified): Don_t_show_any_graphic_text_V.mp4
6a3cac5ed414 | b-roll | 10.0s | no speech | hint (unverified): Photorealistic_k_cinematic_b_(1).mp4
Descriptions go in a separate append-only file. A new run adds a new revision and never overwrites an old one, so my own notes on a clip are never replaced by a model's guess.
What to take from it
This isn't really about video. Any small business collecting AI-made material, images, clips, drafts, will hit the same wall: it's made faster than anyone can keep track of it. The fix isn't a bigger folder or a paid tool. It's three habits:
- Let the machine do the listening and looking, locally and for free where you can, and break its answer on purpose before you trust it.
- Identify things by what they contain, not what they're called.
- Write it down once.
I did this with Claude doing the hands-on work while I steered, and the same approach works with the AI tools you already have. If you'd like help setting up something like this for your own business, that's exactly what my one-on-one AI guidance is for. Let my Claude help your Claude.
Researched and drafted with AI, then reviewed and edited by the site's editors.