A Single-Core 2 GB Server, Two Packages, and 39 Packaged Products: The Raster Rendering Loop That Made It Work**
The moment that forced this loop was a rejection email from Adobe Stock. Batch of 40 JPEGs, all rejected in under an hour. I opened the CSV I had uploaded and looked at the header row. Five columns, and mine had six. That is the entire rule. Adobe's upload template is a 5-column CSV, and if the header does not match exactly, the batch dies before a human ever sees a thumbnail. I had written a metadata generator that guessed. Guessing does not survive contact with a CSV parser. That failure, and two more like it on Vecteezy and Dreamstime, is why the rendering and packaging loop I run now is boring on purpose. Here is what it looks like on a server with 1 vCPU and 2 GB of RAM, and why it has held up across 1,860 assets made and 1,077 ready or submitted. The metadata headers are a contract, not a suggestion Every stock platform has its own CSV shape, and they do not forgive creativity: Adobe Stock: 5 columns. Filename, title, keywords, category, releases. Wrong header, instant reject. Vecteezy: 4 columns. Filename, title, keywords, plus the moderation fields the template expects. Dreamstime: 15 columns. The widest of the three, and the easiest to get subtly wrong. My make_metadata.py script writes those three headers verbatim from a folder of files. It takes --platform adobe, --platform vecteezy, or --platform dreamstime, and it writes the header the platform demands and nothing else. Since I stopped generating headers dynamically, my CSV rejections went to zero. The rejection reasons that still come back are content reasons, which is the only kind of rejection worth getting. One honest caveat: if you do not pass a titles file, the script title-cases the filename. That is fine for a first pass, but replace generic filenames with real titles and keywords before you submit. Filenames like asset-001.jpg do not help you in search. Double-posting is the fastest way to get flagged Early on I ran a batch twice because I lost track of what I had already uploaded. Duplicate submissions are exactly the kind of behavior that gets an account flagged. So now there is a ledger. upload_tracker.py is a plain-text tracker with three commands: pending shows what has not been posted, done marks an item, status summarizes. A re-run reads the ledger first and skips anything already marked. It uses the standard library only, no install, and it has prevented every double-post since I built it. The lesson generalizes: any step in your pipeline that is not idempotent will eventually bite you. Make the tracking part of the pipeline, not part of your memory. Rendering on 1 core and 2 GB is a discipline, not a limitation The whole pipeline runs on a 1 vCPU, 2 GB RAM box with no GPU. That constraint shaped the loop more than anything else: One process at a time. I never fan out image jobs in parallel. On one core, parallelism just adds memory pressure and context switching for zero throughput gain. gc.collect() after every asset. Python holds references longer than you think. Without this, a 300-asset batch would creep toward the 2 GB ceiling. Measure peak RSS, not average. resource.getrusage(RUSAGE_SELF).ru_maxrss tells you the real ceiling you are living under. Average memory lies; peak memory gets your process OOM-killed. One file per subprocess for heavy renders. When the render step exits, the OS reclaims the memory. A long-lived process accumulates. Render at the resolution you need. Inspecting a preview at 60 dpi instead of 300 is the single biggest time and memory saving in the loop. AI image generation costs money per image, so every wasted render is a direct loss. The 60 dpi preview habit alone cut my iteration cost noticeably, because I stopped rendering full resolution just to check composition. Packaging is where a file becomes a product A zip with a real cover sells. A bare file does not. pack_product.py takes a folder and produces three things: the product zip, a 1280×720 cover, and a 600×600 thumbnail. Covers are rendered with Pillow, one of only two third-party packages in the whole kit. The other is reportlab, for md2pdf.py, which turns a Markdown file into a styled PDF with headings, tables, lists, and links in one command. The metadata and tracking scripts need nothing but Python. The folder layout matters as much as the scripts: project/ assets/ raw output output/ ready-to-submit files + metadata.csv packs/ finished zips + covers research/ notes and sources scripts/ the tools Keep research/ forever. I deleted mine once to "clean up" and lost the raw material for several future products. That mistake cost me more than any server bill. What the loop produced With this loop running: 39 packaged products, 856 MB of output, from roughly 19,000 lines of pipeline code and 300+ pages of research behind the rules the scripts enforce. The first paid sale through this system was for the Digital Asset Factory itself, $2.80. I am not going to pretend that number is anything but small. What it proves is that the pipeline end to end works: assets get made, metadata passes review checks, products get packaged, and something sells. The four scripts I described are the packaging layer of that system. They are standalone, no framework, no cloud account, Python 3.9+, and two pip installs if you want the PDF and cover tools. I bundled them, with the folder conventions and the RAM notes above, into the Digital Asset Factory, the full system built from this work. If you are a maker drowning in the same metadata and packaging problems I was, it is the honest version of what worked for me, failures included. Get digital asset factory Building digital products? Free guides & kits for digital product sellers.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to