Four Python Scripts That Package Stock Assets With Only Pillow And Reportlab
Last spring I lost a full evening to a CSV header. I had 40 vectors ready for Adobe Stock, generated overnight on my little 1 vCPU server, and I built the upload sheet by hand in a spreadsheet. Adobe rejected the whole batch on import. Not the art. The header. One column out of place and the reviewer never even saw my files. I fixed the header, re-uploaded, and then nearly double-posted three files because I had lost track of what was already submitted. That night I stopped hand-rolling packaging and wrote four scripts. They are still what I use, they ran the batch that produced 39 finished products and 856 MB of output, and the only third-party packages involved are Pillow and reportlab.
The metadata header is a compliance problem, not a formatting problem
All three big channels check your CSV before a human ever looks at your work. Adobe wants a 5-column layout. Vecteezy wants 4 columns. Dreamstime wants 15. Get the column count wrong and the upload fails instantly, before review. There is no partial credit.
So make_metadata.py writes exactly the header each platform demands 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 Without a titles file, it title-cases the filename and uses that as the title. That is fine for a first pass, but replace it before you submit. Reviewers see lazy filenames and it costs you. Pass --titles titles.json when you have real titles and keywords ready. The script uses only the Python standard library, so there is nothing to install for it at all.
Double-posting is the fastest way to get flagged
This is the mistake I almost made. Stock platforms treat repeated submissions as spam behavior, and a re-run of a batch script is exactly how it happens. You run the batch Monday, it half-fails, you fix a bug Thursday and run it again, and now three assets exist twice in the queue.
upload_tracker.py is a tiny ledger that remembers what is done:
python upload_tracker.py pending items.txt python upload_tracker.py done "asset-001.jpg" python upload_tracker.py status pending shows only what has not been posted, so a re-run physically cannot double-post. It is also standard library only. The whole point is that the boring safety tool should have zero dependencies, because the tool you skip installing is the tool that protects you.
A Markdown file is not a product until it is a PDF
Half my products started as a Markdown document. A raw .md file does not sell, and writing HTML templates for every product is how projects die. md2pdf.py takes one command:
python md2pdf.py content.md product.pdf "My Product Title" It handles headings, tables, lists, and links with clean styling, using reportlab under the hood. That is the second of only two third-party packages in the entire kit. Everything else is standard library.
Packaging: a zip with a real cover sells, a bare file does not
pack_product.py bundles everything in one step:
python pack_product.py my-product "My Product" "A short subtitle" 9 It zips the files and renders a 1280×720 cover and a 600×600 thumbnail with Pillow, writing my-product.zip, my-product-cover.jpg, and my-product-thumb.jpg into a _system folder. Point it elsewhere with --dir or the PACK_ROOT environment variable. No editing the script, no config file.
How this runs on 1 vCPU and 2 GB RAM
The whole pipeline ran on a single-core, 2 GB box with no GPU. That only works with habits, not hope:
- One process at a time. Never fan out image jobs in parallel on one core.
- Call
gc.collect()after each asset so memory actually frees between items. - Measure peak RSS, not average.
resource.getrusage(RUSAGE_SELF).ru_maxrssgives you the real ceiling. - Run heavy render steps one file per subprocess so memory returns to the OS.
- Render at the resolution you need. Inspecting a preview at 60 dpi instead of 300 saves an enormous amount of RAM.
And keep a fixed folder layout: assets/, output/, packs/, research/, scripts/. Keep research/ forever. I deleted my notes once to clean up and regretted it immediately. Your scrape logs and research are the raw material for the next product.
Where this came from
These four scripts came out of a parent project of about 19,000 lines of pipeline code, backed by over 300 pages of research into the platform rules they enforce. The metadata rules in particular are not guesses. They are the exact header layouts that pass review on Adobe, Vecteezy, and Dreamstime today, learned the hard way through the rejection I described at the top.
I collected all of it, the scripts plus the rules behind them, into the compliance metadata kit. It is the tool I built from this work, not a course. If you only need the metadata side, the two standard-library scripts alone are worth it, and they run on any Python 3.9 install with zero dependencies.
Building digital products? Free guides & kits for digital product sellers.