Two Lines in Requirements.txt: Pillow, Reportlab, and Nothing Else
The rejection email was four words long: "Invalid CSV header."
I had just spent three hours naming 60 vector files and writing titles. All of it was sitting in a spreadsheet I built by hand, and Vecteezy's uploader refused the whole batch because one column was in the wrong place. No partial credit. No "fix it and retry." Just reject.
That was the moment I stopped treating packaging as something I would do "later, carefully." It turns out "carefully" is not a schedule. So I wrote the scripts. They shipped 39 products out the door, and the entire dependency list is this:
pillow
reportlab
Two lines. The rest is the Python standard library. Here is what actually works and why.
Wrong headers get rejected instantly, so generate them
Adobe, Vecteezy and Dreamstime each want a different CSV. Adobe takes 5 columns, Vecteezy takes 4, and Dreamstime takes 15. Get one header text wrong and the whole upload dies before a human ever looks at your art.
So make_metadata.py does one job: it walks a folder of files and writes the exact header each platform demands, nothing more. Three runs, three files:
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 extra input, the filename is title-cased into the title. That is fine for a first pass, but replace it before you submit. The script accepts --titles titles.json when you have real titles and keywords ready.
This script needs no install. Standard library only. If metadata is the thing you keep putting off, you can fix it today with zero packages.
Double posting is how accounts get flagged
The second failure mode is sneakier. You run a batch halfway, something interrupts you, you run it again, and suddenly 40 of your 60 assets are live twice. Running a batch twice is the fastest way to get an account flagged.
upload_tracker.py is a plain ledger. It remembers what you have marked done, so a re-run only shows what is left:
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
No database, no cloud account, no sync service. A file plus a script. It has never failed me because there is nothing in it to fail.
A product needs a cover, and a PDF needs a renderer
A zip with a real cover sells. A bare file does not. That is the only reason Pillow is in the requirements. pack_product.py zips the files and renders a 1280x720 cover plus a 600x600 thumbnail:
python pack_product.py my-product "My Product" "A short subtitle" 9
The other package, reportlab, exists because a PDF with real headings, tables, lists and links makes a Markdown doc look like a product instead of a draft:
python md2pdf.py content.md product.pdf "My Product Title"
Those are the only two third-party packages. Everything else, the CSV generation, the tracking, the zip, runs on Python alone.
The whole pipeline ran on 1 vCPU and 2 GB of RAM
This is the part people find hard to believe. The pipeline that produced 39 products and 856 MB of output ran on 1 vCPU, 2 GB of RAM and no GPU. It held together because of a few unglamorous habits:
- One process at a time. Never fan out image jobs in parallel on one core.
- Call
gc.collect()after each asset to free memory between items. - Measure peak RSS, not average.
resource.getrusage(RUSAGE_SELF).ru_maxrsstells you the real ceiling. - Run heavy render steps as one file per subprocess, so memory goes back to the OS.
- Render at the resolution you need. A 60 dpi preview beats a 300 dpi one when you are just checking.
The parent project has roughly 19,000 lines of pipeline code and 300+ pages of research behind the rules these scripts enforce. The scripts themselves are small. The point is that small is enough.
The folder layout that keeps it from turning into a junk drawer
project/
assets/ raw output (one file per asset)
output/ ready-to-submit files + metadata.csv
packs/ finished product zips + covers
research/ your notes and sources
scripts/ these tools
Keep research/ forever. Scrape logs and notes are the raw material for the next product. Deleting them to "clean up" is a mistake I made once and will not repeat.
These four scripts sit at the packaging layer. Above them, the full pipeline runs on a free stack: Hermes Agent schedules jobs, retries failures and makes decisions, with 9Router handling local LLM access. The packaging layer is where the rules from 300+ pages of research get enforced, and it is the part you can copy today.
None of this is clever. The requirements file is two lines. The scripts are boring on purpose. That is exactly why they shipped 39 products instead of sitting in a backlog with the metadata CSVs I never got around to fixing.
If you are building vector packs, stickers or templates and the packaging keeps stalling you, this is the tool built from that work, the same scripts and the playbook behind them.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)