I lost a morning of uploads to a CSV header. Not a bug in my code, not a rejection for quality. I had the column order wrong for Vecteezy, the reviewer never even opened the files, and the batch of 40 assets sat in "error" until I noticed. That was the moment I stopped trusting my memory and started writing the boring parts down.
Here is what I learned, in the order I learned it.
Metadata is a format check, not a suggestion
Every stock platform validates the upload CSV before a human sees anything. Adobe Stock wants a 5-column header. Vecteezy wants 4 columns. Dreamstime wants 15. Get one column wrong or add a stray comma in a title and the whole file bounces back instantly, with an error message that tells you the row number and nothing else.
So I stopped writing these files by hand and wrote make_metadata.py. It takes a folder of images and emits the exact header each platform expects, nothing more. Adobe gets 5 columns, Vecteezy gets 4, Dreamstime gets 15. If you pass a titles file it uses your real titles and keywords. If you don't, it title-cases the filename, which is fine for a first pass but you should replace it before submitting. Filenames like abstract_waves_003.jpg do not sell.
The rule I follow now: the CSV is generated, never typed. Typed spreadsheets are where the mistakes live.
The double-post problem is worse than the rejection problem
The fastest way to get an account flagged on a stock site is uploading the same asset twice. And the easiest way to do that is running your batch script twice, which happens to everyone eventually. You rerun a job to "catch the last few files" and now you have duplicates.
I wrote upload_tracker.py because of this. It is a ledger, nothing fancy. You mark an asset done, you ask it what is pending, it tells you. A re-run of the batch now skips anything already marked. It uses the Python standard library alone, no installs, because a guard tool should never be the thing that fails to run on a fresh server.
The honest version of this advice: automation multiplies whatever habit you already have. If your habit is careful, it multiplies careful. If your habit is rerunning things and hoping, it multiplies that too. Put the guard in the tool, not in your discipline.
A document is not a product until it looks like one
I had good content sitting in Markdown files for months. Nobody buys a .md file. The gap between "I wrote this" and "this is a product I can sell" was purely mechanical, so I closed it with two scripts.
md2pdf.py turns Markdown into a styled PDF with headings, tables, lists, and working links, using reportlab. One command: input file, output file, title. pack_product.py takes a folder and produces the zip plus a 1280×720 cover and a 600×600 thumbnail, rendered with Pillow. A zip with a real cover sells. A bare file does not. I do not have a cleverer insight than that.
Both scripts write their output to a _system folder by default, and you can redirect with --dir or an environment variable. Small thing, but it means the scripts never scatter files into whatever directory you happened to be standing in.
The folder layout is part of the pipeline
A catalogue turns into a junk drawer without a fixed structure. Mine is five folders: assets for raw output, output for ready-to-submit files plus their CSVs, packs for finished zips and covers, research for notes, scripts for tools.
The one I got wrong at first was research. I deleted my scrape logs and notes during a cleanup because they looked like clutter. Then I needed them for a follow-up product and they were gone. Your research notes are the raw material for products you have not made yet. Keep them forever.
Small server habits that saved me
The whole pipeline runs on 1 vCPU and 2 GB RAM with no GPU. That constraint forced habits worth more than any upgrade:
- One process at a time. Never fan out image jobs in parallel on a single core, it just thrashes.
- Call
gc.collect()after each asset so memory does not accumulate across a batch. - Measure peak RSS with
resource.getrusage(RUSAGE_SELF).ru_maxrss, not your average usage. Peak is what crashes you. - Run heavy render steps as one file per subprocess so memory actually returns to the OS.
- Inspect previews at 60 dpi instead of rendering at 300. You only need the real resolution for the final file.
None of this is clever. All of it is the difference between a batch finishing and a batch dying at file 60.
Where this comes from
These four scripts are the packaging layer of a bigger pipeline: about 19,000 lines of code, 300+ pages of research behind the rules the scripts enforce, and 39 finished products totalling 856 MB of output, all validated on that same tiny server. The prompt engine behind it all is the vector prompt pack, and the scripts above are what turned its output into sellable files.
I wrote them down because I kept relearning them the hard way, one bounced CSV at a time. If you want the same packaging layer without rebuilding it, the kit is called the Pipeline Starter Kit and it ships with the vector prompt pack.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)