Two Packages, One Server: Metadata CSVs, Covers and PDFs for 39 Products
The first time a stock platform rejected one of my upload batches, it took me four hours to understand why. One column was out of order in the CSV. That's it. I had generated 39 products, zipped them, written the docs, and the batch died on a header row.
That rejection changed how I build. I stopped treating metadata and packaging as the boring afterthought and wrote scripts that enforce the rules instead of leaving them to my attention span at 1 a.m. Here is what actually matters.
Three platforms, three headers, no mercy
Adobe wants five columns. Vecteezy wants four. Dreamstime wants fifteen. Get one wrong and the whole file bounces before a human ever sees your art. I built one script, make_metadata.py, that writes the exact 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
It title-cases the filename and uses that as a first pass. Fine for a dry run, not fine for submission. Pass --titles titles.json to supply real titles and keywords before you upload. The script does not guess, it writes the columns.
If you only use this one and the tracker, you need no install at all. Standard library only.
A ledger beats your memory
The fastest way to get an account flagged is running a batch twice. I got close once. It was 861 files, a re-run after a crash, and only a gut feeling that stopped me. After that I wrote upload_tracker.py. It remembers what you posted:
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
A re-run of the same list cannot double-post, because the ledger knows. This is the least glamorous tool in the kit and the one I would pay for.
Docs that look like products
You have the content. You need it to look like something worth buying. md2pdf.py turns a Markdown file into a clean styled PDF with headings, tables, lists and links:
python md2pdf.py content.md product.pdf "My Product Title"
Packaging: a zip alone does not sell
pack_product.py zips the files and renders two images: a cover at 1280×720 and a thumbnail at 600×600.
python pack_product.py my-product "My Product" "A short subtitle" 9
It writes the zip, cover and thumb into a _system folder next to where you run it. Point it elsewhere with --dir or the PACK_ROOT environment variable. No editing the script.
That is the entire dependency story: two third-party packages across four scripts. Pillow for the covers, reportlab for the PDFs. Nothing else.
It ran on 1 vCPU and 2 GB of RAM
The full pipeline produced 39 products, 856 MB of output, from a single-core server with no GPU. It works because of five habits, not hardware:
- One process at a time. Never fan out image jobs in parallel on a one-core box.
-
Free memory between items.
gc.collect()after each asset. -
Measure peak RSS, not average.
resource.getrusage(RUSAGE_SELF).ru_maxrsstells you the real ceiling. - One file per subprocess for heavy render steps, so memory returns to the OS.
- Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering it at 300.
The folder layout matters too. Keep assets/, output/, packs/, research/ and scripts/ separate. And keep research/ forever. I deleted scrape logs once to "clean up." They were the raw material for the next three products. I won't repeat that.
What this came from
These four scripts sit under a larger pipeline: roughly 19,000 lines of code in the parent project, with 300+ pages of research behind the rules the scripts enforce. The tools are the packaging layer of everything I sell.
I packaged them as the compliance metadata kit: the four working scripts, the folder structure, and the RAM notes above. Not a tutorial that ends at "here's how you'd do it." The same tools that shipped 39 products on a 2 GB server.
If you have ever lost a day to a rejected CSV column, start there.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)