하나의 헤더 행 때문에 오후 제출 작업 전체를 날렸습니다.
그날은 화요일이었습니다. 저는 Adobe Stock에 올릴 40개의 JPG 파일과 전날 밤 직접 만든 CSV 파일을 가지고 있었고, 자신만만하게 업로더에 붙여넣었습니다. 거절 통보는 즉각적이었습니다. 열(column) 개수가 잘못되었다는 것이죠. Adobe는 특정 순서로 다섯 개의 열을 요구하는데, 제가 키워드 열의 위치를 잘못 잡았던 것입니다. 그래서 수정하고 재제출한 후, Vecteezy를 열어보니 그들의 CSV는 네 개의 열이 다른 순서로 필요하다는 것을 깨달았습니다. 그리고 Dreamstime은 무려 15개의 열이었습니다. 저는 자정까지 같은 파일에 대해 스프레드시트에서 동일한 메타데이터 행을 손으로 세 번이나 다시 작성해야 했습니다.
그날 밤, 저는 다시는 메타데이터 행을 직접 타이핑하지 않겠다고 결심했습니다. 실제로 작동하는 방법은 다음과 같습니다.
하나의 폴더 입력, 세 개의 CSV 출력
핵심 통찰은 지루합니다: 에셋 자체는 절대 변하지 않고, 오직 이를 감싸는 '래퍼(wrapper)'만 바뀐다는 것입니다. 모든 플랫폼이 요구하는 것은 제목, 키워드, 카테고리 등 동일한 근본 데이터이며, 단지 그 형태만 다를 뿐입니다. 따라서 해결책은 하나의 폴더의 파일을 읽어와 각 플랫폼이 정확히 요구하는 CSV를 출력하는 단일 스크립트가 됩니다.
make_metadata.py가 이 역할을 수행합니다. 이 스크립트에 폴더를 지정하면 다음과 같습니다:
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
같은 폴더에서 세 개의 결과물이 나옵니다. 각 파일은 해당 플랫폼의 업로더가 가장 먼저 확인하는 헤더를 가지고 있습니다. 검토 과정에서는 사람이 작품을 보기 전에 잘못된 헤더 때문에 거절당하기 때문에, 올바른 헤더를 갖추는 것은 선택적인 다듬기가 아니라 필수 관문(gate)인 셈입니다.
제목과 키워드의 경우, 이 스크립트는 titles.json 파일을 받습니다. 이 파일이 없으면 파일명을 제목으로 자동 대문자 변환하여 사용합니다. 이는 첫 시도에는 괜찮지만, 제출하기 전에 반드시 대체해야 합니다. floral_wreath_v2.jpg와 같은 파일명은 형편없는 스톡 제목과 더 나아가 형편없는 키워드를 만듭니다.
전체 과정은 표준 라이브러리만 사용합니다. pip install도, API 키도, 클라우드 계정도 필요 없습니다. 이 점이 생각보다 중요합니다. 왜냐하면 마감 기한에 실제로 사용하는 도구는 설정 과정의 마찰(friction)이 전혀 없는 도구이기 때문입니다.
아무도 경고해주지 않는 더블 포스트 문제
제가 스트레스 받는 방식으로 학습한 실패 사례가 있습니다. 업로드 배치 작업을 두 번 실행하면 자체 계정이 플래그 지정될 수 있습니다. 기여자들은 중복 제출 건으로 검토를 받게 되며, 자동화된 파이프라인은 중복을 너무나 쉽게 만듭니다. 스크립트를 재실행하면 트래커 상태가 오래되었고(stale), 이제 같은 파일이 큐에 두 번 들어갑니다.
그래서 두 번째 스크립트는 단순하고 지루한 원장입니다. upload_tracker.py는 이미 게시된 것을 기억합니다:
python upload_tracker.py pending items.txt # 아직 완료되지 않은 항목
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
이것은 영리하지 않습니다. 그게 요점입니다. 관리자 패널이 있는 sqlite 데이터베이스가 더 인상적이고 덜 사용될 것입니다. cat으로 읽을 수 있는 텍스트 원장은 제출 배치 작업 전에 실제로 신뢰하게 되는 것입니다.
나머지 파이프라인은 거의 아무것도 없이 실행됩니다
저는 이 과정에 실제 서버가 필요하다는 가정에 반대하고 싶습니다. 현재 카탈로그를 생성하는 전체 파이프라인, 즉 완성된 제품 39개, 출력물 856 MB는 vCPU 1개, RAM 2 GB, GPU 없이 실행되었습니다. 이것을 가능하게 만든 습관들은 다음과 같습니다:
- 한 번에 하나의 프로세스만 처리합니다. 단일 코어에서 이미지 작업을 병렬로 분산(fan out)시키지 마세요. 그저 과부하(thrashes)만 일으킵니다.
- 각 에셋 처리 후
gc.collect()를 호출합니다. 2 GB 메모리 환경에서는 항목 간 누수된 메모리가 완료와 에셋 30개에서 죽음의 차이를 만듭니다.
- 평균 RSS가 아닌 최대(peak) RSS를 측정하세요.
resource.getrusage(RUSAGE_SELF).ru_maxrss는 실제 상한선(ceiling)을 알려주며, 평균 메모리는 거짓말을 합니다.
- 무거운 렌더링 단계의 경우, 서브프로세스당 하나의 파일만 사용하여 메모리가 실제로 OS로 반환되게 하세요.
- 필요한 해상도에서 렌더링하세요. 300dpi 대신 60dpi에서 미리보기를 검사하는 것이 제가 시도했던 어떤 최적화보다 시간과 RAM을 더 많이 절약해줍니다.
메타데이터 및 추적 스크립트는 거의 사용하지 않기 때문에 이 예산 내에 들어가며 여유 공간이 있습니다.
패키징은 별개의 문제입니다. 자체 스크립트가 필요합니다
저를 반복적으로 막혔던 두 가지 문제가 더 있어, 그것들에도 스크립트를 추가했습니다.
마크다운(Markdown) 문서를 판매 가능한 PDF로 변환하는 방법: md2pdf.py content.md product.pdf "내 제품 제목"을 사용하면 reportlab을 활용하여 실제 제목, 표, 목록 및 링크가 포함된 스타일리시한 PDF를 얻을 수 있습니다. 워드(Word) 파일에 손댈 필요 없이 한 번의 명령으로 끝납니다.
제품 묶음(Bundling): pack_product.py는 파일을 압축하고 1280x720 커버와 600x600 썸네일을 렌더링합니다. 실제 커버가 있는 zip 파일은 판매되지만, 아무 내용 없는 파일은 그렇지 않습니다. 그리고 39개 제품의 커버를 수작업으로 만드는 것은 절대 불가능했습니다. 이 작업에는 Pillow가 필요한데, 이는 전체 키트에서 단 두 개의 서드파티 종속성 중 하나입니다.
폴더 레이아웃도 예상보다 중요합니다:
project/
assets/ 원본 출력물
output/ 제출 준비 파일 + metadata.csv
...
research/ 폴더는 영원히 보존해야 합니다. 저는 예전에 오래된 스크랩 로그와 노트를 '정리'하기 위해 삭제했다가, 후속 제품에 정확히 그 자료들이 필요했던 적이 있습니다. 다시는 그런 실수를 하지 않을 것입니다.
어디서 얻을 수 있나요
위에 설명된 모든 것은 더 큰 파이프라인의 패키징 계층이며, 이 파이프라인 코드는 약 19,000줄에 달하고, 해당 플랫폼들이 제출물을 실제로 검토하고 거부하는 방식에 대한 300페이지 이상의 리서치 자료를 기반으로 합니다. 저는 제가 직면했던 실제 병목 현상을 해결한 네 가지 스크립트를 독립적인 키트로 모았으며, 이것이 바로 그 39개 제품을 패키징했던 것과 동일한 스크립트들입니다. 만약 당신도 자정 무렵 CSV 파일을 손으로 작성하는 것에 지쳤다면, 여기에서 얻을 수 있습니다:
벡터 프롬프트 팩 받기
디지털 제품 제작 중이신가요? 디지털 제품 판매자를 위한 무료 가이드 및 키트.