Last March I had 12 finished vector sets sitting in a folder and got three rejection emails in one afternoon. Adobe bounced my batch because the CSV header had six columns instead of five. Vecteezy did the same for a four-column file where I'd guessed at the field order. I hadn't made bad art. I'd made bad paperwork. That afternoon is why I now generate every upload CSV with a script instead of typing headers by hand.
The header is the gate, not the art
All three stock platforms reject a wrong CSV instantly, before any human looks at your work. Adobe wants exactly five columns. Vecteezy wants exactly four. Dreamstime wants fifteen. If you're repurposing one asset across all three, you're maintaining three different file formats for the same JPG, and hand-editing them is how the errors creep in.
So make_metadata.py writes the header each platform demands and nothing else. It walks a folder of files, title-cases the filename as a default title, and emits one of three CSVs:
python make_metadata.py ./my-assets --platform adobe --out metadata.csv --ext .jpg
python make_metadata.py ./my-assets --platform vecteezy --out vecteezy.csv --ext .jpg
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
It's standard library only. No pip install. If you pass --titles titles.json you get real titles and keywords; without it, the filename becomes the title, which is fine for a first pass but you should replace it before you submit. The point is the header is never wrong again. One source of truth, three outputs.
Double-posting is the fastest way to get flagged
The second failure I had, I avoided by luck. I re-ran a batch upload once because I thought the first run had failed partway. It hadn't. If a human hadn't caught it, I'd have posted duplicates, and duplicate submissions across a portfolio is exactly the pattern that gets an account reviewed and flagged.
upload_tracker.py is a 40-line ledger that makes re-runs safe. It remembers what's done:
python upload_tracker.py pending items.txt # shows what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Before every batch I run pending first. Whatever it lists is what I upload. Standard library again, no database, just a flat file. It has prevented at least three accidental double-posts since I started using it, which on a small account is the difference between a clean record and a strike.
Repurposing means one folder, three CSVs, zero retyping
The workflow that actually works for covering all three channels from one asset:
- Generate or finish the asset once, into
assets/. - Copy the ready-to-submit files into
output/. - Run
make_metadata.pythree times, once per platform. - Mark each asset
doneas you upload it.
The folder layout matters more than it sounds. Without a fixed structure a catalogue becomes a junk drawer:
project/
assets/ raw output
output/ ready-to-submit files + CSVs
packs/ finished product zips + covers
research/ your notes and sources
scripts/ the tools
Keep research/ forever. I deleted mine once to "clean up" and then wanted a source note three weeks later. It was gone. Your scrape logs and notes are raw material for future products.
Where this ran: 1 vCPU, 2 GB RAM, no GPU
None of this needs a big machine, but a small box punishes bad habits. This pipeline ran on 1 vCPU and 2 GB RAM, and the habits that made it work:
- One process at a time. Never fan out image jobs in parallel on a single core.
- Call
gc.collect()after each asset to free memory between items. - Measure peak RSS, not average:
resource.getrusage(RUSAGE_SELF).ru_maxrssgives you the real ceiling. - One file per subprocess for heavy render steps, so memory actually returns to the OS.
- Inspect previews at 60 dpi instead of rendering at 300 when you only need to check composition.
The scripts themselves are so light that two of them need no install at all. The other two, md2pdf.py (Markdown to styled PDF via reportlab) and pack_product.py (zips a product and renders a 1280×720 cover plus a 600×600 thumbnail via Pillow), are the only ones that touch third-party packages, and it's just those two.
The honest numbers
This is not a passive income story. As of writing I've made about 1,700 assets, 58 are live on Adobe, and my first sale was $0.99. I still remember refreshing the dashboard for that 99 cents. The pipeline doesn't make the money for you. What it does is remove the paperwork failures, the wrong header, the duplicate post, the broken zip, so the only variable left is whether the work is good and whether you keep submitting. That's the part that takes volume, and volume is only survivable if the packaging is automated.
The four scripts in this post are the packaging layer of a larger system, built from roughly 19,000 lines of pipeline code and 300+ pages of research, validated on that same 1 vCPU / 2 GB RAM box. I've bundled the exact versions that packaged 39 products (856 MB of output) into the digital asset factory, along with the folder layout and the tiny-server habits above. It's the tool I wish I'd had before those three rejection emails. No hype, it's a working kit, judge it by what's in it.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)