요금제+7% 보너스

ChatGPT에서 한 번에 여러 이미지 생성하기

Python, CSV 파일, 안전한 동시성, 재시도, Base64 디코딩, 비용 제어를 사용해 GPT Image 2 API로 제품 이미지를 대량 생성하는 방법을 알아보세요.

ChatGPT에서 한 번에 여러 이미지 생성하기

100개의 제품이 포함된 스프레드시트가 있다고 상상해 보세요. 각 행에는 제품 이름, 색상, 재질, 선호하는 카메라 앵글이 있습니다. 이러한 세부 정보를 ChatGPT에 100번 복사하는 대신, Python 스크립트가 스프레드시트를 읽고 각 행마다 제품 이미지 하나를 요청하도록 할 수 있습니다.

이 가이드에서 “대량 제품 이미지 생성”이란 바로 이런 뜻입니다. 이는 하나의 이미지에 대한 열 가지 변형을 요청하는 것과 다릅니다. 모든 제품은 자체 프롬프트, 작업 ID, 출력 파일, 상태 기록을 받습니다.

이 워크플로는 GPTProto의 GPT Image 2 API를 사용해 Python으로 구축합니다. 이전에 이미지 API 경험이 없어도 됩니다. 최종 스크립트는 CSV 카탈로그를 읽고, 이미지 크기를 확인하고, 한 번에 몇 개의 제품을 처리하고, 결과를 다운로드하며, 완료된 작업을 기억해 다시 시작해도 같은 제품을 다시 생성하지 않습니다.

요약하면 다음과 같습니다:

  1. 각 고유 제품 프롬프트마다 n=1을(를) 사용하세요.

  2. 작은 워커 풀을 통해 여러 개의 서로 다른 SKU 작업을 제출하세요.

  3. 이미지를 기다리기 전에 반환된 prediction ID를 저장하세요.

  4. 작업이 완료되는 즉시 임시 출력 URL을 다운로드하세요.

  5. 성공한 제품을 다시 제출하지 말고 manifest에서 이어서 진행하세요.

“prediction ID,” “polling,” “manifest”와 같은 용어가 낯설어도 걱정하지 마세요. 전체 스크립트가 나오기 전에 아래에서 설명합니다.

목차

What You Will Have at the End

You will start with two files:

  • products.csv: a spreadsheet-like list of the products you want to process.

  • bulk_product_images.py: the Python script from this guide.

When the script runs, it creates two more items automatically:

  • generated/: a folder containing files such as MUG-001-01.png.

  • manifest.json: a record showing which products completed, failed, or still need checking.

The flow is straightforward:

products.csv → Python script → GPT Image 2 API → downloaded images
                         ↘ manifest.json

Begin with the three sample products in this guide. Once they work, replace the sample rows with your real catalog.

Beginner Terms Used in This Guide

Term Plain-English meaning
API A way for your script to send instructions to GPT Image 2 without using a chat window.
API key A private password that identifies your GPT Proto account and authorizes API usage.
SKU Your own unique code for one product, such as MUG-001. It is also used in the output filename.
Request One message sent by the script to the API. In this workflow, one product normally creates one generation request.
Task or prediction ID The receipt number returned after a generation request is accepted. The script uses it to check the result.
Polling Asking the result endpoint every few seconds whether a task has finished.
Concurrency The number of products being processed at the same time. Three workers means up to three active jobs.
Manifest A local record of every SKU, task ID, result, and error. It allows a stopped script to continue safely.
Base64 Image data represented as a long text string. It must be decoded before it becomes a normal image file.

The API workflow is similar to collecting an order from a restaurant. The first request places the order. The returned task ID is the collection number. Polling checks whether the order is ready. The output URL is where the finished item can be collected. The manifest is your receipt book.

Can ChatGPT Generate Multiple Product Images at Once?

ChatGPT can create multiple images across a conversation, so it is fine for visual exploration and a small number of manual variations. It is not a dependable catalog pipeline: there is no CSV queue, stable SKU-to-file mapping, concurrency control, or retry manifest.

The API is the better fit when the question is “How do I create multiple product images and know which file belongs to which product?”

Need ChatGPT interface GPT Image 2 API workflow
Explore one concept Good fit Possible, but more setup
Process a CSV catalog Manual Automated
Use a different prompt per SKU Repetitive One row becomes one request
Control filenames and folders Manual download Deterministic naming
Resume failed work Manual review Manifest-based
Integrate with a DAM, PIM, or store Limited Designed for code integration

Searches such as “ChatGPT generate multiple images at once” often mix two different goals: several variations of one prompt and many images for different products. The distinction matters once cost, file ownership, and reruns enter the picture.

What “GPT Image 2 API Batch” Can Mean

People use “batch” for three separate patterns:

1. Several variations from one prompt

Set n between 1 and 10. This is useful when a designer wants several candidates for the same product and composition. It does not create product-specific prompts for ten SKUs.

2. Several independent requests at the same time

Read multiple products from a file and submit one request per row. A small ThreadPoolExecutor provides controlled concurrency. This is the pattern used in this tutorial because each product can have its own name, color, material, angle, and background.

3. OpenAI's asynchronous Batch API

OpenAI's direct Batch API accepts JSONL request files, uses a separate rate pool, offers a 50% discount, and completes within a 24-hour window. The OpenAI Batch API guide lists /v1/images/generations and /v1/images/edits as supported endpoints.

That is a separate OpenAI feature. Do not assume the same JSONL Batch endpoint exists on GPT Proto. The GPT Proto workflow below uses its documented image endpoint and prediction-status route.

What You Need

You need a computer, Python 3, a GPT Proto API key, and a text editor. Visual Studio Code is convenient, but any editor that saves plain-text files will work.

1. Check that Python is installed

Open Terminal on macOS or Linux, or PowerShell on Windows, and run:

python --version

If Windows says that python is not recognized, try:

py --version

A result such as Python 3.11.8 means Python is ready. If neither command works, install a current Python 3 release and enable the option that adds Python to your system path.

2. Create a project folder

Create a folder named gpt-image-bulk. Inside it, create these two empty files:

  • bulk_product_images.py

  • products.csv

Paste the complete Python script from this guide into the first file. Paste the sample CSV shown below into the second file. Both files must be in the same folder unless you change the paths in the script.

3. Create and store your API key

Create an account on GPT Proto, add credit, and generate an API key. An API key should be treated like a password. Do not publish it in an article, upload it to GitHub, or paste it directly into the Python file.

Store the key temporarily in an environment variable. On macOS or Linux, run:

export GPTPROTO_API_KEY="your-key-here"

On Windows PowerShell, run:

$env:GPTPROTO_API_KEY="your-key-here"

The variable normally lasts for the current terminal window. If you close that window and open another one, set it again before running the script.

4. Install the one required Python package

The script uses requests to communicate with the API and download images. Install it with:

python -m pip install requests

Windows users who needed the py command should run py -m pip install requests instead.

5. CSV에 제품 추가하기

CSV는 “쉼표로 구분된 값”을 의미합니다. Excel, Google Sheets 및 대부분의 카탈로그 도구에서 내보낼 수 있는 간단한 스프레드시트 형식입니다. 행당 제품 하나를 추가하세요:

제품당 한 행으로 products.csv를 만드세요:

sku,name,color,material,angle,background,avoid
MUG-001,쌓을 수 있는 커피 머그,무광 네이비,스톤웨어,3/4 뷰,부드러운 따뜻한 회색,증기나 손
BAG-014,컴팩트 크로스바디 백,포레스트 그린,페블드 레더,정면 3/4,밝은 베이지,소품이나 모델
LAMP-207,휴대용 테이블 램프,크림,분체 도장 알루미늄,정면,깨끗한 흰색,눈에 띄는 케이블이나 추가 물체

각 열에는 하나의 역할이 있습니다:

열 입력할 내용 예
sku 고유한 제품 코드 MUG-001
name 간단한 제품 이름 쌓을 수 있는 커피 머그
color 정확한 눈에 보이는 색상 또는 마감 무광 네이비
material 주요 눈에 보이는 소재 스톤웨어
angle 원하는 카메라 뷰 3/4 뷰
background 카탈로그 배경 부드러운 따뜻한 회색
avoid 이미지에 포함되어서는 안 되는 물체나 실수 증기나 손

Python 스크립트가 해당 열 이름을 찾기 때문에 헤더 행을 표시된 그대로 유지하세요. 모든 SKU는 고유해야 합니다. 제품 이름이나 필드에 쉼표가 포함된 경우 올바르게 인용되도록 스프레드시트 프로그램에서 파일을 내보내세요.

각 행마다 다른 긴 프롬프트를 작성하기보다 짧고 사실적인 필드를 사용하세요. 스크립트가 동일한 사진 규칙으로 필드를 결합하므로 카탈로그가 일관되게 보이는 데 도움이 됩니다.

1단계: 재사용 가능한 제품 프롬프트 만들기

제품 프롬프트는 SKU별로 변경되는 사실과 전체 카탈로그에 적용되는 규칙을 분리해야 합니다. 이렇게 하면 프롬프트 수정을 측정할 수 있습니다: 공유 템플릿을 한 번 변경하고, 작은 테스트 세트를 다시 생성한 다음, 이전 버전과 비교하세요.

def build_prompt(row):
    return f"""카탈로그 제품 사진 한 장을 생성하세요.
제품: {row['name']}
색상: {row['color']}
소재: {row['material']}
뷰: {row['angle']}
배경: {row['background']}

제품이 사실적인 비율과 소재 질감을 유지하며 중앙에 오도록 하세요.
부드러운 스튜디오 조명, 자연스러운 접촉 그림자, 여유 있는 크롭 안전 공간을 사용하세요.
텍스트, 로고, 라벨, 사람, 추가 물체는 없습니다.
또한 피하세요: {row.get('avoid', 'none')}.
콜라주나 콘택트 시트가 아닌 단일 제품 이미지를 반환하세요."""

모든 제품에 대해 이 함수를 편집할 필요는 없습니다. 스크립트가 머그 행을 읽으면 {row['name']}는 쌓을 수 있는 커피 머그가 됩니다; 가방 행을 읽으면 동일한 자리표시자가 컴팩트 크로스바디 백가 됩니다. 이것을 템플릿이라고 합니다: 제품 데이터가 변경되는 동안 구조는 동일하게 유지됩니다.

“단일 제품 이미지” 지침은 모델이 요청을 콜라주로 바꾸는 것을 방지하는 데 도움이 됩니다. 카메라 뷰, 크롭, 조명, 그림자 및 배경에 대한 공유 지침은 개별 제품이 관련 없는 이미지가 아니라 하나의 카탈로그처럼 보이도록 도와줍니다.

초기 결과가 일관되지 않으면 먼저 공유 규칙을 편집하세요. 예를 들어 “여유 있는 크롭 안전 공간”을 “제품이 정사각형 캔버스의 약 75%를 채워야 합니다.”로 바꾸세요. 전체 파일에 적용하기 전에 세 가지 다른 제품 유형에서 해당 변경을 테스트하세요.

실제 브랜드 제품의 경우 텍스트 투 이미지는 컨셉 샷이나 일반 상품에 가장 적합합니다. 정확한 형상, 포장 또는 라벨 배치가 중요하다면 승인된 제품 참조를 사용한 이미지 편집을 사용하세요; 해당 워크플로는 나중에 다룹니다.

2단계: 대기열을 시작하기 전에 제품 하나 테스트하기

제품 하나로 시작하세요, n=1, quality="low", 그리고 1024x1024 같은 일반적인 크기를 사용합니다. 낮은 품질은 최종 품질로 생성하는 비용을 지불하기 전에 구도를 확인하는 데 유용합니다.

다음 cURL 명령은 작은 API 테스트입니다. cURL은 웹 요청을 보내는 명령줄 도구입니다. 이 명령은 Bash 문법을 사용하므로 API 키를 설정한 후 macOS 또는 Linux의 Terminal에 붙여넣으세요. Windows 초보자는 수동 cURL 테스트를 건너뛰고 이 가이드 뒤에 나오는 크로스 플랫폼 Python 스크립트를 사용해도 됩니다.

curl --request POST \
  --url "https://gptproto.com/api/v3/openai/gpt-image-2/text-to-image" \
  --header "Authorization: Bearer $GPTPROTO_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "prompt": "A centered matte navy stoneware coffee mug, three-quarter catalog view, soft studio light, warm gray background, natural contact shadow, no text or props",
    "n": 1,
    "quality": "low",
    "size": "1024x1024",
    "enable_sync_mode": false,
    "response_format": "url"
  }'

중요한 줄의 의미는 다음과 같습니다:

필드 의미
Authorization 요청을 보내는 계정을 GPT Proto가 알 수 있도록 개인 API 키를 전송합니다.
prompt 원하는 제품 이미지를 설명합니다.
n: 1 이 프롬프트에 대해 결과 하나를 요청합니다.
quality: low 첫 테스트에는 비용이 더 낮은 초안 설정을 사용합니다.
size: 1024x1024 정사각형 이미지를 요청합니다.
enable_sync_mode: false 생성이 끝날 때까지 요청을 열어 두는 대신 작업 영수증을 먼저 반환합니다.
response_format: url 이미지가 준비되면 다운로드 링크를 반환합니다.

성공적인 첫 응답은 data.id에 예측 ID가 포함된 JSON 객체입니다. JSON은 API에서 사용하는 구조화된 텍스트 형식일 뿐입니다. 다음과 같은 축약된 예시처럼 보일 수 있습니다:

{
  "data": {
    "id": "abc123",
    "status": "created",
    "urls": {
      "get": "https://gptproto.com/api/v3/predictions/abc123/result"
    }
  }
}

이 응답은 이미지가 아닙니다. 작업이 접수되었음을 확인하는 응답입니다. 반환된 실제 ID를 복사한 다음 그 결과를 조회하세요:

curl --request GET \
  --url "https://gptproto.com/api/v3/predictions/abc123/result" \
  --header "Authorization: Bearer $GPTPROTO_API_KEY"

다음 값을 바꾸세요: abc123 을(를) 작업 ID로. 만약 data.status가 아직 created 또는 processing이면 몇 초 기다린 후 GET 명령을 다시 실행하세요. completed가 되면 data.outputs에 생성된 출력이 포함됩니다. 전체 Python 스크립트가 이러한 검사를 자동으로 수행하므로 모든 제품에 대해 이 수동 과정을 반복할 필요가 없습니다.

현재 요청 및 응답 필드는 GPT Proto GPT Image 2 텍스트-이미지 참조를 참조하세요.

제품 하나가 다음 검사를 통과할 때까지 전체 카탈로그를 시작하지 마세요:

  • 잘린 이미지가 모든 스토어프런트 배치에 충분한 여백을 남깁니다.

  • 색상과 재질이 의도된 용도에 충분히 가깝습니다.

  • 배경과 접촉 그림자가 카탈로그 스타일과 일치합니다.

  • 임의로 만들어진 로고, 라벨, 소품 또는 중복 제품이 나타나지 않습니다.

  • 결과 경로와 다운로드 코드가 모두 사용자 환경에서 작동합니다.

테스트가 401 Unauthorized를 반환하면 가장 먼저 API 키와 Authorization 헤더를 확인하세요. 403을 반환하면 모델 액세스와 계정 잔액을 확인하세요. 이 항목 하나에 대한 테스트가 성공할 때까지 전체 카탈로그로 계속 진행하지 마세요.

3단계: “크기는 16의 배수여야 합니다” 오류 방지

GPT Image 2는 일반적인 프리셋과 유효한 사용자 지정 치수를 허용합니다. 사용자 지정 크기의 경우 양쪽 변이 16의 배수여야 하고, 가장 긴 변은 3,840픽셀을 초과할 수 없으며, 종횡비는 3:1을 초과할 수 없고, 이미지는 655,360~8,294,400픽셀을 포함해야 합니다. 총 해상도가 2560×1440을 초과하는 출력은 API 문서에서 실험적 기능으로 표시됩니다.

“양쪽 변이 16의 배수여야 합니다”는 너비와 높이가 각각 나머지 없이 16으로 나누어져야 한다는 뜻입니다. 1024, 1200, 1536, 2048 같은 숫자는 이 검사를 통과합니다. 1000 같은 숫자는 통과하지 못합니다. 이 규칙은 가장 긴 변뿐만 아니라 너비와 높이 모두에 적용됩니다.

유료 요청을 보내기 전에 검증하세요:

def validate_size(size):
    if size == "auto":
        return

    width, height = map(int, size.lower().split("x"))
    pixels = width * height

    if width % 16 or height % 16:
        raise ValueError("GPT Image 2 width and height must be multiples of 16")
    if max(width, height) > 3840:
        raise ValueError("The longest edge cannot exceed 3840 pixels")
    if max(width, height) / min(width, height) > 3:
        raise ValueError("Aspect ratio cannot exceed 3:1")
    if not 655_360 <= pixels <= 8_294_400:
        raise ValueError("Total pixels are outside the supported range")

1200x1200은 양쪽 변이 16으로 나누어지므로 유효합니다. 1000x1000은 유효하지 않습니다. 스토어프런트에서 최종적으로 1000x1000이 필요하다면 1024x1024로 생성한 후 승인 후 크기를 조정하세요.

지원되는 프리셋에는 1024x1024, 1536x1024, 1024x1536, 2048x2048, 2048x1152, 3840x2160, 그리고 2160x3840이(가) 포함됩니다.

4단계: 무한 루프가 아닌 제어된 동시성을 사용하세요

동시성은 스크립트가 동시에 처리하는 제품 수를 의미합니다. 작업자를 계산대라고 생각해 보세요. 작업자 하나는 활성 제품 하나를 처리하며, 작업자 세 명이면 최대 세 제품이 시스템을 함께 통과할 수 있습니다.

수백 개의 작업자를 시작하는 루프는 속도 제한에 부딪히고, 메모리를 소모하며, 실패 복구를 어렵게 만들 수 있습니다. 두세 개로 시작하세요. 계정 동작과 완료 시간을 확인한 뒤에만 늘리세요.

from concurrent.futures import ThreadPoolExecutor, as_completed

with ThreadPoolExecutor(max_workers=3) as pool:
    futures = [pool.submit(process_product, row) for row in products]
    for future in as_completed(futures):
        print(future.result())

이 예제를 사용하기 위해 Python 스레딩을 이해할 필요는 없습니다. max_workers=3가 중요한 설정입니다. 만약 반복해서 429 오류가 발생하면 2 또는 1로 줄이세요. 긴 테스트가 속도 제한 오류 없이 실행된다면 더 큰 값을 신중하게 시도해 볼 수 있습니다.

모델 제공자가 공개한 속도 제한이 중개 API 계정의 한도와 자동으로 같지는 않습니다. 활성 GPT Proto 키의 기준으로 429 Too Many Requests 및 해당 Retry-After 헤더를 신뢰하세요. 코드에 공격적인 숫자를 하드코딩하지 말고 동시성을 구성 가능하게 유지하세요.

OpenAI는 직접 모델 액세스에 대한 티어 기반 한도도 공개합니다. 해당 수치는 유용한 참고 자료이지만 GPT Proto의 속도 제한으로 제시되어서는 안 됩니다.

5단계: URL 및 Base64 결과를 올바르게 저장하세요

GPT Proto는 response_format="url" 및 response_format="b64_json"를 이 엔드포인트에 대해 지원합니다.

초보자는 먼저 url을 사용해야 합니다. API는 일반 이미지 링크를 반환하며, 스크립트는 파일을 generated 폴더에 다운로드합니다. 결과 URL은 영구 자산 저장소가 아니라 전달 링크이므로 즉시 다운로드하세요. 승인된 파일은 자체 제품 미디어 폴더, DAM 또는 객체 스토리지에 저장하세요.

Base64는 동일한 이미지를 전달하는 또 다른 방법입니다. 응답에는 URL 대신 매우 긴 텍스트 문자열이 포함됩니다. 시스템이 모든 데이터를 JSON 안에 유지해야 할 때 유용하지만, 파일을 열기 전에 텍스트를 다시 바이너리 이미지 바이트로 변환해야 합니다. 이 워크플로의 첫 버전에서는 Base64가 필요하지 않습니다.

Base64 출력의 경우 문자열을 바이트로 디코딩하세요:

import base64
from pathlib import Path

def save_base64_image(value, destination):
    # Also accepts a data URI such as data:image/png;base64,...
    encoded = value.split(",", 1)[1] if value.startswith("data:") else value
    Path(destination).write_bytes(base64.b64decode(encoded, validate=True))

흔한 GPT Image 2 Base64 디코딩 버그는 인코딩된 텍스트를 .png 파일에 직접 쓰는 것입니다. 유효한 이미지 파일에는 API가 반환한 JSON 문자열이 아니라 디코딩된 바이너리 바이트가 들어 있습니다.

GPT Proto 대신 OpenAI를 직접 사용한다면, GPT 이미지 모델은 b64_json을 반환한다는 점에 유의하세요. OpenAI의 이미지 생성 가이드에 따르면 GPT 이미지 모델에서는 URL 응답 형식이 지원되지 않습니다. 공급자별 차이 때문에 응답 파싱은 실제로 호출하는 엔드포인트와 일치해야 합니다.

6단계: 중복 이미지를 만들지 않고 재시도하세요

API 요청은 계정이 작업을 너무 빠르게 보내거나, 네트워크가 끊기거나, 생성 자체가 거부되어 실패할 수 있습니다. 실패한 모든 명령을 반복하고 싶을 수 있지만, 그러면 중복 이미지가 생성될 수 있습니다.

재시도가 모든 요청에 똑같이 안전한 것은 아닙니다. POST는 새 작업을 생성하고, GET는 기존 작업만 확인합니다.

  • GET 상태 쿼리는 다른 이미지를 생성하지 않으므로 재시도해도 안전합니다.

  • POST가 확실한 429 거부를 받으면 Retry-After를 기다린 후 다시 시도할 수 있습니다.

  • POST 연결 시간 초과는 모호합니다. 클라이언트가 응답을 놓쳤더라도 서버가 작업을 수락했을 수 있습니다. 무작정 반복하면 중복이 생성되고 비용이 청구될 수 있습니다.

  • 완료되었거나 실패했거나 검열 차단된 작업은 자동으로 다시 제출해서는 안 됩니다.

아래 스크립트는 제출 직후 작업 ID를 manifest.json에 기록합니다. 다음 실행에서는 동일한 SKU를 다시 게시하지 않고 기존 작업을 폴링합니다. 제출이 알 수 없는 네트워크 상태로 끝나고 작업 ID가 없으면 레코드가 manual_check로 표시됩니다. 다시 시도하기 전에 계정 기록을 확인하세요.

안전한 재시도를 위해 지터를 포함한 지수 백오프를 사용하세요. API가 Retry-After를 보내면 이를 준수하고, 지연 시간과 시도 횟수에 상한을 두세요. 반복된 실패는 무한 루프가 아니라 매니페스트에 기록되어야 합니다.

7단계: image_generation_user_error 처리

image_generation_user_error는 완전한 진단이 아니라 범주입니다. 입력이나 요청과 관련된 문제 때문에 요청을 생성할 수 없었다는 뜻입니다. 무엇을 변경할지 결정하기 전에 내부 오류 코드와 메시지를 읽으세요.

일반적인 조치는 다음과 같습니다:

오류 신호 가능한 조치
moderation_blocked 또는 정책 세부 정보 프롬프트와 참조 이미지가 있다면 검토하세요. 변경하지 않고 재시도하지 마세요
잘못된 크기 로컬 검증을 실행하고 치수를 수정하세요
잘못된 매개변수 또는 지원되지 않는 형식 페이로드를 현재 엔드포인트 참조와 비교하세요
인증 실패(401) 키 값과 권한 부여 헤더를 확인하세요
권한 또는 잔액 실패(403) 모델 액세스와 잔액을 확인하세요. 자동 백오프를 사용하지 마세요
속도 제한(429) Retry-After를 준수하고, 동시성을 낮추고, 상한을 두고 재시도하세요
서버 오류(500) 요청을 기록하고 중복 위험을 이해한 경우에만 재시도하세요

요청이 통과될 때까지 단어를 삭제하여 검열 오류를 “고치려” 하지 마세요. 제품, 의도된 사용, 참조 자산 및 프롬프트가 공급자의 정책을 준수하는지 확인하세요.

초보자라면 작업이 실패할 때 이 순서를 사용하세요:

  1. SKU를 manifest.json에서 찾으세요.

  2. 제품을 즉시 다시 제출하지 말고 해당 error 필드를 읽으세요.

  3. 메시지에 크기나 매개변수가 명시되어 있으면 해당 값을 수정하세요.

  4. 만약 이것이 429이면, IMAGE_WORKERS를 낮추고 계속하기 전에 기다리세요.

  5. 정책 또는 검열 오류라면 제품과 프롬프트를 수동으로 검토하세요.

  6. 상태가 manual_check이면 레코드를 삭제하기 전에 작업 기록을 확인하세요. 원래 요청이 이미 존재할 수 있습니다.

8단계: 팀 친화적인 생성 매니페스트 유지하기

매니페스트는 구성해야 하는 API 기능이 아닙니다. 스크립트가 컴퓨터에 생성하는 JSON 파일입니다. 생성 로그라고 생각하세요.

팀이나 비즈니스의 경우 이미지 폴더만으로는 충분하지 않습니다. 매니페스트는 각 에셋을 SKU, 프롬프트, 설정, 작업 ID, 상태, 출력 경로 및 오류와 연결합니다.

예시 레코드:

{
  "MUG-001": {
    "sku": "MUG-001",
    "status": "completed",
    "task_id": "prediction-id",
    "quality": "low",
    "size": "1024x1024",
    "prompt": "카탈로그 제품 사진 한 장을 생성하세요...",
    "files": ["generated/MUG-001-01.png"],
    "error": null
  }
}

이 레코드에서 completed는 이미지가 생성되고 다운로드되었음을 의미합니다. task_id는 API 접수 번호입니다. files는 팀에게 이미지가 저장된 위치를 알려줍니다. 생성이 실패하면 error에 SKU를 수정해야 하는지, 재시도해야 하는지, 검토해야 하는지 결정하는 데 필요한 정보가 포함됩니다.

이렇게 하면 검토자에게 안정적인 인수인계를 제공합니다. 또한 거부된 항목만 다시 생성하고, 프롬프트 버전을 비교하고, 에셋을 생성한 요청까지 추적할 수 있습니다. 더 큰 팀의 경우 동일한 필드를 데이터베이스에 저장하고 검토자, 승인 상태, 프롬프트 버전, 캠페인 및 권리 메모를 추가하세요.

대량 제품 생성을 위한 완전한 Python 스크립트

다음 스크립트는 워크플로를 결합합니다. 현재 GPT Proto v3 텍스트-투-이미지 엔드포인트와 예측 결과 경로를 사용하며, SKU당 n=1을 전송하고, 기본적으로 3개의 워커를 사용하며, manifest.json에서 알려진 작업을 재개합니다.

이 코드는 기본 API 데모보다 길지만, 여러분을 위해 여러 가지 안전 작업을 수행합니다:

  • 요청을 보내기 전에 이미지 크기를 확인합니다.

  • API가 제품을 수락하자마자 작업 ID를 저장합니다.

  • 수동으로 GET 명령을 실행하지 않아도 각 이미지를 기다립니다.

  • URL 결과를 다운로드하고 Base64 결과를 디코딩합니다.

  • 한 번에 처리되는 제품 수를 제한합니다.

  • 실패를 기록하고 재시작 후 알려진 작업을 계속합니다.

첫 실행 시에는 함수를 변경하지 않고 스크립트를 복사하세요. products.csv와 API 키만 제공하면 됩니다. 상단 근처의 설정—SIZE, QUALITY, WORKERS, 그리고 POLL_TIMEOUT—은 나중에 조정할 수 있습니다.

import base64
import csv
import json
import os
import random
import re
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from urllib.parse import urlparse

import requests

API_ROOT = "https://gptproto.com/api/v3"
SUBMIT_URL = f"{API_ROOT}/openai/gpt-image-2/text-to-image"
RESULT_URL = f"{API_ROOT}/predictions/{{task_id}}/result"

CSV_PATH = Path(os.getenv("PRODUCTS_CSV", "products.csv"))
OUTPUT_DIR = Path(os.getenv("IMAGE_OUTPUT_DIR", "generated"))
MANIFEST_PATH = Path(os.getenv("IMAGE_MANIFEST", "manifest.json"))

SIZE = os.getenv("IMAGE_SIZE", "1024x1024")
QUALITY = os.getenv("IMAGE_QUALITY", "low")
WORKERS = int(os.getenv("IMAGE_WORKERS", "3"))
POLL_TIMEOUT = int(os.getenv("IMAGE_POLL_TIMEOUT", "900"))

API_KEY = os.environ["GPTPROTO_API_KEY"]
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

manifest_lock = threading.Lock()


class SubmissionStateUnknown(RuntimeError):
    """The POST may have reached the server, but no task ID was received."""


def validate_size(size):
    if size == "auto":
        return
    if not re.fullmatch(r"\d+x\d+", size.lower()):
        raise ValueError("Size must look like 1024x1024 or be 'auto'")

    width, height = map(int, size.lower().split("x"))
    pixels = width * height
    if width % 16 or height % 16:
        raise ValueError("GPT Image 2 width and height must be multiples of 16")
    if max(width, height) > 3840:
        raise ValueError("The longest edge cannot exceed 3840 pixels")
    if max(width, height) / min(width, height) > 3:
        raise ValueError("Aspect ratio cannot exceed 3:1")
    if not 655_360 <= pixels <= 8_294_400:
        raise ValueError("Total pixels are outside the supported range")


def build_prompt(row):
    return f"""Create one catalog product photograph.
Product: {row['name']}
Color: {row['color']}
Material: {row['material']}
View: {row['angle']}
Background: {row['background']}

Keep the product centered with realistic proportions and material texture.
Use soft studio lighting, a natural contact shadow, and generous crop-safe space.
No text, logos, labels, people, or extra objects.
Also avoid: {row.get('avoid', 'none')}.
Return a single product image, not a collage or contact sheet."""


def load_manifest():
    if not MANIFEST_PATH.exists():
        return {}
    return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))


manifest = load_manifest()


def save_record(sku, **changes):
    with manifest_lock:
        record = manifest.setdefault(sku, {"sku": sku})
        record.update(changes)
        temporary = MANIFEST_PATH.with_suffix(".json.tmp")
        temporary.write_text(
            json.dumps(manifest, indent=2, ensure_ascii=False),
            encoding="utf-8",
        )
        temporary.replace(MANIFEST_PATH)


def parse_json(response):
    try:
        return response.json()
    except ValueError:
        return {"raw_response": response.text[:1000]}


def api_error(response):
    payload = parse_json(response)
    return RuntimeError(
        f"HTTP {response.status_code}: {json.dumps(payload, ensure_ascii=False)}"
    )


def submit(prompt):
    payload = {
        "prompt": prompt,
        "n": 1,
        "quality": QUALITY,
        "size": SIZE,
        "enable_sync_mode": False,
        "response_format": "url",
    }

    # Retry only a definite 429 rejection. A connection failure is ambiguous:
    # the job may exist even though this client did not receive its ID.
    for attempt in range(5):
        try:
            response = requests.post(
                SUBMIT_URL,
                headers=HEADERS,
                json=payload,
                timeout=(10, 180),
            )
        except requests.RequestException as exc:
            raise SubmissionStateUnknown(str(exc)) from exc

        if response.status_code != 429:
            if response.status_code >= 400:
                raise api_error(response)
            body = parse_json(response)
            data = body.get("data", body)
            task_id = data.get("id")
            if not task_id:
                raise RuntimeError(f"Submission returned no data.id: {body}")
            return task_id

        if attempt == 4:
            raise api_error(response)
        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after and retry_after.isdigit() else 2**attempt
        time.sleep(min(30, delay) + random.random())

    raise RuntimeError("Submission retry loop ended unexpectedly")


def get_result(task_id):
    deadline = time.monotonic() + POLL_TIMEOUT
    delay = 2.0

    while time.monotonic() < deadline:
        try:
            response = requests.get(
                RESULT_URL.format(task_id=task_id),
                headers=HEADERS,
                timeout=(10, 60),
            )
        except requests.RequestException:
            time.sleep(delay + random.random())
            delay = min(10, delay * 1.5)
            continue

        if response.status_code == 429 or response.status_code >= 500:
            retry_after = response.headers.get("Retry-After")
            wait = float(retry_after) if retry_after and retry_after.isdigit() else delay
            time.sleep(min(30, wait) + random.random())
            delay = min(10, delay * 1.5)
            continue
        if response.status_code >= 400:
            raise api_error(response)

        body = parse_json(response)
        data = body.get("data", body)
        status = str(data.get("status", "")).lower()

        if status in {"completed", "succeeded"}:
            outputs = data.get("outputs") or []
            if not outputs:
                raise RuntimeError(f"Completed task returned no outputs: {body}")
            return outputs
        if status in {"failed", "canceled", "cancelled"}:
            raise RuntimeError(json.dumps(data.get("error") or body, ensure_ascii=False))

        time.sleep(delay)
        delay = min(10, delay * 1.25)

    raise TimeoutError(f"Task {task_id} exceeded {POLL_TIMEOUT} seconds")


def filename_part(value):
    cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip())
    return cleaned.strip("-._") or "product"


def extension_from_response(response, url):
    content_type = response.headers.get("Content-Type", "").split(";", 1)[0]
    by_type = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
    if content_type in by_type:
        return by_type[content_type]
    suffix = Path(urlparse(url).path).suffix.lower()
    return suffix if suffix in {".png", ".jpg", ".jpeg", ".webp"} else ".png"


def save_output(output, sku, index):
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    stem = f"{filename_part(sku)}-{index:02d}"

    if isinstance(output, dict):
        output = output.get("url") or output.get("b64_json") or output.get("data")
    if not isinstance(output, str):
        raise TypeError(f"Unsupported output value: {output!r}")

    if output.startswith(("http://", "https://")):
        response = requests.get(output, timeout=(10, 180))
        response.raise_for_status()
        destination = OUTPUT_DIR / f"{stem}{extension_from_response(response, output)}"
        destination.write_bytes(response.content)
        return str(destination)

    encoded = output.split(",", 1)[1] if output.startswith("data:") else output
    destination = OUTPUT_DIR / f"{stem}.png"
    destination.write_bytes(base64.b64decode(encoded, validate=True))
    return str(destination)


def process_product(row):
    sku = row["sku"].strip()
    prompt = build_prompt(row)
    previous = manifest.get(sku, {})

    if previous.get("status") == "completed":
        return sku, "skipped: already completed"
    if previous.get("status") == "manual_check" and not previous.get("task_id"):
        return sku, "skipped: verify task history before resubmitting"
    if previous.get("status") == "failed":
        return sku, "skipped: review the recorded error before retrying"

    task_id = previous.get("task_id")

    try:
        if not task_id:
            task_id = submit(prompt)
            save_record(
                sku,
                status="submitted",
                task_id=task_id,
                quality=QUALITY,
                size=SIZE,
                prompt=prompt,
                files=[],
                error=None,
            )

        save_record(sku, status="processing")
        outputs = get_result(task_id)
        files = [save_output(value, sku, i) for i, value in enumerate(outputs, 1)]
        save_record(sku, status="completed", files=files, error=None)
        return sku, f"completed: {', '.join(files)}"

    except SubmissionStateUnknown as exc:
        save_record(
            sku,
            status="manual_check",
            task_id=None,
            quality=QUALITY,
            size=SIZE,
            prompt=prompt,
            files=[],
            error=f"Submission state unknown: {exc}",
        )
        return sku, "manual check required"
    except TimeoutError as exc:
        # Keep the task ID. The next run will poll this job instead of submitting
        # the SKU again, so a slow generation does not become a duplicate.
        save_record(sku, status="poll_timeout", task_id=task_id, error=str(exc))
        return sku, f"poll timed out; resume task {task_id} on the next run"
    except Exception as exc:
        save_record(sku, status="failed", task_id=task_id, error=str(exc))
        return sku, f"failed: {exc}"


def main():
    validate_size(SIZE)
    if WORKERS < 1:
        raise ValueError("IMAGE_WORKERS must be at least 1")

    with CSV_PATH.open(newline="", encoding="utf-8-sig") as handle:
        products = list(csv.DictReader(handle))

    required = {"sku", "name", "color", "material", "angle", "background"}
    missing = required - set(products[0]) if products else required
    if missing:
        raise ValueError(f"CSV is missing columns: {sorted(missing)}")

    skus = [row["sku"].strip() for row in products]
    if not all(skus):
        raise ValueError("Every CSV row must have a non-empty SKU")
    if len(skus) != len(set(skus)):
        raise ValueError("CSV contains duplicate SKUs; make each SKU unique before running")

    with ThreadPoolExecutor(max_workers=WORKERS) as pool:
        futures = [pool.submit(process_product, row) for row in products]
        for future in as_completed(futures):
            sku, message = future.result()
            print(f"[{sku}] {message}")


if __name__ == "__main__":
    main()

스크립트 실행 방법: 초보자 안내

1. 두 입력 파일 확인

gpt-image-bulk 폴더를 열고 다음 항목이 포함되어 있는지 확인하세요:

  • bulk_product_images.py, 전체 Python 코드가 포함되어 있습니다.

  • products.csv, 헤더와 최소 하나의 제품 행이 포함되어 있습니다.

스크립트를 bulk_product_images.py.txt로 저장하지 마세요. 편집기에서 파일 확장자를 숨기는 경우, 계속하기 전에 파일 형식을 확인하세요.

2. 해당 폴더에서 터미널 열기

터미널이 두 파일과 같은 폴더를 보고 있어야 합니다. cd 뒤에 폴더 경로를 사용할 수 있습니다. 예:

cd path/to/gpt-image-bulk

현재 폴더는 macOS/Linux에서는 pwd, PowerShell에서는 Get-Location으로 확인할 수 있습니다.

3. API 키 설정 및 requests 설치

설정 섹션에 표시된 명령을 사용해 GPTPROTO_API_KEY를 설정하세요. 그런 다음 아직 설치하지 않았다면 requests를 설치하세요:

python -m pip install requests

4. 파일 실행

macOS, Linux 및 대부분의 Python 설치 환경:

python bulk_product_images.py

Windows에서는 Python 명령이 py인 경우 다음을 사용하세요:

py bulk_product_images.py

첫 번째 제품은 완료되는 데 시간이 걸릴 수 있습니다. API가 생성하는 동안 터미널이 반드시 멈춘 것은 아닙니다. 작업이 완료되면 다음과 비슷한 줄이 표시됩니다:

[MUG-001] completed: generated/MUG-001-01.png
[BAG-014] completed: generated/BAG-014-01.png
[LAMP-207] completed: generated/LAMP-207-01.png

이제 폴더에 generated/ 및 manifest.json이 포함됩니다. 생성된 이미지를 열고 각 이미지를 일치하는 CSV 행과 비교하세요. 그런 다음 텍스트 편집기에서 manifest.json을 열어 모든 성공한 SKU에 "status": "completed"이 있는지 확인하세요.

5. 다시 실행할 때 어떤 일이 발생하는지 이해하기

스크립트는 새 요청을 보내기 전에 manifest.json을 읽습니다. 완료된 SKU는 건너뜁니다. 이전에 폴링 중 중단된 작업은 작업 ID를 유지하고 다시 확인합니다. manual_check 또는 failed로 표시된 레코드는 자동으로 제출되지 않습니다.

이것은 의도된 동작입니다. “명령을 다시 실행”하는 것이 “성공한 모든 이미지에 다시 비용을 지불”하는 일이 되지 않도록 합니다. 실패한 레코드를 안전하게 재시도할 수 있다면 해당 오류를 검토한 후 manifest.json에서 해당 SKU의 레코드만 제거하세요. 전체 카탈로그를 의도적으로 다시 제출하려는 것이 아니라면 전체 매니페스트를 삭제하지 마세요.

6. 초안에서 최종 이미지로 이동

첫 번째 시도에서는 IMAGE_QUALITY=low 및 IMAGE_WORKERS=2 또는 3을 유지하세요. 초안을 검토하고 승인된 제품 행만 products-final.csv라는 새 파일에 복사하세요.

macOS 또는 Linux에서 별도의 최종 실행을 하려면 초안 파일이 그대로 유지되도록 새 출력 및 매니페스트 이름을 사용하세요:

PRODUCTS_CSV=products-final.csv \
IMAGE_OUTPUT_DIR=generated-final \
IMAGE_MANIFEST=manifest-final.json \
IMAGE_QUALITY=high \
python bulk_product_images.py

Windows PowerShell에서는 동일한 값을 먼저 설정한 다음 스크립트를 실행하세요:

$env:PRODUCTS_CSV="products-final.csv"
$env:IMAGE_OUTPUT_DIR="generated-final"
$env:IMAGE_MANIFEST="manifest-final.json"
$env:IMAGE_QUALITY="high"
py bulk_product_images.py

이렇게 하면 비교를 위해 초안 이미지와 초안 매니페스트를 보존하면서 generated-final/에 최종 이미지가 생성됩니다.

대량 작업을 위한 GPT Image 2 가격

GPT Image 2 가격은 토큰 기반이므로 모든 작업에 적용되는 단일 “제품 이미지당 비용”은 정직하게 말해 없습니다. 텍스트 입력, 참조 이미지 입력, 출력 크기, 품질, 결과 수가 모두 총액에 영향을 줍니다.

초보자에게 “백만 토큰당”은 청구 단위이며, 월 구독이나 백만 토큰을 미리 구매해야 한다는 요구 사항이 아닙니다. 프롬프트는 텍스트 입력 토큰이 됩니다. 편집에 사용된 참조 제품 사진은 이미지 입력 토큰이 됩니다. 생성된 그림은 이미지 출력 토큰이 됩니다. 텍스트-이미지 요청은 소스 이미지가 업로드되지 않았으므로 참조 이미지 입력 비용이 없습니다.

작성 시점에 GPT Proto 모델 페이지에는 백만 토큰당 다음 요율이 나와 있습니다:

토큰 범주 GPT Proto 책정 요율 OpenAI 표준 요율
텍스트 입력 $4.00 $5.00
이미지 입력 $6.40 $8.00
캐시된 이미지 입력 $1.60 $2.00
이미지 출력 $24.00 $30.00

이는 나열된 OpenAI 표준 토큰 요율보다 20% 낮습니다. 가격은 변경될 수 있으므로 견적을 게시하거나 대규모 실행을 시작하기 전에 실시간 GPT Image 2 모델 페이지를 확인하세요.

OpenAI의 가격 참고 자료에는 일반적인 크기에 대한 유용한 출력 전용 예시도 나와 있습니다. 1024x1024 이미지는 저품질 약 $0.006, 중품질 $0.053, 고품질 $0.211로 나와 있습니다. 1536x1024 또는 1024x1536 이미지는 각각 약 $0.005, $0.041, $0.165입니다. 입력 토큰은 추가입니다. 이 수치를 영구적인 것으로 간주하지 말고 현재 OpenAI API 가격 페이지를 확인하세요.

카탈로그 견적을 내려면 실제 제품 구성을 반영하는 20개 항목 파일럿을 실행하세요. 실제 지출, 승인된 이미지 비율, 재생성 비율을 기록하세요. 그런 다음 계산하세요:

예상 카탈로그 비용 =
  시도당 평균 API 비용
  × 승인된 이미지당 평균 시도 횟수
  × 제품 수

재생성 비율은 간과하기 쉽습니다. 네 번의 시도가 필요한 저렴한 초안은 첫 번째나 두 번째 시도에서 승인된 더 잘 제어된 중간 품질 작업보다 비용이 더 많이 들 수 있습니다.

텍스트-이미지 대신 참조 이미지를 사용해야 할 때

순수 텍스트-이미지는 제품이 일반적이거나, 이미지가 개념적이거나, 정확한 포장이 판매 가능한 품목과 일치할 필요가 없을 때 적합합니다. 정확한 SKU 표현을 위한 기본값이 되어서는 안 됩니다.

기존 제품의 형태, 색상, 포장 또는 디자인 언어를 보존해야 할 때 GPT Image 2 이미지 편집 엔드포인트를 사용하세요. GPT Image 2는 공식 형식 문서에서 각 50MB 미만의 PNG, WebP 또는 JPEG 참조 이미지를 최대 16개까지 허용하며, 높은 입력 충실도를 자동으로 사용합니다. GPT Proto의 제공자 형식 엔드포인트는 이미지 URL 또는 Base64 데이터 URI를 허용합니다. 자세한 내용은 GPT Image 2 이미지 편집 참조를 참조하세요.

좋은 참조 세트에는 다음이 포함됩니다:

  • 깨끗한 정면 또는 3/4 제품 사진 한 장.

  • 형태가 모호할 때 두 번째 각도.

  • 중요한 재질 질감이나 컨트롤을 위한 클로즈업.

  • 조명과 배경을 위한 별도의 스타일 참조로, 프롬프트에 명확히 명시.

참조 이미지는 제어를 향상시키지만 픽셀 단위로 완벽한 라벨, 작은 텍스트 또는 제품 치수를 보장하지는 않습니다. 사람 검토자가 게시 전에 생성된 자산을 소스 제품과 비교해야 합니다.

비즈니스 및 팀 사용을 위한 프로덕션 체크리스트

파일럿에서 대량 생성으로 전환하기 전:

  • 프롬프트 버전을 고정하고 매니페스트에 저장하세요.

  • 변경 불가능한 SKU 하나를 기본 에셋 키로 사용하세요.

  • 초안은 낮은 품질로 생성하고, 승인된 구성만 최종 설정으로 렌더링하세요.

  • 워커 수를 구성 가능하게 유지하고, 429 응답이 반복되면 줄이세요.

  • 폴링하기 전에 작업 ID를 저장하세요.

  • 모든 임시 결과 URL을 즉시 다운로드하세요.

  • 다음 상태를 구분하세요: failed, manual_check, completed.

  • 모더레이션, 권한 또는 잔액 오류는 변경 없이 재시도하지 마세요.

  • 샘플 결과에서 기하 구조, 색상, 라벨, 그림자, 자르기, 중복 객체를 확인하세요.

  • 소스/참조 권리를 보존하고 검토 승인을 기록하세요.

  • API 키는 CSV나 리포지토리가 아니라 비밀 관리자 또는 환경 변수에 보관하세요.

카탈로그를 자동화하기 전 프롬프트 개발을 위해서는 GPT Image 2 프롬프트 예시가 시각적 방향을 정립하는 데 도움이 될 수 있습니다. 선택한 방향을 대규모로 실행하기 전에 구조화된 필드로 변환하세요.

작은 카탈로그 슬라이스로 시작하기

API 호출은 쉬운 부분입니다. 안정적인 대량 생성은 하나의 제품과 하나의 변형을 분리하고, 동시성을 제한하고, 입력을 검증하고, 예측 ID를 저장하고, 모든 실행을 재개 가능하게 만드는 데서 비롯됩니다.

대표적인 SKU 10~20개를 낮은 품질로 시작하세요. 승인 및 재생성 비율을 측정하고, 공유 프롬프트를 수정한 다음에야 대기열을 확장하세요. 현재 매개변수와 가격은 GPT Image 2 모델 페이지에서 확인하거나, 스크립트를 실행할 준비가 되면 GPT Proto 홈페이지에서 계정을 만들 수 있습니다.

자주 묻는 질문

GPT Image 2 API로 여러 제품 이미지를 만들려면 어떻게 해야 하나요?

제품 행마다 하나의 요청을 사용하고, 일반적으로 n=1로 설정한 뒤, 제어된 워커 풀을 통해 해당 요청을 처리하세요. 반환된 각 작업 ID를 저장하고 다운로드한 출력을 원본 SKU에 매핑하세요. n>1은 동일한 프롬프트의 여러 변형을 원할 때만 사용하세요.

ChatGPT에 한 번에 여러 이미지를 생성해 달라고 요청할 수 있나요?

대화 중에 ChatGPT에 여러 이미지나 변형을 요청할 수 있지만, 이 인터페이스는 수동 창작 작업에 가장 적합합니다. CSV 입력, 안정적인 파일 이름, 재시도, 승인 상태, 스토어 연동이 필요하다면 API를 사용하세요.

GPT Image 2의 레이트 리밋은 무엇인가요?

제공자, 계정, 현재 한도에 따라 다릅니다. OpenAI는 직접 액세스에 대한 티어 기반 한도를 공개하지만, 해당 값을 GPTProto 계정에 그대로 적용해서는 안 됩니다. 워커 두세 개로 시작하고, 429 응답과 Retry-After를 읽은 다음, 관찰된 동작에 따라 조정하세요.

GPT Image 2에서 크기는 16의 배수여야 한다고 하는 이유는 무엇인가요?

사용자 지정 너비와 높이는 각각 16으로 나누어떨어져야 합니다. 또한 크기는 최대 변, 가로세로 비율, 총 픽셀 한도 내에 있어야 합니다. 요청을 보내기 전에 로컬에서 검증하세요. 스토어에서 유효하지 않은 치수를 요구하는 경우, 가까운 유효한 크기로 생성한 뒤 나중에 크기를 조정하세요.

GPT Image 2 Base64 결과를 어떻게 디코딩하나요?

data-URI 접두사가 있으면 제거하고, 남은 문자열을 base64.b64decode에 전달한 뒤, 결과 바이트를 이미지 파일에 쓰세요. 인코딩된 JSON 문자열을 이미지 확장자로 직접 저장하지 마세요.

GPT Image 2 API 대량 생성은 팀에 적합한가요?

생성 외에 더 많은 단계가 파이프라인에 포함된다면 그렇습니다. 팀에는 매니페스트, 변경 불가능한 SKU 매핑, 프롬프트 버전, 검토자 상태, 비용 추적, 모호한 제출에 대한 규칙이 필요합니다. 이러한 제어가 있어야 작업 인계와 부분 재실행이 실용적입니다.

제품 이미지에 OpenAI Batch API를 사용해야 하나요?

최대 24시간까지 기다릴 수 있고 JSONL 프로세스에 맞는 직접 OpenAI 워크로드에 사용하세요. 별도의 레이트 풀과 할인 가격을 제공합니다. 이 가이드의 GPTProto 엔드포인트에는 GPTProto가 Batch API 지원을 명시적으로 문서화하지 않는 한 문서화된 예측 워크플로를 사용하세요.
2026년 가장 저렴한 AI 이미지 생성기 6개: 이미지당 실제 비용

2026년 가장 저렴한 AI 이미지 생성기 6개: 이미지당 실제 비용

가장 저렴한 AI 이미지 생성기는 무엇을 구매하려는지에 따라 달라집니다. 개인용 이미지 서너 장이 필요하다면 무료 브라우저 도구가 유료 API보다 비용이 적게 듭니다. 하지만 앱 안에서 수백 장의 이미지가 필요하다면, 무료 한도를 계속 바꾸고 수동으로 다운로드하는 방식은 실용적인 비용 모델이 아닙니다. API 생성의 경우, GPT Image 1 Mini는 이 비교에서 저품질 1024×1024 이미지 한 장당 약 $0.0035로 가장 낮은 시작 비용을 가집니다 , 여기에 소액의 텍스트 입력 요금이 더해집니다. 요금은 품질, 크기, 토큰 사용량에 따라 달라집니다. Nano Banana 2 Lite는 1K 이미지당 $0.0202로 가장 저렴하면서 예측 가능한 정액 옵션입니다. 이 구분은 중요합니다. 하나는 가능한 가장 낮은 진입 가격이고, 다른 하나는 스타트업 예산에 넣기가 더 쉽습니다. 아래 모든 가격은 2026년 9월 3일에 확인했으며, 여섯 모델 모두 GPTProto의 AI 이미지 생성기 를 통해 이용할 수 있습니다. 지금 이미지 생성하기

Schuyler Stacy | 2026-09-03

5 Best Midjourney API Alternatives in 2026: Real Model APIs, Not Discord Wrappers

5 Best Midjourney API Alternatives in 2026: Real Model APIs, Not Discord Wrappers

Midjourney can produce an excellent image from a loose creative brief. It is far less convenient when that image needs to sit inside an application, automated campaign system, product catalog, or customer-facing workflow. The reason is straightforward: Midjourney does not provide a standard public API for general developer use. Its current community guidelines also prohibit unauthorized automation of the service. Try Midjourney on GPTProto That leaves developers with two different routes: Use a third-party service that submits jobs to Midjourney on your behalf. Replace Midjourney with another image model that has a documented API. This guide covers the second route. The five options below are alternative image models—not Discord bots, browser automation tools, or unofficial Midjourney wrappers. The short answer: Seedream 5.0 Pro is the best overall Midjourney API alternative for polished visual concepts. Nano Banana Pro is the safer choice for complex commercial graphics, GPT Image 2 is better for exact instructions and text, FLUX.1 Kontext Max is worth considering for iterative editing, and Qwen Image Plus is the affordable option for bulk drafts. If you need Midjourney itself rather than a different model, compare the current third-party Midjourney API providers or open the Midjourney API page on GPTProto .

Tiffany Layne | 2026-09-01

Which AI Image Model Is Best for E-Commerce in 2026? 6 Models Tested on Product Photos

Which AI Image Model Is Best for E-Commerce in 2026? 6 Models Tested on Product Photos

One attractive sample tells you almost nothing about whether an AI image model is safe for e-commerce. A beautiful product photo can still contain the wrong bottle cap, a redesigned package, an invented feature, or—worse—the wrong price. We tested six image models with the same three e-commerce tasks: a nail-polish product-in-use photo, a floor lamp placed in a living room, and a wireless-earbuds ad with fixed copy. We tracked retries, product changes, physical mistakes, text accuracy, and whether the result could be published without repair. The short answer: Nano Banana Pro was the best AI image model for e-commerce overall. It was the only model that stayed near the top across all three tests. GPT Image 2 was the best for ads with exact text , while Qwen Image Plus was the lowest-cost option for bulk drafts . Midjourney produced the strongest lifestyle image, but it was the least trustworthy when the product and sales copy had to remain exact. If you only need general image-editing recommendations, see our separate guide to the best image-editing AI models . This comparison focuses specifically on e-commerce production and the cost of getting an image that is actually ready to publish. Try Nano Banana Pro Now

2026-08-27

7 Best Third-Party Midjourney API Providers in 2026: Pricing, Risks, and Verified Options

7 Best Third-Party Midjourney API Providers in 2026: Pricing, Risks, and Verified Options

Midjourney still does not document a public, self-serve API for general developer access. That has not stopped a market of third-party Midjourney API providers from offering hosted endpoints, Discord-account wrappers, and hybrid setups. The problem is not finding a list. It is finding a list that is still current. Several search results continue to recommend products that have removed Midjourney, no longer expose a verifiable product page, or cannot be checked securely. Other comparisons put a hosted request next to a bring-your-own-account wrapper as if they were equivalent. They are not. Try Midjourney on GPTProto My ranking is straightforward: GPTProto is the best overall choice for a multi-model workflow. It puts a versionless Midjourney route beside 200+ other text, image, video, and audio models under one key and shared balance. At about $0.06 per run , however, it is not the lowest listed price here. APIFrame is the strongest dedicated Midjourney workflow option. Its published range is $0.02–$0.10 per job , with four outputs per job, but its Basic plan starts at $39 per month . TTAPI is the price pick. Its listed Imagine rates start around $0.03 in Relax mode , rising to $0.05 Fast and $0.07 Turbo . One warning matters more than any ranking. Midjourney's Terms of Service , effective May 27, 2026, prohibit automated tools that access or generate through the service and prohibit reselling or redistributing access. Every provider in this list is unofficial. A working endpoint today does not remove account, policy, or service-continuity risk tomorrow.

Schuyler Stacy | 2026-08-25