
    i0                        d 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j                  j                  d e ee      j$                               ddlZdZdZdZd	ZeZd
ZdZedz  Zedz  Zedz  ZdZdZdZ dZ!de de" de de de de de de de dZ#defdZ$deddfd Z%d! Z&e'd"k(  r e&        yy)#u  
sample-A-portrait: 인물+텍스트 하이브리드 광고 배너 생성
- 배경: Gemini AI (비즈니스 정장 한국인 남성, 사무실, 우측 인물)
- 텍스트: HTML/CSS + Playwright
- 출력: /home/jay/workspace/output/meta-ads/angle-A/concept-samples/sample-A-portrait.png
    N)Path)sync_playwright)WORKSPACE_ROOTFONT_DIRSUBHEAD_MIN_PX             gzG?g?z'output/meta-ads/angle-A/concept-sampleszsample-A-portrait-bg.jpgzsample-A-portrait.pngzgemini-3.1-flash-image-previewz0https://generativelanguage.googleapis.com/v1betaz3https://www.googleapis.com/auth/generative-languageuT  Premium photo-realistic advertisement background for Meta ads. Korean male in his 30s wearing a sharp navy business suit, confident expression, slight smile, looking slightly off-camera. Subject positioned on the RIGHT THIRD of the frame. LEFT HALF of the image must be intentionally blurred/clean dark navy background — this space is reserved for text overlay. Setting: modern high-rise office, Seoul skyline visible through large windows behind. Lighting: premium studio-style, warm key light from upper-right, subtle rim light. Color palette: deep navy (#1B2A4A dominant), warm whites, subtle gold accents. Camera: 85mm portrait lens, shallow depth of field, background softly bokeh'd. Style: like a premium financial services advertisement (Merrill Lynch / Goldman Sachs quality). NO text, NO watermark, NO clipart. 1:1 square format 1080x1080px.z<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=1080" />
  <style>
    @import url('file://u  /pretendard.css');

    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    html, body {
      width: 1080px;
      height: 1080px;
      overflow: hidden;
      -webkit-font-smoothing: antialiased;
      font-family: 'Pretendard', 'Noto Sans KR', 'Apple SD Gothic Neo', sans-serif;
    }

    /* 배경 레이어 */
    #bg {
      position: absolute;
      inset: 0;
      background-image: url('un  ');
      background-size: cover;
      background-position: center center;
      z-index: 0;
    }

    /* 좌측 텍스트 영역 어둠 강화 오버레이 */
    #left-overlay {
      position: absolute;
      top: 0; left: 0;
      width: 560px; height: 1080px;
      background: linear-gradient(
        90deg,
        rgba(27, 42, 74, 0.92) 0%,
        rgba(27, 42, 74, 0.82) 70%,
        rgba(27, 42, 74, 0.0) 100%
      );
      z-index: 1;
    }

    /* 전체 미묘한 비네트 */
    #vignette {
      position: absolute;
      inset: 0;
      background: radial-gradient(
        ellipse at 30% 50%,
        transparent 30%,
        rgba(10, 15, 30, 0.45) 100%
      );
      z-index: 2;
      pointer-events: none;
    }

    /* 텍스트 콘텐츠 래퍼 */
    #content {
      position: absolute;
      inset: 0;
      z-index: 10;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      padding: 72px 60px 72px 72px;
    }

    /* 상단 영역: 메인 헤드라인 */
    .top-section {
      display: flex;
      flex-direction: column;
      gap: 0;
      max-width: 520px;
    }

    /* 오렌지 상단 액센트 바 */
    .accent-bar {
      width: 52px;
      height: 5px;
      background: #FF6B35;
      border-radius: 3px;
      margin-bottom: 28px;
    }

    /* 메인 헤드라인 */
    .headline {
      font-size: z/px;
      font-weight: 900;
      line-height: u  ;
      letter-spacing: -0.03em;
      color: #FFFFFF;
      word-break: keep-all;
      text-shadow:
        0 2px 12px rgba(0, 0, 0, 0.50),
        0 4px 40px rgba(0, 0, 0, 0.30);
    }

    /* 헤드라인 내 오렌지 강조 */
    .headline .highlight {
      color: #FF6B35;
    }

    /* 중간 서브 메시지 */
    .sub-message {
      margin-top: 32px;
      font-size: z/px;
      font-weight: 400;
      line-height: u  ;
      color: rgba(255, 255, 255, 0.78);
      word-break: keep-all;
      max-width: 480px;
      letter-spacing: -0.01em;
    }

    /* 하단 CTA 영역 */
    .bottom-section {
      display: flex;
      flex-direction: column;
      gap: 20px;
      max-width: 520px;
    }

    /* CTA 버튼 스타일 */
    .cta-button {
      display: inline-flex;
      align-items: center;
      gap: 12px;
      background: #FF6B35;
      color: #FFFFFF;
      font-size: a8  px;
      font-weight: 800;
      padding: 22px 40px;
      border-radius: 8px;
      letter-spacing: -0.02em;
      word-break: keep-all;
      align-self: flex-start;
      box-shadow:
        0 4px 24px rgba(255, 107, 53, 0.45),
        0 2px 8px rgba(0, 0, 0, 0.30);
    }

    .cta-arrow {
      font-size: uX   px;
      font-weight: 900;
    }

    /* 브랜드명 */
    .brand {
      font-size: u9  px;
      font-weight: 500;
      color: rgba(255, 255, 255, 0.50);
      letter-spacing: 0.06em;
      text-transform: uppercase;
    }
  </style>
</head>
<body>

  <!-- 배경 -->
  <div id="bg"></div>

  <!-- 좌측 어둠 오버레이 (텍스트 가독성) -->
  <div id="left-overlay"></div>

  <!-- 비네트 -->
  <div id="vignette"></div>

  <!-- 텍스트 콘텐츠 -->
  <div id="content">

    <!-- 상단: 메인 헤드라인 -->
    <div class="top-section">
      <div class="accent-bar"></div>
      <div class="headline">
        열심히는 하는데,<br>
        <span class="highlight">월급은</span><br>
        제자리걸음?
      </div>
      <div class="sub-message">
        노력한 만큼 보상받는<br>방법이 따로 있습니다.
      </div>
    </div>

    <!-- 하단: CTA -->
    <div class="bottom-section">
      <div class="cta-button">
        다른 방법이 있다는 걸 아세요?
        <span class="cta-arrow">→</span>
      </div>
      <div class="brand">Premium Career Solution</div>
    </div>

  </div>

</body>
</html>returnc            	         t        d       d } d }	 t        j                  t              } t        dt	        |        d       t
         dt         d}d|  dd}ddt        igigdddgid}t        j                         }t        j                  |||d      }|j                          t        j                         |z
  }|j                         }	|	j!                  dg       }
|
s$t        dt        j"                  |	      d d        |
d   j!                  di       j!                  dg       }t%        d |D        d       }|5|D cg c]  }d|v s|j!                  dd       }}t        d|d d         |d!   j!                  d"d#      }t'        j(                  |d!   d$         }d%|v rd&nd'}t*        j-                  |      }|j/                  |       t        d(| d)t	        |      d*d+|d,d-       |S # t        $ rj}t        d	| d
       t        j                  d      }|st        d      t        dt	        |       d       t
         dt         d| }ddi}Y d }~d }~ww xY wc c}w ).Nu5   [Step 1] Gemini API로 배경 이미지 생성 중...u      인증: SA 토큰 사용 (len=)z/models/z:generateContentzBearer zapplication/json)AuthorizationContent-Typeu     SA 토큰 실패: u   . API 키로 fallback...GEMINI_API_KEYu)   SA 토큰과 API 키 모두 사용 불가u     인증: API 키 사용 (len=z:generateContent?key=r   partstextresponseModalitiesIMAGETEXT)contentsgenerationConfig   )headersjsontimeout
candidatesu   candidates 없음. 응답: i  r   contentc              3   *   K   | ]  }d |v s|  yw)
inlineDataN ).0ps     Y/home/jay/workspace/.worktrees/task-2116-dev1/tools/ai-image-gen/gen_sample_a_portrait.py	<genexpr>z&generate_background.<locals>.<genexpr>(  s     =Q<1+<q=s   	 u$   이미지 파트 없음. 텍스트:    r    mimeTypez
image/jpegdatajpegz.jpgz.pngu     배경 저장:  (,z bytes, .1fu   초))printgcloud_authget_service_account_tokenGEMINI_SCOPElenGEMINI_API_BASEMODEL_ID	Exceptionget_api_keyRuntimeError	BG_PROMPTtimerequestspostraise_for_statusr   getdumpsnextbase64	b64decodeBG_PATHwith_suffixwrite_bytes)tokenapi_keyurlr   epayloadstartrespelapsedr)   r   r   
image_partr#   
text_partsmime	img_bytesextbg_paths                      r$   generate_backgroundrS      s   	
AB EG755lC0UA>? !(3CD&ug..
  34561GV3DEG
 IIKE==gGSIDiikE!G99;D,+J8D9I$39O8PQRRqMi,00"=E=%=tDJ16FA&A+aeeFB'F
FA*Ra.AQRSSl#''
LAD  L!9&!ABIdN&C!!#&G	"	gYbY(:(73-t
TUNO  7$QC'?@A))*:;JKK.s7|nA>? !(3H	R!#5678 Gs%   A	G8 	I.I.8	I+AI&&I+rR   c                 X   t        d       d| j                          }t        j                  |      }t        dz  }|j                  |d       t        d|        t               5 }|j                  j                  g d	      }	 |j                  d
d
d      }|j                  d|j                          d       |j                  d       t        j                  j                  dd       |j                  t!        t              dd       t        dt                |j#                          	 d d d        |j%                  d       t        j'                         j(                  dz  }t        dt         d|dd       y # |j#                          w xY w# 1 sw Y   ixY w)Nu:   [Step 2] HTML 오버레이 생성 및 Playwright 캡처...zfile://)bg_urlz_tmp_sample_a_portrait.htmlzutf-8)encodingu     HTML 템플릿 저장: )z--no-sandboxz--disable-setuid-sandboxz--font-render-hinting=none)argsi8  )widthheight)viewportnetworkidle)
wait_untili	  Tparentsexist_okpngF)pathtype	full_pageu     캡처 완료: )
missing_oki   u     최종 이미지: r+   z.0fz KB))r.   resolveHTML_TEMPLATEformat
OUTPUT_DIR
write_textr   chromiumlaunchnew_pagegotowait_for_timeout
FINAL_PATHparentmkdir
screenshotstrcloseunlinkstatst_size)rR   rU   html_content	html_pathr#   browserpagesize_kbs           r$   capture_overlayr}   =  s   	
FGw()*F ''v'6L ::I8	%i[
12		 a**## $ 
	##tt-L#MD II	 1 1 345-IP !!$'##D4#@OOZuON%j\23MMO+0 %oo''$.G	 Bwsm4
@A MMO+ s%   )F 	BF'F FF  F)c                  \   t        d       t        d       t        dt                t        d       t        j                  dd       t	        j                         } t               }t        |       t	        j                         | z
  }t        d|dd       t        d	t                y )
Nz<============================================================u2   sample-A-portrait 하이브리드 이미지 생성u   출력: Tr]   u   
완료! 총 소요: r-   u   초u   결과물: )r.   ro   rh   rq   r9   rS   r}   )total_startrR   totals      r$   mainr   j  s    	(O	
>?	HZL
!"	(OTD1))+K "#G GIIK+%E	"5+S
12	K
|
$%    __main__)(__doc__r@   r   sysr9   pathlibr   r:   playwright.sync_apir   
gen_configr   r   r   ra   insertrs   __file__rp   r/   
_SIZE_22PX
_SIZE_28PX
_SIZE_30PX
_SIZE_32PX_SUBHEAD_PX_LH_1_18_LH_1_6rh   rB   ro   r4   r3   r1   r8   rU   rf   rS   r}   r   __name__r!   r   r$   <module>r      s`     
    / ? ? 3tH~,,- . 




 GG

1
111
+DD
H 
 "
 #  %X ?&~  : "  9 ,      /gBR6T 6z&BT &Bd &BZ&( zF r   