The Four Tasks I Stopped Scripting And Now Hand Off Before Every Upload Batch
The moment I stopped trusting my own automation was a Tuesday in March. I had a batch of 60 vectors ready for Adobe Stock, my script generated the CSV, I uploaded, and got rejected within minutes. Wrong header. Adobe's u
The moment I stopped trusting my own automation was a Tuesday in March. I had a batch of 60 vectors ready for Adobe Stock, my script generated the CSV, I uploaded, and got rejected within minutes. Wrong header. Adobe's uploader expects a specific 5-column layout, and my script had quietly written the columns in an order that looked fine to my eye and nothing like what their parser wants. I fixed that one by hand, then sat back and realized something uncomfortable: I had written scripts for the fun parts of this pipeline and left the boring, failure-prone parts to my own tired hands at 11pm.
That inversion became the rule I run by now. Here are the four tasks I deliberately stopped scripting and now hand off to small, boring, single-purpose tools before every upload batch.
Metadata headers are a hard gate, not a style choice
All three of my channels reject a wrong CSV header instantly. Adobe wants 5 columns. Vecteezy wants 4. Dreamstime wants 15. There is no partial credit and no warning, the file either parses or it does not.
So I stopped generating metadata inline in my batch scripts. Now a standalone tool, make_metadata.py, reads a folder of files and writes the exact header each platform demands, and nothing else. One command per platform:
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 uses the standard library alone, no install. If I pass --titles titles.json I get real titles and keywords. Without it, the filename is title-cased and used as the title, which is fine for a first pass but I always replace it before submitting. The point is not cleverness. The point is that the header string lives in one place and I have never had a rejection for formatting since.
Double-posting is the fastest way to get flagged
This one cost me a scare early on. I re-ran a batch script after a partial failure, and it re-uploaded everything that had already gone through. Duplicate submissions are the kind of thing that gets an account flagged, and on a small contributor account you do not get the benefit of the doubt.
The fix is not smarter scripting, it is a dumb ledger. upload_tracker.py keeps a plain record of what has been posted, so a re-run can never double-post:
python upload_tracker.py pending items.txt # shows what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
It is also standard library only. I check pending before every batch and mark done as I go. The lesson: the failure mode I feared most was solved by the least interesting tool in the folder.
A document is not a product until it looks like one
I had Markdown content sitting in research/ for months because turning it into something sellable felt like a whole project. It is not, it is one command. md2pdf.py uses reportlab to render headings, tables, lists and links into a clean PDF:
python md2pdf.py content.md product.pdf "My Product Title"
reportlab is one of exactly two third-party packages in my whole packaging layer. The other is Pillow. That constraint matters, because every dependency is something that can break on the tiny server I run this on.
Packaging is where sales actually happen
A zip with a real cover sells. A bare file does not. I used to skip covers when I was tired, and I could see it in the numbers on the listings that went out naked. pack_product.py bundles the files and renders 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, the cover and the thumbnail into a _system folder, and you can redirect it with --dir or the PACK_ROOT environment variable. No editing the script, which means no chance I fat-finger a path at the end of a long session.
The constraints that make all of this hold together
None of this is impressive hardware. The whole pipeline ran on 1 vCPU, 2 GB RAM, no GPU, and it packaged 39 products, about 856 MB of output, out of roughly 19,000 lines of pipeline code. The habits that make a box that small work:
- One process at a time. Never fan out image jobs in parallel on a single core.
- Call
gc.collect()after each asset to free memory between items. - Measure peak RSS with
resource.getrusage(RUSAGE_SELF).ru_maxrss, not average, because the peak is what kills you. - One file per subprocess for heavy render steps, so memory actually returns to the OS.
- Render at the resolution you need. Inspecting a preview at 60 dpi beats rendering at 300.
And one non-technical rule I learned the hard way: keep your research/ folder forever. I deleted scrape logs and notes once to "clean up" and then needed them as raw material for a later product. That mistake will not happen twice.
The pattern across all four tasks is the same. The risky, repetitive handoff steps are not where I want my judgment spent, and they are not where a script should be clever. They are where a small, boring tool with an exact job should sit.
If you want the same four tools, these are the exact scripts that packaged those 39 products, plus the folder layout and the small-server notes above. I put them together as the vector prompt pack, built from this work rather than written around it.
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.