Pillow와 Reportlab만으로 메타데이터, PDF 및 커버를 처리하는 단일 코어 서버의 활용법
요약
본 글은 Pillow와 Reportlab 같은 기본적인 파이썬 라이브러리를 활용하여 메타데이터 처리, PDF 생성 및 커버 제작 등 복잡한 디지털 자산 워크플로우를 단일 코어 서버에서 자동화하는 방법을 소개합니다. 특히 스톡 플랫폼의 엄격한 요구사항에 맞춰 CSV 헤더를 맞추고, 중복 업로드를 방지하는 간단한 로직을 구현하는 것이 핵심입니다.
핵심 포인트
- 스톡 플랫폼은 CSV 헤더 구조가 매우 엄격하므로, 각 플랫폼별 요구사항에 맞는 메타데이터 스크립트가 필수적이다.
- 중복 게시물 방지를 위해 데이터베이스 없이도 작동하는 간단한 원장(ledger) 로직을 구현할 수 있다.
- Reportlab과 Pillow 같은 표준 라이브러리만으로 PDF 생성 및 이미지 렌더링 등 복잡한 작업을 처리 가능하다.
- 고성능 하드웨어보다, 반복적이고 지루한 부분을 자동화하는 스크립트 작성 능력이 더 중요하다.
지난 3월, 저는 새벽 2시에 업로드 스크립트를 통해 40개의 에셋을 일괄 처리했습니다. 뿌듯한 기분으로 잠자리에 들었고, 아침에 일어나 보니 Adobe가 전체 CSV를 거부했습니다. 헤더의 한 열이 잘못된 것이었습니다. 40개의 행이 잘못된 것이 아니라, 첫 번째 행의 열 개수가 잘못된 것입니다. 바로 이 순간, 저는 스프레드시트를 수동으로 편집하는 것을 멈추고 지루한 부분을 정확하게 처리하는 스크립트를 작성하기로 결심했습니다. 왜냐하면 실제로 여러분의 출시를 막는 것은 바로 그 지루한 부분들이기 때문입니다.
저는 모든 것을 vCPU 1개와 RAM 2GB가 장착된 서버에서 실행합니다. GPU도, 클라우드 계정도 없습니다. 사람들은 더 많은 것이 필요하다고 생각하지만, 단일 코어가 실제로 할 수 있는 것에 대한 존중을 보여준다면 그렇지 않습니다. 제가 사용한 방법과 그 이유를 알려드리겠습니다.
CSV 헤더는 계약서이며, 플랫폼들이 가장 먼저 확인하는 항목입니다
Adobe Stock은 5개 열의 CSV를 요구합니다. Vecteezy는 4개 열을 요구하고, Dreamstime은 15개 열을 요구합니다. 그들 중 어느 곳이든 단 하나의 이미지가 검토되기 전에 잘못된 헤더를 가진 파일을 거부합니다. 저는 제가 잘못된 파일들을 넣어보면서 알게 되었습니다.
그래서 make_metadata.py는 각 플랫폼이 요구하는 정확한 헤더만 작성하고 다른 것은 아무것도 쓰지 않습니다. Adobe에게는 5개 열을, Vecteezy에게는 4개 열을, Dreamstime에게는 15개 열을 제공합니다. 이 스크립트는 파일 폴더를 읽어 파일 이름을 제목 형식(title-case)으로 변환하여 행을 채웁니다. 만약 실제 제목과 키워드가 담긴 titles.json 파일을 전달하면, 그 내용을 사용합니다. 이것이 없다면 파일 이름 기반의 제목이 생성되는데, 이는 첫 번째 시도에는 괜찮지만 최종 제출물로는 좋지 않습니다. 업로드하기 전에 반드시 교체하세요. 이 플랫폼들의 검토 과정은 매우 엄격해서, 게으른 메타데이터를 수정하는 것이 가장 저렴한 비용으로 해결할 수 있는 문제이며 거부되는 가장 흔한 이유입니다.
중복 게시물(Double-posting)은 계정 경고를 받는 가장 빠른 방법입니다
이것 때문에 저는 실제 불안감을 느꼈습니다. 배치 스크립트를 두 번 실행했는데, 이미 실행했다는 것을 잊었기 때문입니다. 그러면 중복 제출물이 생깁니다. 스톡 플랫폼들은 이를 스팸 행위로 간주하고 계정에 경고를 줍니다.
수정 사항은 매우 간단합니다. upload_tracker.py는 데이터베이스가 필요 없는 표준 라이브러리 기반의 원장(ledger)입니다. 자산 하나를 완료로 표시하면, 재실행 시 보류 중인 항목만 보여줍니다. 파이썬 코드가 약 80줄 정도였고, 이로써 실수하는 전체 범주를 제거했습니다. 자동화된 배치 작업을 실행하려면, 아무리 기발한 것이 필요하기 전에 이런 종류의 기능이 필요합니다.
Pillow와 reportlab으로 사람들이 프레임워크가 필요하다고 생각하는 것의 90% 커버
마크다운 파일에서 제품용 PDF를 만들 때, 저는 무거운 옵션들을 살펴보다가 탭을 닫았습니다. md2pdf.py는 reportlab만 사용합니다: 제목(headings), 표(tables), 목록(lists), 링크(links), 깔끔한 스타일링 등 모든 것을 하나의 명령어로 처리합니다:
python md2pdf.py content.md product.pdf "My Product Title"
커버도 마찬가지입니다. 실제 1280x720 크기의 커버와 600x600 썸네일이 들어간 zip 파일이 판매됩니다. 단순히 파일을 넣는 것만으로는 안 됩니다. pack_product.py는 Pillow를 사용해 이 두 가지를 모두 렌더링하고, 파일들을 zip으로 압축하며, 모든 것을 _system 폴더에 작성합니다. 전체 키트에서 외부 패키지는 단 두 개입니다. 나머지는 모두 표준 라이브러리입니다.
2GB로 충분하게 만드는 RAM 관리 습관들
단일 코어가 사람들이 생각하는 제약 조건이 아닙니다. 메모리가 문제입니다. 이 습관들이 파이프라인을 작동하게 유지합니다:
- 한 번에 하나의 프로세스만 처리합니다. 1코어 장치에서 이미지 작업을 병렬로 분산(fan out)하지 마십시오. 코어가 하나일 때 병렬 처리는 아무것도 사주지 못하고 OOM kill(메모리 부족으로 인한 강제 종료)을 유발할 뿐입니다.
- 각 자산 처리 후
gc.collect()를 호출합니다. 파이썬은 메모리를 항상 자체 스케줄에 따라 반환하지 않으며, 2GB에서는 게으른 정리 작업 하나하나가 느껴집니다. - 평균 RSS(Resident Set Size)가 아닌 최대 RSS를 측정합니다.
resource.getrusage(RUSAGE_SELF).ru_maxrss는 실제 상한선을 알려줍니다. 평균 메모리는 다음 대용량 파일이 시스템을 마비시킬지 여부에 대해 아무것도 말해주지 않습니다. - 무거운 렌더링 단계는 각 서브프로세스에서 하나의 파일을 처리하도록 실행하여, 서브프로세스가 종료될 때 OS가 메모리를 회수하게 합니다.
- 필요한 해상도에서 렌더링합니다. 300dpi 대신 60dpi로 미리보기를 검사하는 것이 원활한 작동과 스왑 폭풍(swap-storm) 사이의 차이를 만듭니다.
저는 그 박스에서 전체 과정을 검증했습니다: 39개의 완성된 제품, 856 MB의 출력물, 그리고 상단에 에이전트 오케스트레이터가 있는 약 19,000줄 분량의 파이프라인으로 실행되었습니다. 이 키트에 포함된 패키징 레이어는 작고 지루하지만 하중을 지탱하는 중요한 부분입니다.
연구 폴더 영구 보존하기
실제 작업 시간이 소요되었던 구조적인 측면에서 한 가지 참고 사항이 있습니다. 확장 가능한 레이아웃은 간단합니다: 원본 출력물은 assets/, 제출 준비가 된 파일은 output/, 완성된 zip과 커버는 packs/, 도구(tool)는 scripts/, 메모와 스크랩 로그는 research/에 넣습니다. 저는 한때 정리하기 위해 연구 폴더를 삭제했다가, 나중에 후속 제품을 만드는 데 정확히 그 메모들이 필요했습니다. 보관하세요. 디스크 공간은 저렴하지만, 연구를 다시 하는 것은 그렇지 않습니다.
이 모든 것이 흥미롭지는 않습니다. 그것이 요점입니다. 아트 생성 기능에 관심이 쏠리지만, 메타데이터 헤더, 업로드 원장(upload ledger), PDF 렌더러, 그리고 커버 패커가 파일 폴더를 실제로 판매할 수 있는 39개의 제품으로 만들어주는 핵심 요소들입니다. 이 네 가지 스크립트가 제가 매주 실행하는 것이며, 이것이 2 GB짜리 박스로도 충분한 이유입니다.
아이디어 대신 정확한 스크립트를 원하신다면, 저는 이 작업에서 파생된 파이프라인 스타터 키트로 패키징했습니다. 그것들은 제가 판매하는 벡터 프롬프트 팩 옆에 놓여 있는데, 이것은 같은 파이프라인의 콘텐츠 측면입니다. 이 스크립트들은 제가 새벽 2시에 거절당한 CSV 파일들에 지쳐서 만들었고, 네 번째로 설명하기보다는 그냥 건네주는 것이 나았기 때문에 만든 도구입니다.
디지털 제품을 제작하고 계신가요? 디지털 제품 판매자를 위한 무료 가이드 및 키트.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기