DEV Community

How I Turn One Idea Into a Repeatable Product Pipeline (No GPU)

How I Turn One Idea Into a Repeatable Product Pipeline (No GPU)

I once uploaded the same 12 vectors twice in one afternoon. Same folder, same batch script, one coffee too many. The metadata had matching titles, so the platform flagged the account for duplicate submission. That was the day I stopped treating "upload" as a manual step.

The fix was not a framework or a cloud account. It was four Python scripts I could read in an evening. Here is what actually works.

Metadata is where every submission dies

Adobe, Vecteezy and Dreamstime each reject a wrong CSV header instantly. Not the file, the header. You can have perfect art and a single missing column and the whole batch bounces.

make_metadata.py builds the exact header each platform demands: 5 columns for Adobe, 4 for Vecteezy, 15 for Dreamstime. Nothing extra, nothing missing.

python make_metadata.py ./my-assets --platform adobe --out metadata.csv --ext .jpg
Enter fullscreen mode Exit fullscreen mode

One honest caveat. Without a --titles titles.json file, the script title-cases the filename and uses that as the title. That is fine for a first pass. Replace it before you submit, because filename-as-title reads like exactly what it is.

This matters more when you understand the scale of the stack. The parent project this came from is roughly 19,000 lines of pipeline code, and 300+ pages of research sit behind the rules these scripts enforce. The metadata rules are not arbitrary. They are the compressed output of a lot of rejected batches.

The double-post is the fastest way to lose an account

The 12 vectors were not a skill problem. They were a memory problem. I could not reliably remember which of 200 files I had already sent.

upload_tracker.py is a ledger. It remembers what you have posted, so a re-run physically cannot double-post.

python upload_tracker.py pending items.txt
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

It uses the standard library alone. No install, no database. A re-run on a finished batch shows an empty pending list and stops. That is the whole point.

A doc is not a product until it looks like one

You have the content. You need it to survive contact with a buyer. A raw Markdown file does not.

md2pdf.py turns a Markdown file into a styled PDF with headings, tables, lists and links in one command:

python md2pdf.py content.md product.pdf "My Product Title"
Enter fullscreen mode Exit fullscreen mode

This is the step people skip, and it is the one that changes the price you can charge. A styled PDF reads as a product. A .md file reads as a draft.

A zip with no cover does not sell

pack_product.py bundles the whole thing: zips the files, and renders a 1280x720 cover plus a 600x600 thumbnail.

python pack_product.py my-product "My Product" "A short subtitle" 9
Enter fullscreen mode Exit fullscreen mode

It writes the zip, cover and thumb into a _system folder. Point it elsewhere with --dir or the PACK_ROOT environment variable, no script edits needed.

All four scripts fit in one idea of a pipeline. I used them to package 39 products, 856 MB of output, on a single vCPU with 2 GB of RAM and no GPU. Not a demo. The actual run.

The folder layout that keeps a catalogue from becoming a junk drawer

project/
  assets/      raw output, one file per asset
  output/      ready-to-submit files + metadata.csv
  packs/       finished zips + covers
  research/    your notes and sources
  scripts/     these tools
Enter fullscreen mode Exit fullscreen mode

Two things I learned the hard way. Keep research/ forever. Your scrape logs and notes are the raw material for the next product, and deleting them to "clean up" is a mistake this project made once and will not repeat. Second, one process at a time. Never fan out image jobs in parallel on a one-core box.

Running it on a tiny server

The full stack here runs at $0. Hermes Agent handles scheduling, decisions and retries. 9Router is a local proxy that routes LLM calls. Both free. The packaging layer is the four scripts.

The RAM habits that make 2 GB work:

  • One process at a time, no parallel render fan-out.
  • Call gc.collect() after each asset to free memory between items.
  • Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss gives you the real ceiling.
  • Process one file per subprocess for heavy render steps, so memory returns to the OS.
  • Render at the resolution you actually need. A 60 dpi preview beats a 300 dpi render you throw away.

Where to start

If you only touch metadata and tracking, install nothing. make_metadata.py and upload_tracker.py need only Python 3.9+. The other two need exactly two packages: Pillow for the covers, reportlab for the PDF.

The order I would follow: get your folder layout right, run metadata once, track every upload, then package one product end to end. One idea, one repeatable pass. Then the next 38 get cheap.

I built the kit from this exact work, including the scripts that produced those 39 products. It is the same code, not a rewrite.

See the kit


Building digital products? Free guides & kits for digital product sellers.

Top comments (0)