The moment I nearly quit was a Tuesday night in front of Adobe Stock's upload screen. I had 58 assets ready, I'd spent weeks making them, and the CSV got rejected in about four seconds. Wrong header. Adobe wants a 5-column format. Vecteezy wants 4 columns. Dreamstime wants 15. I had written one CSV and assumed a stock site is a stock site. They are not. I re-ran my batch upload to "fix it" and double-posted half the folder, which is exactly the kind of thing that gets an account flagged. That night I stopped making art for a week and wrote scripts instead.
Here is what I learned, with the numbers attached.
The metadata problem is a formatting problem, not a creativity problem
Every rejection I got early on was a header problem. Adobe's bulk upload expects exactly 5 columns in a fixed order. Vecteezy expects 4. Dreamstime expects 15. Get one wrong and the file is rejected before anyone looks at your art.
So I wrote make_metadata.py. It takes a folder of files and a platform flag and writes the header that platform demands, and nothing else. Three commands:
python make_metadata.py ./my-assets --platform adobe --out metadata.csv
python make_metadata.py ./my-assets --platform vecteezy --out vecteezy.csv
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv
It uses only the Python standard library. No pip install. You can pass a titles.json if you have real titles and keywords, and you should, because the fallback title-cases the filename, which is fine for a first pass and not fine for submission. Reviewers see lazy titles and they behave accordingly.
Double-posting is the fastest way to lose an account
This is the one nobody warns you about. You upload 40 files, something fails at file 31, you fix it and re-run the batch. Now 30 files exist twice. Platforms treat that as spam behavior. I did it once and got lucky. I decided I would never do it again.
upload_tracker.py is a small ledger:
python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Before any batch, I run pending and upload only what it shows. It also uses the standard library alone. It is the least interesting script in the set and the one that has saved me the most trouble.
A document is not a product until it looks like one
I had real content sitting in Markdown files. Nobody buys a raw .md file. md2pdf.py turns one Markdown file into a styled PDF with headings, tables, lists, and working links, in one command:
python md2pdf.py content.md product.pdf "My Product Title"
It uses reportlab, one of only two third-party packages in the whole kit. The other is Pillow. Everything else is standard library, because every dependency is something that breaks on a small server at 2 AM.
Packaging is where sales actually start
My first sale was $0.99 and it came from a product that had a real cover. Before that, bare zips sat unsold. The difference was not the content, it was the presentation.
pack_product.py bundles a product in one step:
python pack_product.py my-product "My Product" "A short subtitle" 9
It writes the zip, a 1280x720 cover, and a 600x600 thumbnail. Covers go in listings, thumbnails go in grids, and both sizes matter because marketplaces crop and scale aggressively. Output lands in a _system folder by default, and you can redirect it with --dir or a PACK_ROOT environment variable so you never edit the script.
The whole thing runs on 1 vCPU and 2 GB of RAM
I run this on a tiny server with no GPU, and it works because of habits, not hardware:
- One process at a time. Parallel image jobs on one core just thrash.
-
gc.collect()after each asset, so memory does not accumulate across a batch. - Measure peak RSS with
resource.getrusage(RUSAGE_SELF).ru_maxrss, not averages. The peak is what kills you. - One file per subprocess for heavy renders, so the OS actually gets the memory back.
- Render at the resolution you need. Inspecting a preview at 60 dpi instead of 300 saves most of the memory.
The folder layout that keeps a catalogue from becoming a junk drawer
project/
assets/ raw output
output/ ready-to-submit files + metadata.csv
packs/ finished zips + covers
research/ notes and sources
scripts/ these tools
One rule I learned the hard way: never delete research/. My scrape logs and notes are the raw material for future products. I cleaned that folder out once to "tidy up" and lost weeks of source material. It will not happen twice.
Where this stands now
That pipeline has produced about 1,700 assets, 58 are live on Adobe Stock, and the first sale was $0.99, which I keep as a reminder that the first dollar matters more than the size of it. The scripts packaged 39 products, about 856 MB of output, sitting under roughly 19,000 lines of pipeline code and 300+ pages of research behind the rules they enforce.
I am not going to pretend the numbers are bigger than they are. What I can say is that every rule in the scripts came from a real rejection, a real double-post, or a real out-of-memory crash, and the pipeline runs unattended on hardware that costs almost nothing.
I put the four scripts, the folder layout, and the RAM notes together into one kit called digital asset factory. It is the packaging layer I wish I had on that Tuesday night, and it plugs into a free stack if you want to grow it into a full pipeline. If it saves you one rejected CSV, it has done its job.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)