I ran the same batch twice on a Tuesday night in March. Forty-one JPGs, already uploaded to Adobe Stock the night before, uploaded again because my terminal history was right there and I was tired. Adobe's review queue doesn't forgive that. Duplicate submissions get both copies rejected, and enough of them gets your account flagged. I sat there watching the same filenames hit the upload form twice and realized the problem wasn't carelessness. It was that I had no memory between runs.
That night I wrote the first version of upload_tracker.py. It's about eighty lines of standard library Python and it has caught a double-post three times since. Here's what I learned building the ledger and the CSV builders around it, and why the boring parts of this pipeline matter more than the art.
The CSV headers are a hard gate, not a suggestion
All three stock platforms reject a malformed CSV on upload, before a human ever looks at your work. Adobe wants 5 columns. Vecteezy wants 4. Dreamstime wants 15. Get one column count wrong and the whole file bounces.
make_metadata.py writes exactly those headers and nothing else. You point it at a folder:
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 title-cases the filename if you don't supply titles, which is fine for a first pass. It is not fine to submit. Keyword quality is the difference between page four and page one of search results, so pass --titles titles.json with real titles and keywords once you have them. I keep one JSON per product batch and reuse the keyword sets that performed.
A ledger that makes double-posting impossible
The tracker is a flat text ledger, one asset per line. Three commands:
python upload_tracker.py pending items.txt # what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
The workflow that fixed my Tuesday problem: generate items.txt from the folder, run pending before every upload session, mark done as each file goes up. If the batch dies halfway through, pending picks up exactly where you stopped. A re-run can't re-post because the ledger only ever lists what hasn't been done.
I keep the ledger file in the output/ folder next to the CSV it tracks. One ledger per platform per batch. It's dumb and it works, and dumb is what you want at 1 a.m.
One folder layout, kept forever
Without a fixed structure a catalogue becomes a junk drawer fast. Mine:
project/
assets/ raw output
output/ ready-to-submit files + metadata.csv
packs/ finished zips + covers
research/ notes, scrape logs, sources
scripts/ the tools
The one I'd defend hardest is research/. I deleted mine once to "clean up". Six weeks later I wanted the keyword notes behind a product that had sold and they were gone. Your research logs are the raw material for the next three products. Keep them.
Small server habits that keep the pipeline alive
The whole thing runs on 1 vCPU, 2 GB RAM, no GPU. That's not a limitation, it's a design constraint, and it forced habits worth keeping even on bigger machines:
- One image job at a time. Parallel fan-out on a single core just thrashes.
-
gc.collect()after each asset. Freeing memory between items keeps peak usage flat. - Measure peak RSS, not average:
resource.getrusage(RUSAGE_SELF).ru_maxrssshows the real ceiling. - One file per subprocess for heavy render steps, so memory goes back to the OS.
- Render previews at 60 dpi when you're only inspecting them. 300 dpi inspection is wasted RAM.
The result: the pipeline packaged 39 products, 856 MB of output, on that box without a single out-of-memory kill.
The rest of the kit
Two more scripts round it out. md2pdf.py turns a Markdown doc into a styled PDF (headings, tables, lists, links) with one command, which is how I turn research notes into sellable docs. pack_product.py zips a product and renders a 1280×720 cover plus a 600×600 thumbnail, because a zip with a real cover sells and a bare file does not.
Dependencies are deliberately tiny. Pillow and reportlab, that's it, and only for the PDF and packaging scripts. make_metadata.py and upload_tracker.py are standard library only. No install, no cloud account, no framework. Python 3.9+ and you're running.
Where to get it
I bundled these four scripts, plus the folder layout and the RAM notes, into the pipeline starter kit that sits under my vector prompt pack workflow. The same tools that packaged those 39 products. If you're building the same kind of pipeline, they'll save you the Tuesday night I lost.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)