[KongchangAI]
· 3 min read· 1,542 words

Unifying a Digital Product Catalog with Four Python Scripts: From Junk Drawer to Branded Store

Unifying a Digital Product Catalog with Four Python Scripts: From Junk Drawer to Branded Store

Four lightweight Python scripts unify a product catalog into one consistent brand.

An indie developer turns "forty products that look like forty stores" into a single branded catalog on a 1 vCPU, 2 GB server using four Python scripts depending only on Pillow and reportlab: pack_product.py for consistent covers and thumbnails, make_metadata.py for platform-specific CSVs, upload_tracker.py for dedupe, and md2pdf.py for styled PDFs. Plus fixed folder rules and low-memory discipline. It's a packaging layer, not distribution.

An indie developer opens their Gumroad dashboard, with 40 finished product folders sitting off to the side, and feels a pang of embarrassment. The products themselves are fine, but the entire catalog looks like it was made by forty different strangers. Covers range from dark blue serif fonts to neon green clip art, and some even use a screenshot of a ZIP file as the cover. A buyer emailed to ask: are these really from the same store? That moment became a turning point—shifting from treating packaging as an afterthought to running the entire product catalog as a single product.

RSS source article image

The method documented in this article has a hard constraint: the entire workflow runs on a server with 1 vCPU, 2 GB of RAM, and no GPU. The tools are four standalone Python scripts—no framework, no cloud account, and the only third-party dependencies are Pillow and reportlab. The final output is 39 products, 856 MB of packaged output, backed by roughly 19,000 lines of pipeline code. For indie developers and small teams, a toolchain of this scale is actually more controllable than SaaS.

The Core Mindset Shift: The Catalog Is One Product, Multiple SKUs

Buyers don't evaluate your ZIP files one by one—they evaluate your store. When covers, filenames, and folder structures are inconsistent, every new product launch dilutes your store's credibility instead of adding to it.

The author's approach is to pick one cover template and never change it again. pack_product.py renders all covers (1280x720) and thumbnails (600x600) through the same Pillow code path, with identical fonts and layout—only the title and subtitle change. A single command writes out the ZIP, cover, and thumbnail at once:

python pack_product.py my-product "My Product" "A short subtitle" 9

By the ninth product, all covers are already interchangeable. That's exactly the goal—a buyer who liked one of your products should recognize the next one as yours at a glance.

Naming and Directory Structure: Lock It Down Before Writing Code

A directory turns into a junk drawer when its structure is improvised every time. The fixed structure given in the article is: assets/ for raw output, output/ for files to be submitted plus metadata.csv, packs/ for finished ZIPs and covers, research/ for notes and sources, and scripts/ for tools.

Two rules that sound boring but matter a lot. First, filenames become titles. The metadata script converts filenames into title case when there's no title file, so botanical-border-04.jpg generates a usable title on the first pass; if your filename is final_final_v3.jpg, that's what gets inherited into the CSV. Second, never delete research/. The author once deleted scraping logs to "clean up," and lost the raw source material for three subsequent products—never to be recovered.

Metadata: Stock Platforms Filter You Out with CSV Format First

If you also sell on stock platforms, the catalog problem doubles—each platform requires a different CSV shape, and a wrong header gets you rejected instantly. Adobe Stock requires a 5-column header, Vecteezy 4 columns, and Dreamstime 15 columns. Get one column wrong, and the entire batch bounces back before a human ever sees your work.

make_metadata.py works by writing only the headers each platform requires—not a single extra column. The same batch of assets, three commands, 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

Just like the cover logic, titles and keywords for all assets across all platforms come from the same single source of truth. Whether a buyer finds you on Adobe or in your own store, the brand reads the same.

How stock platform CSV uploads work: Microstock platforms like Adobe Stock, Vecteezy, and Dreamstime offer bulk upload channels in addition to uploading images one by one on the web—usually a ZIP containing image files plus a metadata CSV. Each row in the CSV corresponds to one image, with columns for title, keywords, category, and other fields. When validating, the platform parses the header first: column names, column count, and order must exactly match the official template, or the entire batch is rejected by the system before it reaches manual review—and the error message often just says "format error" without specifying which column. That's why a single wrong header column can scrap the whole batch. Since each platform's template is incompatible with the others, the same batch of assets must be uploaded to multiple platforms with multiple CSVs, while keywords and titles must stay consistent—otherwise the same work presents fragmented brand information across platforms. Generating CSVs from a single data source according to each platform's template is exactly how scripts solve this conflict.

The Fastest Way to Get Banned: Duplicate Uploads

This one isn't about aesthetics—it's about survival. The fastest way to get your stock account flagged is to run an upload batch twice. The author once re-ran a partially failed script and published 30 duplicates at once, spent an entire evening cleaning it up, and got off lucky.

upload_tracker.py is a pure ledger that remembers what's already been published:

python upload_tracker.py pending items.txt   # show what's not done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status

Check the ledger before enqueueing, and re-runs will never duplicate a publish. Uses only the Python standard library, zero install.

Document Products: Markdown Isn't a Product—PDF Is

Half of the author's catalog isn't image assets but documents like guides, checklists, and prompt handbooks. A raw Markdown file is not a product. md2pdf.py renders a Markdown file into a clean PDF with styled headings, tables, lists, and links:

python md2pdf.py content.md product.pdf "My Product Title"

The only dependency is reportlab, and layout is consistent every time, so the PDFs in your catalog visually match the covers beside them.

Survival Habits on a Small Server

This workflow doesn't need a bigger machine, but it needs discipline. On a single core, never run more than one process at a time—don't parallelize image tasks; call gc.collect() after each asset is processed to free memory; measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss rather than average usage, because it's the peak that kills a 2 GB machine; run re-renders in subprocesses, one process per file, so memory is returned to the OS; and preview at 60 dpi when you only need to check layout, rather than rendering at 300 dpi.

The meaning of peak RSS and gc.collect(): RSS (Resident Set Size) is the physical memory a process is actually using at a given moment. Python uses reference counting plus cyclic garbage collection, and memory from freed objects often isn't returned to the OS immediately—it stays in the interpreter's memory pool for reuse, so average memory looks low while the peak can instantly blow past a 2 GB machine. gc.collect() forces a full collection, promptly freeing memory held by cyclic references. And resource.getrusage(RUSAGE_SELF).ru_maxrss returns the peak resident memory over the process's lifetime—that's the real metric for judging "will this OOM." Re-renders go in subprocesses because when a subprocess exits, the OS reclaims all its memory at once—more thorough than manual cleanup in a long-running process.

Multiple Perspectives and Scope of Applicability

It should be noted that this article's methodology comes mainly from a single developer's practical experience and lacks cross-validation from multiple sources. Practices like "unified cover template, filename-as-title, upload ledger to prevent duplicates" align with long-standing wisdom in the indie developer community and are fairly well-established; whereas specific CSV column counts and script implementation details depend on the author's own stock channels and need to be re-verified when switching platforms.

Also, this workflow has a clear positioning: it's a packaging layer, not a distribution layer. It solves "does this look like one brand" and "will I get banned for format errors"—not pricing, product selection, or traffic. For indie sellers with more than twenty SKUs who list on multiple channels simultaneously, the payoff is most obvious; at the three-to-five product stage, manual maintenance may be faster than building a pipeline.

The four scripts—make_metadata.py, upload_tracker.py, md2pdf.py, pack_product.py—are the real takeaway of this article. No frameworks, no cloud accounts, just two third-party packages, and half the scripts need only Python itself. The tools aren't complicated; the hard part is sticking with the decision to treat the catalog as a product.

One-line Summary

Four lightweight Python scripts unify packaging and distribution so the product catalog reads as one brand.

Paragraph Summary

The article chronicles an indie developer's practice of running the entire product catalog as a single product, starting from the embarrassment of "forty products that look like forty stores." The method runs on a 1 vCPU, 2 GB server with no GPU and relies on four Python scripts with only Pillow and reportlab: pack_product.py renders covers and thumbnails consistently, make_metadata.py generates CSVs matching Adobe, Vecteezy, and Dreamstime headers, upload_tracker.py uses a pure standard-library ledger to prevent duplicate uploads, and md2pdf.py renders Markdown into style-consistent PDFs. Alongside are rules like a fixed directory structure, filename-as-title, never deleting the research directory, plus low-memory survival discipline: serial processing, gc.collect, and measuring peak RSS. The author stresses this is a packaging layer, not a distribution layer, suited to sellers with over twenty SKUs listing on multiple channels.

#Python scripts#digital product catalog#brand packaging#stock platform CSV#indie developer workflow
Share:

Related articles