
    (<iI                    ,   d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	Z	ddl
mZ ddlmZmZmZ eZdZdZd	Zd
ZdZdZ ee      j0                  Zedz  Zedz  dz  Zedz  ZdZdZdZdZ ddZ!ddZ"ddZ#d dZ$d dZ%d!dZ&d Z'e(dk(  r e'        yy)"u   M3-1 서울대보험쌤 × 정당한 대우 Google 광고 배너 생성
- m3-1-1200x628.png (가로형)
- m3-1-1080x1080.png (정사각형)
    )annotationsN)Path)sync_playwright)
CTA_MIN_PXFONT_DIRWORKSPACE_ROOT,   0   :   <   g333333?g      ?zoutput/google-ads/banners/m3outputz	v4-hybridzbg_m3_1.jpgz0https://generativelanguage.googleapis.com/v1betazgemini-3-pro-image-previewzgemini-3.1-flash-image-previewu_  Photographic scene of an elegant, warm private study in the evening. Medium shot, slightly low angle looking across the room. A large walnut-toned bookshelf fills most of the back wall — books neatly organized, warm ambient lighting from integrated shelf lighting. A solid walnut desk in the foreground, left side: a MacBook Pro closed, a handwritten notebook open with a pen resting on it, and a small glass of water. A warm reading lamp on the right edge of the desk casting directional amber light. Through a window on the left wall, a soft Seoul cityscape at dusk — lights beginning to appear, sky in deep blue-orange gradient. The room feels lived-in but immaculate — a space of concentrated expertise and quiet ambition. Camera: 50mm equivalent, natural indoor lighting with warm color temperature. No text. No logos. No people. No icons. No graphics.c                     t         j                  j                  dt        t                     ddl} | j                         S )u7   gcloud_auth 모듈로 Bearer 토큰을 획득합니다.r   N)syspathinsertstrBASE_DIRgcloud_authget_access_token)r   s    T/home/jay/workspace/.worktrees/task-2057-dev2/tools/ai-image-gen/gen_m3_1_banners.pyget_auth_tokenr   <   s*    HHOOAs8}%''))    c                    t          d| d}d|  dd}t        j                  |||d      }|j                  dk(  r|S |r+t          d| d	| }d
di}t        j                  |||d      S |S )u6   SA 토큰 우선, 실패 시 API 키로 Gemini 호출.z/models/z:generateContentzBearer zapplication/json)AuthorizationContent-Type,  )headersjsontimeout   z:generateContent?key=r   )GEMINI_API_BASErequestspoststatus_code)	tokenapi_keymodelpayload
url_bearerheaders_bearerrespurl_keyheaders_keys	            r   _call_modelr.   C   s    #$HUG3CDJ)0'8J\]N==^'SVWD3$%XeW4I'S%'9:}}WkQTUUKr   c                    t         j                         r9t         j                         j                  dkD  rt	        dt                 t         S t	        d       t               } t        j                  j                  dt        t                     ddl}|j                  d      }ddt        igigd	d
dgid}t        t        g}d}t!        |d      D ]  \  }}t	        d| d|        	 t#        | |||      }|j&                  dv r9t	        d|j&                   d|j(                  dd  d       d|j&                   }n|j&                  dk7  r8t	        d|j&                   d|j(                  dd         d|j&                   }|j+                         }
|
j-                  dg       }|s(t	        dt+        j.                  |
      dd         d}|d   j-                  di       j-                  dg       }t1        d |D        d      }|7t	        d|D cg c]  }t3        |j5                                c}        d}r|d    j-                  d!d"      }t7        j8                  |d    d#         }t         j;                  |       t	        d$t          d%t=        |      d&d'| d       t         c S  t?        d(|       # t$        $ r$}	t        |	      }t	        d|	        Y d}	~	d}	~	ww xY wc c}w ))uI   Gemini API로 배경 이미지를 생성하고 경로를 반환합니다.iP  u,   [배경] 기존 배경 이미지 재사용: u5   [배경] Gemini API로 배경 이미지 생성 중...r   NGEMINI_API_KEYpartstextresponseModalitiesIMAGETEXT)contentsgenerationConfig   u	     시도 u	   : 모델=u     요청 실패: )i  i  i  u     모델 접근 실패 (z: r    )zHTTP z  HTTP r   
candidatesu     응답에 candidates 없음: zNo candidatescontentc              3  *   K   | ]  }d |v s|  yw)
inlineDataN ).0ps     r   	<genexpr>z&generate_background.<locals>.<genexpr>   s     A|q/@1As   	u     이미지 없음. parts: zNo inlineDatar=   mimeTypez
image/jpegdatau     [배경] 저장 완료:  (,z bytes, mime=u    배경 이미지 생성 실패: ) BG_PATHexistsstatst_sizeprintr   r   r   r   r   r   r   get_api_key	BG_PROMPTMODEL_IDFALLBACK_MODEL_ID	enumerater.   	Exceptionr$   r2   r   getdumpsnextlistkeysbase64	b64decodewrite_byteslenRuntimeError)r%   gar&   r(   models_to_try
last_errorattemptr'   r+   erC   r:   r1   
image_partr@   mime	img_bytess                    r   generate_backgroundrc   R   s    ~~GLLN22V;<WIFG	
ABEHHOOAs8}%nn-.G  34561GV3DEG
 01MJ#M15 &	')E734	ugug>D .,T-=-=,>b4C@QQRST !1!1 23Js"GD,,-R		$3/@AB !1!1 23Jyy{XXlB/
3DJJt4DTc4J3KLM(J1!!)R044WbAAeA4H
/0OAaffh0O/PQR(J,'++JE$$Z%=f%EF	I&*7)2c)nQ5G}UYTZZ[\]M&P 9*F
GGI  	QJ%aS)*	4 1Ps   +K K3	K0K++K0c                    d| j                          }dt         dt         dt         d| dt         dt         dt         d	t
         d
t         dt
         dS )u'   1200×628 가로형 배너 HTML 생성.file://<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
  @font-face {
    font-family: 'Pretendard';
    src: url('file:///Pretendard-Bold.otf') format('opentype');
    font-weight: 700;
  }
  @font-face {
    font-family: 'Pretendard';
    src: url('file:///Pretendard-Medium.otf') format('opentype');
    font-weight: 500;
  }
  @font-face {
    font-family: 'Pretendard';
    src: url('file://uG  /Pretendard-SemiBold.otf') format('opentype');
    font-weight: 600;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }

  body {
    width: 1200px;
    height: 628px;
    overflow: hidden;
    font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
    background: #1A0E00;
  }

  .container {
    position: relative;
    width: 1200px;
    height: 628px;
    overflow: hidden;
  }

  /* 배경 이미지: 우측 40% 영역에서 전체로 확장 */
  .bg-image {
    position: absolute;
    right: 0;
    top: 0;
    width: 65%;
    height: 100%;
    background-image: url('u  ');
    background-size: cover;
    background-position: center right;
  }

  /* 좌→우 딥 브라운 그라데이션 오버레이 */
  .gradient-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(
      to right,
      rgba(62,39,35,0.78) 0%,
      rgba(62,39,35,0.78) 40%,
      rgba(62,39,35,0.70) 52%,
      rgba(62,39,35,0.40) 68%,
      rgba(62,39,35,0.10) 82%,
      transparent 100%
    );
  }

  /* 골드 세로 액센트바 */
  .accent-bar {
    position: absolute;
    top: 80px;
    left: 0;
    width: 4px;
    height: 460px;
    background: linear-gradient(to bottom, #C9A84C, #D4B87A);
    border-radius: 2px;
  }

  /* 좌측 텍스트 영역: 60% */
  .text-area {
    position: absolute;
    left: 0;
    top: 0;
    width: 62%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 48px 56px 48px 60px;
    gap: 0;
  }

  /* 브랜드 뱃지: 높이 48px, 폰트 40px Bold #1A0E00, 배경 #C9A84C */
  .brand-badge {
    display: inline-flex;
    align-items: center;
    background: #C9A84C;
    color: #1A0E00;
    font-size: u  px;
    font-weight: 700;
    height: 48px;
    padding: 0 20px;
    border-radius: 6px;
    letter-spacing: -0.5px;
    margin-bottom: 26px;
    width: fit-content;
    white-space: nowrap;
  }

  /* 헤드라인: 58px Bold #FFF8E7, line-height 1.2 */
  .headline {
    font-size: z?px;
    font-weight: 700;
    color: #FFF8E7;
    line-height: u   ;
    letter-spacing: -1.2px;
    margin-bottom: 20px;
  }

  /* 서브카피: 44px Medium #C9A84C */
  .sub-copy {
    font-size: u   px;
    font-weight: 500;
    color: #C9A84C;
    letter-spacing: -0.5px;
    margin-bottom: 12px;
    text-shadow: 0 2px 8px rgba(0,0,0,0.7);
  }

  /* 서브카피2: 40px Medium #FFF8E7 */
  .sub-copy-2 {
    font-size: u  px;
    font-weight: 500;
    color: #FFF8E7;
    letter-spacing: -0.5px;
    margin-bottom: 36px;
    text-shadow: 0 1px 4px rgba(0,0,0,0.5);
  }

  /* CTA 버튼: 높이 56px, 너비 220px, 골드 그라데이션, #1A0E00 44px Bold, round 6px */
  .cta-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #C9A84C 0%, #D4B87A 100%);
    color: #1A0E00;
    font-size: u  px;
    font-weight: 700;
    height: 56px;
    min-width: 220px;
    padding: 0 28px;
    border-radius: 6px;
    letter-spacing: -0.5px;
    width: fit-content;
    box-shadow: 0 4px 16px rgba(201,168,76,0.35);
    white-space: nowrap;
  }
</style>
</head>
<body>
<div class="container">
  <!-- 배경 이미지 -->
  <div class="bg-image"></div>
  <!-- 그라데이션 오버레이 -->
  <div class="gradient-overlay"></div>
  <!-- 골드 세로 액센트바 -->
  <div class="accent-bar"></div>
  <!-- 텍스트 영역 -->
  <div class="text-area">
    <div class="brand-badge">서울대보험쌤 · TOP사업단</div>
    <div class="headline"><span style="color:#C9A84C">지점장</span>이 직접<br>컨설팅하는 보험영업</div>
    <div class="sub-copy">AI 시스템으로 실적을 극대화</div>
    <div class="sub-copy-2">DB영업 | 멘토링 | 정착금 1,000만원</div>
    <div class="cta-btn">무료 상담 신청 →</div>
  </div>
</div>
</body>
</html>)resolver   _CTA_PX
_SIZE_58PX_LH_1_2
_SIZE_44PXbg_pathbg_uris     r   make_html_1200x628rq      s    w()*F Z  
 Z  
 Z  6 #8 6$l y  |    | 
 y  | eq qr   c                    d| j                          }dt         dt         dt         d| dt         dt         dt         d	t
         d
t         dS )u+   1080×1080 정사각형 배너 HTML 생성.re   rf   rg   rh   u  /Pretendard-SemiBold.otf') format('opentype');
    font-weight: 600;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }

  body {
    width: 1080px;
    height: 1080px;
    overflow: hidden;
    font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
    background: #1A0E00;
  }

  .container {
    position: relative;
    width: 1080px;
    height: 1080px;
    overflow: hidden;
  }

  /* 전체 배경 이미지 */
  .bg-image {
    position: absolute;
    inset: 0;
    background-image: url('uH  ');
    background-size: cover;
    background-position: center;
  }

  /* 딥 브라운 반투명 오버레이 opacity 0.58 */
  .dark-overlay {
    position: absolute;
    inset: 0;
    background: rgba(62, 39, 35, 0.58);
  }

  /* 전체 콘텐츠 레이아웃: 패딩 좌우 72px, 상하 80px */
  .content {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 80px 72px;
  }

  /* 크림 반투명 텍스트 패널 */
  .text-panel {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 860px;
    padding: 56px 48px;
    background: rgba(255, 248, 231, 0.82);
    border-radius: 12px;
    backdrop-filter: blur(4px);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 28px;
    text-align: center;
  }

  /* 브랜드 뱃지: 높이 52px, 폰트 40px Bold #1A0E00, 배경 #C9A84C */
  .brand-badge {
    display: inline-flex;
    align-items: center;
    background: #C9A84C;
    color: #1A0E00;
    font-size: u  px;
    font-weight: 700;
    height: 52px;
    padding: 0 24px;
    border-radius: 6px;
    letter-spacing: -0.5px;
    white-space: nowrap;
    margin-bottom: 0;
  }

  /* 중앙 영역 */
  .middle-area {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    width: 100%;
  }

  /* 헤드라인: 60px Bold #3E2723, 중앙정렬 (크림 패널 위) */
  .headline {
    font-size: z?px;
    font-weight: 700;
    color: #3E2723;
    line-height: u   ;
    letter-spacing: -1.5px;
    text-align: center;
  }

  /* 서브카피 1줄: 44px Medium #A07828 */
  .sub-copy-1 {
    font-size: u  px;
    font-weight: 500;
    color: #A07828;
    letter-spacing: -0.5px;
    text-align: center;
  }

  /* CTA 버튼: 높이 68px, 너비 360px, 골드 그라데이션, #1A0E00 48px Bold */
  .cta-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #C9A84C 0%, #D4B87A 100%);
    color: #1A0E00;
    font-size: u  px;
    font-weight: 700;
    height: 68px;
    min-width: 360px;
    padding: 0 40px;
    border-radius: 6px;
    letter-spacing: -0.5px;
    box-shadow: 0 6px 24px rgba(201,168,76,0.45);
    white-space: nowrap;
  }
</style>
</head>
<body>
<div class="container">
  <!-- 전체 배경 이미지 -->
  <div class="bg-image"></div>
  <!-- 딥 브라운 오버레이 -->
  <div class="dark-overlay"></div>

  <div class="content">
    <!-- 크림 텍스트 패널 -->
    <div class="text-panel">
      <!-- 뱃지 -->
      <div class="brand-badge">서울대보험쌤 · TOP사업단</div>
      <!-- 헤드라인 + 서브카피 -->
      <div class="headline"><span style="color:#C9A84C">지점장</span>이 직접<br>컨설팅하는 보험영업</div>
      <div class="sub-copy-1">AI 시스템으로 실적을 극대화</div>
      <!-- CTA -->
      <div class="cta-btn">무료 상담 신청 →</div>
    </div>
  </div>
</div>
</body>
</html>)ri   r   rj   
_SIZE_60PX_LH_1_25rm   
_SIZE_48PXrn   s     r   make_html_1080x1080rv   K  s    w()*F Z  
 Z  
 Z  0 #8 /$^ y . |    |  | !Oh hr   c           
        t         d| d| dz  }|j                  | d       t               5 }|j                  j	                         }	 |j                  ||d      }|j                  d|j                          d	
       |j                  d       |j                  j                  dd       |j                  t        |      ddd||d       t        d| d|j                         j                  dz  dd       |j!                          	 ddd       |j#                  d       y# |j!                          w xY w# 1 sw Y   1xY w)u:   HTML을 Playwright로 캡처하여 PNG로 저장합니다.
_tmp_m3_1_xz.htmlzutf-8)encoding)widthheight)viewportre   networkidle)
wait_untili  Tparentsexist_okpngr   )ry   yr{   r|   )r   typeclipu     [캡처] rD      .0f KB)N)
missing_ok)TMP_DIR
write_textr   chromiumlaunchnew_pagegotori   wait_for_timeoutparentmkdir
screenshotr   rJ   rH   rI   closeunlink)html_contentr{   r|   output_pathtmp_htmlr@   browserpages           r   capture_html_to_pngr     s@   :eWAfXU;;Hw7		 a**##%
	##u-O#PDII 0 0 234IO!!$'$$TD$AOO[!1Q&EO  K}B{/?/?/A/I/ID/PQT.UUYZ[MMO OOtO$ MMO s$   E
B7D-E-D??EEc                 j   t        d       t        d       t        d       t        j                  dd       t        j                  dd       	 t	               } t        dz  }t        d       	 | rt        |       }n6t        d	      }t        |      j                  d
d      j                  dd      }t        |dd|       t        d|        t        dz  }t        d       	 | rt        |       }n6t        d	      }t        |      j                  d
d      j                  dd      }t        |dd|       t        d|        t        d       t        d       t        d| d|j                         j                  dz  dd       t        d| d|j                         j                  dz  dd       t        d       y # t
        $ r&}t        d|        t        d       d } Y d }~d }~ww xY w# t
        $ r}t        d|         d }~ww xY w# t
        $ r}t        d|         d }~ww xY w)Nz<============================================================u@   M3-1 서울대보험쌤 × 정당한 대우 배너 생성 시작Tr   u   [오류] 배경 생성 실패: u=   [폴백] 다크 그라데이션 배경으로 대체합니다.zm3-1-1200x628.pngu-   
[1] 1200×628 가로형 배너 생성 중...z/nonexistent_bgzurl('file:///nonexistent_bg')nonez.bg-image {zW.bg-image { background: linear-gradient(135deg, #3E2723 0%, #5D3A28 50%, #3E2723 100%);i  it  u
     완료: u     [오류] zm3-1-1080x1080.pngu1   
[2] 1080×1080 정사각형 배너 생성 중...zW.bg-image { background: linear-gradient(160deg, #2C1810 0%, #4A2C1C 40%, #3E2723 100%);i8  z=
============================================================u   생성 완료!u     1200×628 : z  (r   r   r   u     1080×1080: )rJ   r   r   
OUTPUT_DIRrc   rP   rq   r   replacer   rv   rH   rI   )ro   r_   out_1200	html_1200dummyout_1080	html_1080s          r   mainr     s6   	(O	
LM	(OMM$M.TD1%' //H	
:;*73I *+E*5199/ gi  	ItS(;
8*%& 00H	
>?+G4I*+E+E2::/ gi  	ItT8<
8*%&
 
/	
	N8*C(?(?$(Fs'K4
PQ	N8*C(?(?$(Fs'K4
PQ	(Oi  /s34MN.  A3 *  A3 sJ   
G 0A G5 %A H 	G2G--G25	H>HH	H2H--H2__main__)returnr   )
r%   r   r&   z
str | Noner'   r   r(   dictr   zrequests.Response)r   r   )ro   r   r   r   )
r   r   r{   intr|   r   r   r   r   None))__doc__
__future__r   rV   r   r   timepathlibr   r"   playwright.sync_apir   
gen_configr   r   r   rj   rm   ru   rk   rs   rl   rt   __file__r   r   r   r   rF   r!   rM   rN   rL   r   r.   rc   rq   rv   r   r   __name__r>   r   r   <module>r      s   
 #   
    / ; ; 




 >  <<

X

+
M
! E'4 ; 
&*=HDtnk`%0?D zF r   