How I Kept AI Asset Production Near Zero Cost With Only Pillow And Reportlab
I spent one Friday night building a metadata CSV for 400 stock vectors. Uploaded it to Adobe. Every row rejected. The header had four columns. Adobe wanted five. Nothing wrong with the art, nothing wrong with the keyword
I spent one Friday night building a metadata CSV for 400 stock vectors. Uploaded it to Adobe. Every row rejected. The header had four columns. Adobe wanted five. Nothing wrong with the art, nothing wrong with the keywords, just one missing column and the whole batch bounced.
That was the night I stopped doing metadata by hand.
The header is the whole job
Each channel demands a different column count and rejects anything else instantly. Adobe takes 5 columns. Vecteezy takes 4. Dreamstime takes 15. Same asset, three files, three headers, and one typo means a silent full batch rejection.
So make_metadata.py writes the header the platform demands and nothing else:
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 runs on the standard library alone. No install. Without --titles titles.json it title-cases the filename and uses it as the title, which is fine for a first pass and wrong for the actual submission. Replace titles before you upload.
The point is the header is not a detail. On these platforms the header is the submission.
Never post the same batch twice
Fastest way to get an account flagged is running your batch twice. It happens more than people admit. You lose track of which folder went up, or a script dies halfway, or you re-run because you were not sure it worked.
upload_tracker.py is a ledger. It remembers what is done.
python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Also standard library only. Also no install. If you only use these two scripts you never touch pip at all.
I have 1,700 assets made and 58 live on Adobe. Keeping that straight by memory is not possible. My first sale was $0.99, and it took long enough that losing the account to a double-post would have cost more than the sale was worth many times over.
Two libraries, that is the entire bill
I have two third-party packages in this pipeline: Pillow for the covers and reportlab for the PDFs. That is it.
pack_product.py takes a folder and produces a zip, a 1280x720 cover, and a 600x600 thumbnail:
python pack_product.py my-product "My Product" "A short subtitle" 9
It writes my-product.zip, my-product-cover.jpg, and my-product-thumb.jpg into a _system folder next to where you run it. Override with --dir or the PACK_ROOT environment variable, no need to edit the script. A zip with a real cover sells. A bare file does not.
md2pdf.py turns a Markdown doc into a styled PDF with headings, tables, lists, and links:
python md2pdf.py content.md product.pdf "My Product Title"
You already have the content. This makes it look like a product in one command.
Both are pure local rendering. No cloud account, no API key, no per-file charge. Pillow and reportlab both run fine on a small box, and that matters for the next part.
Running the whole thing on 1 vCPU and 2 GB
This pipeline has run on 1 vCPU, 2 GB RAM, no GPU. It works if you stop fighting the box.
One process at a time. Never fan out image jobs in parallel on a single core, you will swap and everything gets slower.
Call gc.collect() after each asset. Free memory between items instead of hoping the allocator notices.
Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss gives you the real ceiling. Average memory tells you nothing when one render spikes and kills the process.
Process one file per subprocess for heavy render steps, so memory returns to the OS instead of sitting in the parent.
Render at the resolution you actually need. Inspecting a preview at 60 dpi beats rendering it at 300.
None of these are tricks. They are the difference between a pipeline that finishes overnight and one that gets OOM-killed at item 40.
What it produced
The working set behind this: 39 finished products, 856 MB of output, roughly 19,000 lines of pipeline code in the parent project, and over 300 pages of research behind the rules the scripts enforce.
A fixed folder layout keeps a catalogue 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 raw material for future products. Deleting them to clean up is the mistake this project made once and will not repeat.
If you want the tools
I packaged the four scripts as the Pipeline Starter Kit: make_metadata.py, upload_tracker.py, md2pdf.py, pack_product.py. Two dependencies, two files that need no install, and the layout that holds the rest together.
It is the packaging layer of the Digital Asset Factory, the full system this work came out of. If you are already generating assets and losing nights to CSVs and duplicates, the four scripts are the part that pays for itself first. If you are not generating yet, build that first.
Building digital products? Free guides & kits for digital product sellers.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.