썸네일 제작기 스킬 (Gemini 활용)
Claude Code 내에서 이미지 생성 에이전트 역할을 하여 기사 및 뉴스레터 썸네일 후보를 생성합니다. Gemini의 웹 UI를 오가며 하나씩 이미지를 생성하는 대신, 이 스킬은 Claude가 전체 과정을 수행하게 합니다: 원고 읽기, 구성 제안, 맞춤 프롬프트 작성, Gemini API 호출, 출력 평가, 간략한 근거와 함께 순위가 매겨진 결과 제시.
결과물은 CMS, 뉴스레터 도구 또는 소셜 스케줄러에 바로 적용할 수 있는 프로덕션 레디 썸네일 후보입니다.
사전 요구사항
스킬이 이미지를 생성하기 전에 다음 두 가지가 준비되어 있어야 합니다:
1. Gemini API 키
Google AI Studio에서 무료 키를 받으세요.
환경 변수로 설정:
export GEMINI_API_KEY=<your-key>
세션 간 유지하려면 셸 프로필 (~/.zshrc 또는 ~/.bashrc)에 추가:
echo 'export GEMINI_API_KEY=<your-key>' >> ~/.zshrc
source ~/.zshrc
설정 확인:
echo $GEMINI_API_KEY
2. generate_image.py 스크립트
이 스크립트는 프로젝트 루트의 ./generate_image.py에 있어야 합니다. 전체 템플릿은 아래 스크립트 템플릿 섹션에 제공됩니다. Claude는 이를 확인하고 없으면 생성하겠다고 제안합니다.
Python 종속성:
pip install google-generativeai Pillow requests
또는 uv 사용:
uv pip install google-generativeai Pillow requests
필수 입력
Claude는 제공되지 않은 경우 다음을 요청합니다:
| 입력 | 필수 | 참고 |
|---|---|---|
| 기사 원문 또는 URL | 예 | 전체 기사 텍스트를 붙여넣거나 가져올 URL을 제공합니다. 구성에 사용할 주제, 후크, 주요 주장을 추출하는 데 사용됩니다. |
| 브랜드 색상 | 권장 | Hex 코드 또는 설명적 이름. 예: #1A1A2E (남색), #E94560 (코랄). 제공되지 않으면 Claude는 깔끔한 중립 기본값을 사용합니다. |
| 폰트 / 서체 스타일 | 권장 | 예: "굵은 산세리프", "에디토리얼 세리프", "Neue Haas Grotesk". 프롬프트에서 텍스트 처리를 안내하는 데 사용됩니다. |
| 스타일 참조 설명 | 권장 | 예: "플랫 일러스트레이션, 미니멀, Stripe 마케팅 사이트 스타일" 또는 "포토리얼리스틱, 어두운 배경, 높은 대비". 스타일 이미지 URL도 제공 가능. |
| 출력 크기 | 아니요 | 기본값 1792x1024 (가로, 표준 기사 썸네일). 옵션: 1024x1024 (정사각형), 1024x1792 (세로/모바일). |
| 후보 수 | 아니요 | 기본값 4. 최소 1, 최대 8 (API 제한 및 비용 관련). |
| 기사 제목 (H1과 다른 경우) | 아니요 | 이미지 프롬프트의 주요 텍스트 요소로 사용됩니다. |
| 후보 선택 | 아니요 | 구성을 제안한 후 Claude는 어떤 것을 생성할지 묻습니다. 사용자는 "전부" 또는 번호로 선택할 수 있습니다. |
출력 구조
1단계 — 구성 제안 (텍스트, API 호출 전)
Claude는 사용자의 승인을 위해 3-4개의 구성 개념을 제시합니다. 형식:
"[기사 제목]"에 대한 구성 개념
1. 대담한 주장
레이아웃: 전체 면적 어두운 배경, 중앙에 큰 흰색 헤드라인,
브랜드 색상으로 아래에 단일 악센트 데이터 포인트 (예: "3배 빠름")
분위기: 높은 권위, 뉴스레터 스타일
최적 대상: LinkedIn, Substack 헤더
근거: 기사의 핵심 주장("X가 Y보다 3배 뛰어나다")은 시각적 앵커로 충분히 구체적입니다 — 독자는 데이터에서 멈춥니다.
2. 개념적 개체
레이아웃: 중앙 개체 일러스트레이션 (예: 시간 낭비 기사에 대한 고장난 시계),
위쪽 1/3에 제목, 미니멀 텍스처 배경
분위기: 에디토리얼, Medium 스타일
최적 대상: 블로그 헤더, Medium 커버, 이메일 프리헤더
근거: 아트 디렉터에게 시각적 은유의 유연성을 제공하며, 다양한 크기에서 작동합니다.
3. 대비 분할
레이아웃: 왼쪽 절반 브랜드 색상, 오른쪽 절반 흰색 또는 이미지,
색상 쪽에 제목, 흰색 쪽에 지원 부제목
분위기: 깔끔하고 전문적이며, 스타트업 브랜드 느낌
최적 대상: 뉴스레터, LinkedIn 캐러셀 첫 슬라이드
근거: 분할 레이아웃은 뉴스레터 A/B 테스트에서 일관된 성과를 보입니다. 작은 크기에서도 텍스트를 읽을 수 있습니다.
4. 타이포그래피 전용
레이아웃: 일러스트레이션 없음, 과장된 제목 처리,
하단에 작은 대문자로 된 저자 이름, 얇은 구분선
분위기: 프리미엄, 자신감, 에디토리얼
최적 대상: Substack, Ghost, 고밀도 이메일 목록
근거: 브랜드에 강한 타이포그래피 아이덴티티가 있을 때 효과적입니다. 가장 빠르게 제작 가능합니다.
어떤 구성을 생성할까요? (숫자로 답변, 예: "1, 3" 또는 "전부")
2단계 — 생성된 이미지 파일
생성 후 Claude는 파일을 ./thumbnails/[article-slug]/에 저장합니다:
thumbnails/
└── article-slug-from-title/
├── candidate_01_bold_claim.png
├── candidate_02_conceptual_object.png
├── candidate_03_contrast_split.png
├── candidate_04_typographic.png
└── evaluation_report.md
3단계 — 평가 요약 테이블
Claude는 컴퓨터 비전을 통해 반환된 각 이미지를 평가하고 다음을 생성합니다:
썸네일 평가 — "[기사 제목]"
생성일: 2026-05-27 | 모델: Gemini Imagen | 크기: 1792x1024
| # | 후보 | 구성 | 브랜드 적합도 /10 | 텍스트 가독성 /10 | 권장 |
|---|---|---|---|---|---|
| 1 | candidate_01_bold_claim.png | 대담한 주장 | 9 | 8 | ★ 최고 추천 — 강력한 데이터 앵커, 브랜드 색상 정확, 200px 너비에서 제목 읽기 가능 |
| 2 | candidate_02_conceptual_object.png | 개념적 개체 | 7 | 9 | 좋은 대안 — 가독성 좋고 깔끔하지만, 일러스트레이션 스타일이 브랜드에서 약간 벗어남 |
| 3 | candidate_03_contrast_split.png | 대비 분할 | 8 | 7 | 전체 크기에서는 잘 작동함; 게시 전에 썸네일 크기에서 테스트 필요 — 오른쪽 텍스트가 좁아짐 |
| 4 | candidate_04_typographic.png | 타이포그래피 | 9 | 10 | 이메일에 가장 강력함 — 브랜드 이탈 위험 제로, 완전 텍스트 기반 |
웹 추천: candidate_01_bold_claim.png
이메일/모바일 추천: candidate_04_typographic.png
소셜 추천: candidate_03_contrast_split.png
저장 위치: ./thumbnails/article-slug-from-title/
Claude가 이 스킬을 실행하는 방법
1단계 — 기사 수집 및 분석
기사 원문을 붙여넣은 텍스트나 URL로 받습니다.
URL이 제공되면 페이지를 가져와 다음을 추출:
- H1 제목
- 처음 3-5 단락 (후크, 핵심 주장, 주요 포인트)
- 언급된 주목할 만한 통계나 명명된 프레임워크
- 저자 이름 (타이포그래피 구성용)
텍스트가 붙여넣어지면 직접 읽습니다. 다음에 집중:
- 후크: 여는 주장이나 긴장감은 무엇인가?
- 핵심 테제: 기사가 주장하거나 가르치는 한 가지는 무엇인가?
- 핵심 세부사항: 시각적 앵커가 될 수 있는 숫자, 명명된 프레임워크, 또는 구체적인 예
- 어조: 공식적/권위적인가, 대화체/접근하기 쉬운가, 도발적/도전적인가?
구성을 제안하기 전에 내부적으로 이러한 발견을 요약하세요 — 제안은 이 특정 기사에 맞춰져야 하며 일반적이지 않아야 합니다.
2단계 — 브랜드 사양 수집
제공되지 않은 경우 사용자에게 브랜드 사양을 요청:
브랜드에 맞는 썸네일을 생성하려면 몇 가지 세부 정보가 필요합니다:
1. 브랜드 색상 (hex 코드 또는 설명) — 예: #1A1A2E, #E94560
2. 폰트 스타일 선호도 — 예: "굵은 산세리프", "에디토리얼 세리프", "기하학적"
3. 시각적 스타일 — 예: "플랫 미니멀", "포토리얼리스틱", "일러스트레이션", "타이포그래피 전용"
4. 스타일 참조 — 일치시키고 싶은 미학을 가진 브랜드나 출판물을 설명하거나, 이미지 URL 공유
아직 브랜드 사양이 없으면 "깔끔한 기본값 사용"이라고 말씀해 주세요. 전문적인 다크온화이트 에디토리얼 스타일을 적용하겠습니다.
사용자가 "깔끔한 기본값 사용"이라고 말하면 다음을 적용:
- 배경:
#FFFFFF또는#0F0F0F(다크 모드 기본값) - 악센트:
#2563EB(파란색) - 폰트 스타일: 굵은 기하학적 산세리프
- 스타일: 미니멀 플랫, 텍스처 없음, 높은 대비
3단계 — 구성 개념 제안
기사의 어조와 내용에 맞춰 3-4개의 구성 개념을 작성합니다. 각 개념은 다음을 포함해야 함:
- 이름 (짧고 기억하기 쉬운 레이블)
- 레이아웃을 정확히 설명 (제목 위치, 시각적 요소를 고정하는 것, 배경 처리)
- 분위기와 가장 적합한 사용 사례 명시
- 이 구성이 이 특정 기사에 적합한 이유를 설명하는 근거 문장 포함
개념을 제시한 후 어떤 것을 생성할지 물어보세요. API 호출 전에 사용자 확인을 기다리세요.
4단계 — Gemini 이미지 생성 프롬프트 작성
선택된 각 구성에 대해 상세한 이미지 생성 프롬프트를 작성합니다. 이미지 생성 프롬프트는 텍스트 프롬프트와 다른 문법을 따릅니다 — 설명적이며 지시적이지 않습니다.
프롬프트 구조:
[주제/구성] + [스타일] + [색상 팔레트] + [분위기/조명] +
[텍스트 처리(있는 경우)] + [피해야 할 것]
대담한 주장 구성 예시 프롬프트:
기사 썸네일 이미지. 진한 남색 배경(#1A1A2E) 중앙에 "기존 방식보다 3배 빠른"이라는
큰 굵은 흰색 산세리프 헤드라인 텍스트. 그 아래 작은 코랄 악센트 텍스트(#E94560)로
부제목 표시. 미니멀 플랫 디자인, 그라디언트 없음, 스톡 포토 요소 없음, 사람 없음.
깔끔한 전문 에디토리얼 스타일, 높은 대비, 뉴스레터 헤더 형식, 16:9 가로 방향.
구성은 타이포그래피 중심 — 텍스트가 주인공, 일러스트레이션 불필요.
피해야 할 것: 클립 아트, 드롭 섀도우, 낮은 대비, 혼잡한 레이아웃.
프롬프트 규칙:
- 브랜드 색상이 제공된 경우 정확한 hex 색상 포함
- 이미지에 표시될 정확한 헤드라인 텍스트 지정
- 스타일을 명시적으로 이름 지정 ("플랫 디자인", "에디토리얼", "포토리얼리스틱") — Gemini는 스타일 카테고리 이름에 잘 반응
- 부정 프롬프트 ("피해야 할 것: ...")를 끝에 추가하여 브랜드 스타일 이탈 감소
- 프롬프트를 300단어 미만으로 유지 — 더 긴 프롬프트가 더 나은 출력을 보장하지 않음
5단계 — 사전 요구사항 확인 및 생성 스크립트 실행
API 호출 전에 다음을 확인:
# API 키 설정 확인
echo $GEMINI_API_KEY
# 스크립트 존재 확인
ls -la ./generate_image.py
# 종속성 확인
python3 -c "import google.generativeai, PIL, requests; print('Dependencies OK')"
스크립트가 없으면 아래 스크립트 템플릿 섹션의 템플릿을 사용하여 생성하겠다고 제안합니다.
각 프롬프트에 대해 생성 스크립트 실행:
python3 generate_image.py \
--prompt "your full prompt here" \
--output "./thumbnails/article-slug/candidate_01_bold_claim.png" \
--width 1792 \
--height 1024
또는 모든 프롬프트를 일괄 설정 파일로 전달:
python3 generate_image.py --config ./thumbnails/article-slug/prompts.json
6단계 — 생성된 이미지 평가
각 이미지가 저장된 후 컴퓨터 비전을 사용하여 검사합니다. 두 가지 차원에서 평가:
브랜드 적합도 (점수 /10):
- 브랜드 색상이 정확한가? (각 1-2점)
- 스타일이 요청된 미학과 일치하는가? (2점)
- 레이아웃이 구성 브리프와 일관된가? (2점)
- AI 아티팩트, 왜곡된 텍스트, 의도하지 않은 요소가 있는가? (문제당 -1점)
텍스트 가독성 (점수 /10):
- 헤드라인 텍스트가 전체 해상도에서 읽을 수 있는가? (3점)
- 헤드라인 텍스트가 이미지를 300px 너비(썸네일 크기)로 축소했을 때 읽을 수 있는가? (3점)
- 텍스트와 배경 사이에 충분한 대비가 있는가? (2점)
- 텍스트 배치가 안전 영역 내에 있는가 (가장자리에서 잘리지 않음)? (2점)
참고: Gemini Imagen은 때때로 철자 오류나 왜곡된 글자 형태로 텍스트를 렌더링합니다. 이런 경우 평가에 기록하고 사용자에게 Canva나 Figma에서 수동으로 텍스트 오버레이를 추가하도록 제안하세요.
7단계 — 평가 보고서 생성
평가 요약 테이블 (출력 구조 섹션에 표시된 형식)을 작성하고 출력 폴더에 evaluation_report.md로 저장합니다.
포함:
- 각 점수에 대한 한 줄 근거
- 사용 사례별 최고 추천 (웹, 이메일/모바일, 소셜)
- 모든 제작 참고 사항 (예: "candidate_02의 텍스트 렌더링이 불완전 — 수동으로 텍스트 오버레이")
- 사용된 전체 프롬프트 (사용자가 필요시 AI Studio에서 직접 반복할 수 있도록)
8단계 — 반복 작업 제안
후보를 전달한 후 한 번의 반복 패스를 제안:
이 중에서 수정할 부분이 있나요?
옵션:
- 특정 후보의 색상 또는 스타일 조정
- 다른 구성 개념 시도
- 헤드라인 텍스트 변경
- 다른 Gemini 매개변수(다른 온도/시드)로 재실행
- 최고 추천의 추가 변형 생성
변경할 내용을 알려주세요.
스크립트 템플릿
Claude는 generate_image.py가 없으면 이 파일을 작성하겠다고 제안해야 합니다. 다음은 사용할 정식 템플릿입니다.
#!/usr/bin/env python3
"""
generate_image.py — 썸네일 제작기 스킬용 Gemini Imagen 래퍼.
사용법:
python3 generate_image.py --prompt "..." --output "./out.png" [--width 1792] [--height 1024]
python3 generate_image.py --config ./prompts.json
설정 JSON 형식:
[
{
"prompt": "...",
"output": "./thumbnails/slug/candidate_01.png",
"width": 1792,
"height": 1024
}
]
요구사항:
pip install google-generativeai Pillow
"""
import os
import sys
import json
import argparse
import base64
from pathlib import Path
try:
import google.generativeai as genai
from google.generativeai import types as genai_types
except ImportError:
print("ERROR: google-generativeai가 설치되지 않았습니다. 실행: pip install google-generativeai")
sys.exit(1)
try:
from PIL import Image
import io
except ImportError:
print("ERROR: Pillow가 설치되지 않았습니다. 실행: pip install Pillow")
sys.exit(1)
def get_api_key() -> str:
key = os.environ.get("GEMINI_API_KEY", "")
if not key:
print("ERROR: GEMINI_API_KEY 환경 변수가 설정되지 않았습니다.")
print("키 받기: https://aistudio.google.com/app/apikey")
print("그런 다음 실행: export GEMINI_API_KEY=<your-key>")
sys.exit(1)
return key
def generate_image(
prompt: str,
output_path: str,
width: int = 1792,
height: int = 1024,
) -> bool:
"""
Gemini Imagen을 호출하여 단일 이미지를 생성하고 output_path에 저장합니다.
성공 시 True를 반환합니다.
"""
api_key = get_api_key()
genai.configure(api_key=api_key)
try:
model = genai.GenerativeModel("imagen-3.0-generate-002")
response = model.generate_content(
prompt,
generation_config=genai_types.GenerationConfig(
candidate_count=1,
temperature=1.0,
),
)
if not response.candidates:
print(f"ERROR: '{output_path}'에 대한 응답에 후보가 없습니다.")
return False
image_data = response.candidates[0].content.parts[0].inline_data.data
image_bytes = base64.b64decode(image_data)
image = Image.open(io.BytesIO(image_bytes))
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
image.save(output_path, "PNG")
print(f"저장됨: {output_path} ({image.size[0]}x{image.size[1]})")
return True
except Exception as e:
print(f"ERROR: '{output_path}' 생성 중 오류 발생: {e}")
return False
def generate_batch(config_path: str) -> None:
"""일괄 설정 파일에서 여러 이미지를 생성합니다."""
with open(config_path, "r") as f:
configs = json.load(f)
success_count = 0
for idx, cfg in enumerate(configs):
prompt = cfg.get("prompt", "")
output = cfg.get("output", f"./output_{idx}.png")
width = cfg.get("width", 1792)
height = cfg.get("height", 1024)
if not prompt:
print(f"SKIP: 설정 {idx}에 프롬프트가 없습니다.")
continue
ok = generate_image(prompt, output, width, height)
if ok:
success_count += 1
print(f"완료: {success_count}/{len(configs)} 이미지 생성 성공")
def main():
parser = argparse.ArgumentParser(description="Gemini Imagen으로 이미지 생성")
parser.add_argument(
"--prompt", type=str, default="",
help="이미지 생성을 위한 텍스트 프롬프트"
)
parser.add_argument(
"--output", type=str, default="./output.png",
help="출력 이미지 파일 경로 (기본값: ./output.png)"
)
parser.add_argument(
"--width", type=int, default=1792,
help="이미지 너비 (기본값: 1792)"
)
parser.add_argument(
"--height", type=int, default=1024,
help="이미지 높이 (기본값: 1024)"
)
parser.add_argument(
"--config", type=str, default="",
help="일괄 생성 설정 JSON 파일 경로"
)
args = parser.parse_args()
if args.config:
generate_batch(args.config)
elif args.prompt:
generate_image(args.prompt, args.output, args.width, args.height)
else:
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()