Short-video factory on stock footage
Hundreds of stock clips filtered, cut and checked into ready-to-post vertical edits, without opening an editor.
- Python
- FFmpeg
- OpenCV
- YuNet
- curl_cffi
- Pexels API
- Pillow
What it makes
Real videos produced by the system.
- 456downloaded clips rejected automatically out of about 1,300 checked, 322 of them for faces
- 340clips left in the working library after a 504-entry blocklist kept by hand
- 30 / 30renders in the last full batch passing automatic QC
340 stock clips in, 49 vertical edits out
Searches stock libraries, filters every clip on five frames, grades it to 1080×1920 and cuts muted vertical edits by rule, then checks each one automatically.
Tap a block to see what it does.
- Input
- AI
- Logic
- People
- Data
- Output
Input
Stock search
26 curated queries on Pexels and Pixabay: official APIs with a key, else search pages via curl_cffi posing as Chrome. 5 parallel downloads, 120 MB cap.
- Pexels API
- Pixabay API
- curl_cffi
Hands to Visual filters
AI
Visual filters
5 frames per clip: YuNet face ≥7.5% of frame height or in 2+ frames, colourfulness ≥10, luma std ≥18, Laplacian ≥22, dHash ≤6 bits from a kept clip.
- OpenCV
- YuNet
- Pillow
- ffprobe
Gets from Stock searchHands to Normalize + grade
Logic
Normalize + grade
Cover-crop to 1080×1920, 30 fps, no audio, contrast 1.11 / saturation 1.24 grade; the same x264 settings everywhere, so the final join needs no re-encode.
- FFmpeg
- x264
Gets from Visual filtersHands to Query audit, Clip library
People
Query audit
One contact sheet per search query plus a worst-first library sheet; duds go to the blocklist with cli.py block. 78 queries tried, 26 kept.
- Pillow
Gets from Normalize + gradeHands to Clip library

Data
Clip library
manifest.json keeps source URL, metrics and usage for every clip; 340 usable after the blocklist. Usage is saved, so repeats are avoided across runs.
- JSON
Gets from Normalize + grade, Query auditHands to Rule-based edit
Logic
Rule-based edit
5–8 cuts of 1.4–2.6 s for 9.5–12 s; ≥65% POV, at least one car and one watch or cash clip, punchiest clip first, no reuse within 5 videos; stream-copy join.
- Python
- FFmpeg
Gets from Clip libraryHands to Automatic QC
Logic
Automatic QC
ffprobe specs, blackdetect (fails over 1.8 s), freezedetect (fails from 3 s), scene-cut count, first-frame brightness, contact sheet. The panel re-renders once on a fail.
- FFmpeg
- ffprobe
Gets from Rule-based editHands to Finished edits

Output
Finished edits
Muted 1080×1920 MP4s ready for Reels and TikTok, audio added later. 49 rendered: bulk batches at CRF 21, single renders from the panel at CRF 16.
- H.264
Gets from Automatic QC
The problem
Faceless luxury pages post fast-cut vertical edits every day. Cutting them takes minutes; finding clips that fit takes hours, because free stock is full of faces, flat footage, adverts and near-duplicates. A search for 'lamborghini interior' returned a tractor and a video game, but no Lamborghini.
What I built
- I built sourcing for Pexels and Pixabay: official APIs when a key is set, otherwise the search pages through a client that impersonates Chrome, because plain requests got 403s.
- I check every download on five sampled frames: YuNet face detection, Hasler–Süsstrunk colourfulness, contrast, Laplacian detail and a perceptual hash against every clip already kept.
- I normalize each clip to the same 1080×1920, 30 fps H.264 profile with a light colour grade, so a finished edit is joined by stream copy, with no re-encode.
- I encoded the edit rules in the assembler: 5–8 cuts of 1.4–2.6 s, at least 65% first-person shots, one car and one watch-or-cash clip per video, the punchiest clip first, no clip reused within five videos.
- I added automatic QC (black frames, freezes, scene cuts, first-frame brightness) and review tools that print one contact sheet per search query, so weak queries get dropped and bad clips blocked for good.
The hard part
Relevance. The filters catch faces and dull footage, not a Toyota inside a 'luxury car interior' result. Random spot checks kept missing these, so I switched to auditing the library one search query at a time: that audit dropped seven queries with almost no usable hits and grew the blocklist to 504 clips.
Result
Of about 1,300 downloaded clips, 456 were rejected automatically and 504 more blocked by hand, leaving 340 in the working library. The engine rendered 49 edits of 9.5 to 12 seconds, and the last full batch passed QC 30 out of 30. The project report rates 72–78% of the clips in each video as on brief; the free catalogue is the ceiling.
My role
Solo project. I set the visual brief from viral reference reels, rejected outputs clip by clip with the reasons logged in a playbook, and decided what to block; Claude Code wrote most of the Python, including long unattended runs overnight.

