
    iHa                        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Zddlm	Z	 ddl
Z
ddlmZ  e	d      Z e	d      ZdZd	Zd
ZddddZddZddZddZddZdZdZddZddZddZddZd Zedk(  r e        yy) uE  
T.O.P 리크루팅 캠페인 - Angle A: 정착지원금
Meta 캐러셀 배너 슬라이드 1-3 생성 스크립트

파이프라인:
  1. Gemini Flash Image API → 배경 이미지 생성 (JPEG)
  2. HTML/CSS 템플릿으로 텍스트 오버레이 구성
  3. Playwright 헤드리스 브라우저로 PNG 캡처 (1080x1080)
    )annotationsN)Path)sync_playwrightz8/home/jay/workspace/output/meta-ads/angle-A/v6-benchmarkz&/home/jay/workspace/tools/ai-image-genzgemini-3.1-flash-image-previewz0https://generativelanguage.googleapis.com/v1betaz3https://www.googleapis.com/auth/generative-languagea  Professional dark navy blue abstract background with subtle geometric shapes suggesting stagnation. Flat horizontal lines and a plateauing graph motif faintly visible in the dark background. Moody corporate atmosphere with very soft diffuse lighting. Dark navy (#003B5C) dominant color with slight emerald green (#047857) subtle accent tones. Clean large empty center area for text overlay. NO text, NO people, NO logos, NO numbers. Purely abstract. 1080x1080 square format.a  Abstract dark navy (#003B5C) to emerald green (#047857) gradient background with subtle geometric clock gear shapes and angular urgency motifs faintly visible. Dramatic lighting with a sense of time pressure and transition. Professional financial atmosphere with deep shadows. Clean empty center area for text overlay. NO text, NO people, NO numbers. Abstract professional. 1080x1080 square format.a  Uplifting professional background blending deep navy (#003B5C) and emerald green on the sides with abstract upward-pointing arrow shapes and growth motifs subtly embedded. Hopeful solution-oriented mood. Modern financial atmosphere with soft glowing light from below. Brighter and more vibrant than a problem slide. Clean empty center area for text overlay. NO text, NO people, NO logos. 1080x1080 square format.zslide-01zslide-02zslide-03c                 J   ddl } ddl}| j                  j                  d      r| j                  d   S dD ]l  }t	        |      j                         st	        |      j                  d      }|j                  d|      }|sM|j                  d      j                         c S  y)	uA   ~/.env 또는 ~/workspace/.env.keys 에서 GEMINI_API_KEY 로드.r   NGEMINI_API_KEY)z/home/jay/workspace/.env.keysz/home/jay/workspace/.envutf-8encodingz%GEMINI_API_KEY\s*=\s*\"?([^\"\n]+)\"?   )
osreenvirongetr   exists	read_textsearchgroupstrip)r   r   pathcontentms        =/home/jay/workspace/output/meta-ads/angle-A/v06/gen_slides.pyget_api_keyr   E   s    	zz~~&'zz*++M *:4j**G*<G		BGLAwwqz''))*     c                     	 t        j                  g dddd      } | j                  j                         }|r|S 	 t        d      # t        $ r"}t        d|        Y d}~t        d      d}~ww xY w)u;   gcloud auth print-access-token 으로 Bearer 토큰 획득.)gcloudauthzprint-access-tokenT)capture_outputtextchecku   [WARN] gcloud CLI 실패: Nu   gcloud 토큰 획득 실패)
subprocessrunstdoutr   	ExceptionprintRuntimeError)resulttokenes      r   get_gcloud_tokenr+   U   s    	04d$
 ##%L  4
55  0*1#.//
4
550s   8A 	A2A--A2c                     	 ddl } | j                  j                  dt        t                     ddl}|j                         S # t        $ r Y t               S w xY w)uQ   SA 서비스 계정 또는 gcloud CLI를 통해 Bearer 토큰을 반환합니다.r   N)	sysr   insertstr	TOOLS_DIRgcloud_authget_service_account_tokenr%   r+   )r-   r1   s     r   get_bearer_tokenr3   h   sM    3y>*4466 s   A A 	AAc                   	 t               }t         dt         d}d| dd}d	d
|igigdddgid}t        d|  dt         d       t        j
                         }	 t        j                  |||d      }	|	j                          t        j
                         |z
  }|	j                         }|j                  dg       }|s%t        dt        j                   |      dd         y|d   j                  di       j                  d	g       }t#        d  |D        d      }|6|D cg c]  }d
|v s|j                  d
d!       }}t        d"|dd#         y|d$   j                  d%d&      }t%        j&                  |d$   d'         }d(|v rd)nd*}|j)                  |      }|j+                  |       |j-                         j.                  d+z  }t        d|  d,|j0                   d-|d.d/|d0d1	       y2# t        $ r}t        d|        Y d}~yd}~ww xY w# t        j                  $ r}t        d|j                  j                   d|j                  j                  dd         d}
t        d|
 d       t         d|
 d}	 t        j                  |||d      }	|	j                          n(# t        $ r}t        d|        Y d}~Y d}~yd}~ww xY wY d}~Ud}~ww xY wc c}w )3uP   Gemini API로 배경 이미지를 생성해서 저장합니다. 성공 시 True.u   [ERROR] 인증 실패: NFz/models/z:generateContentzBearer zapplication/json)AuthorizationzContent-Typepartsr    responseModalitiesIMAGETEXT)contentsgenerationConfig  [u1   ] Gemini 이미지 생성 요청 중... (모델: )   )headersjsontimeoutz  [ERROR] HTTP z: i,  zgemini-2.5-flash-imagez
  [RETRY] u   로 재시도...u     [ERROR] 재시도도 실패: 
candidatesu     [ERROR] candidates 없음:    r   r   c              3  *   K   | ]  }d |v s|  yw)
inlineDataN ).0ps     r   	<genexpr>z$generate_bg_image.<locals>.<genexpr>   s     =Q<1+<q=s   	 u1     [ERROR] 이미지 데이터 없음. 텍스트: r   rE   mimeTypez
image/jpegdatajpegz.jpg.png   u   ] 배경 이미지 저장:  (.0fzKB, z.1fu   초)T)r3   r'   r&   GEMINI_API_BASEMODEL_IDtimerequestspostraise_for_status	HTTPErrorresponsestatus_coder    r%   r@   r   dumpsnextbase64	b64decodewith_suffixwrite_bytesstatst_sizename)	slide_keypromptoutput_pathr)   r*   urlr?   payloadstartrY   fallback_modele2elapsedrL   rB   r6   
image_partrH   
text_parts	mime_typeimage_bytesextactual_pathsize_kbs                           r   generate_bg_imagert   v   s    "
 XhZ/?
@C"5'**G  01231GV3DEG
 
C	{KH:UV
WXIIKE==gGSQ!!# iikE!G==?D,+J-djj.>t.D-EFGqMi,00"=E=%=tDJ16FA&A+aeeFB'F
FA*Ra.AQRS<(,,ZFI"":l#;F#CDKi'&VC))#.KK( ((4/G	C	{5k6F6F5Gr'RUVZ[bcfZggk
lms  's+,*  

 6 67r!**//$3:O9PQR1
>**:;< !.)99IJ	}}S'QTUH%%' 	3B4895	 (0 Gsf   
G3 )H %	K$/K$3	H<HHK!*AK)J.-K.	K7KKKKK!z
@import url('https://fonts.googleapis.com/css2?family=Black+Han+Sans&family=Noto+Sans+KR:wght@400;500;700;900&family=Pretendard:wght@400;600;700;900&display=swap');
a  
@font-face {
  font-family: 'Pretendard';
  src: url('/home/jay/.local/share/fonts/Pretendard/Pretendard-Black.otf') format('opentype');
  font-weight: 900;
}
@font-face {
  font-family: 'Pretendard';
  src: url('/home/jay/.local/share/fonts/Pretendard/Pretendard-Bold.otf') format('opentype');
  font-weight: 700;
}
@font-face {
  font-family: 'Pretendard';
  src: url('/home/jay/.local/share/fonts/Pretendard/Pretendard-SemiBold.otf') format('opentype');
  font-weight: 600;
}
@font-face {
  font-family: 'Pretendard';
  src: url('/home/jay/.local/share/fonts/Pretendard/Pretendard-Regular.otf') format('opentype');
  font-weight: 400;
}
c                    dt          d|  dS )u/   슬라이드 1: 문제 공감 (Problem Empathy)u   <!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=1080" />
  <title>Slide 01 - 문제 공감</title>
  <style>
    uC  

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

    html, body {
      width: 1080px;
      height: 1080px;
      overflow: hidden;
      -webkit-font-smoothing: antialiased;
    }

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

    /* 하단 그라디언트 오버레이 (가독성용, 하단 40%) */
    #bottom-grad {
      position: absolute;
      left: 0; right: 0;
      bottom: 0;
      height: 432px; /* 1080 * 0.40 */
      background: linear-gradient(
        to bottom,
        rgba(0, 59, 92, 0) 0%,
        rgba(0, 59, 92, 0.75) 60%,
        rgba(0, 59, 92, 0.92) 100%
      );
      z-index: 1;
    }

    /* 중앙 상단 약한 다크 오버레이 (배경 텍스트 대비) */
    #top-overlay {
      position: absolute;
      left: 0; right: 0;
      top: 0;
      height: 720px;
      background: linear-gradient(
        to bottom,
        rgba(0, 20, 40, 0.35) 0%,
        rgba(0, 20, 40, 0.15) 100%
      );
      z-index: 1;
    }

    /* 콘텐츠 래퍼 */
    #content {
      position: absolute;
      inset: 0;
      z-index: 10;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }

    /* 헤드라인 블록 (center ~50% 기준) */
    .headline-block {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 16px;
      margin-top: -64px; /* 약간 위로 올려 시각적 무게 중심 */
    }

    .headline-line {
      font-family: 'Black Han Sans', 'Noto Sans KR', sans-serif;
      font-size: 96px;
      font-weight: 900;
      color: #FFFFFF;
      text-align: center;
      line-height: 1.15;
      letter-spacing: -0.02em;
      word-break: keep-all;
      text-shadow:
        0 2px 12px rgba(0, 0, 0, 0.60),
        0 4px 32px rgba(0, 0, 0, 0.40);
    }

    .orange {
      color: #EA580C;
    }

    /* CTA 바 (하단 고정) */
    #cta-bar {
      position: absolute;
      left: 0; right: 0;
      bottom: 0;
      height: 128px;
      background: #047857;
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 20;
    }

    .cta-text {
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 44px;
      font-weight: 700;
      color: #FFFFFF;
      text-align: center;
      letter-spacing: -0.01em;
      word-break: keep-all;
    }
  </style>
</head>
<body>
  <div id="bg"></div>
  <div id="top-overlay"></div>
  <div id="bottom-grad"></div>

  <div id="content">
    <div class="headline-block">
      <div class="headline-line">열심히는 하는데,</div>
      <div class="headline-line">월급은 <span class="orange">제자리걸음</span>?</div>
    </div>
  </div>

  <div id="cta-bar">
    <span class="cta-text">다른 방법이 있다는 걸 아세요? →</span>
  </div>
</body>
</html>LOCAL_FONT_FACEbg_paths    r   build_html_slide1rz      s-       &Y q'-G Gr   c                    dt          d|  dS )u#   슬라이드 2: 긴급성 (Urgency)u   <!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=1080" />
  <title>Slide 02 - 긴급성</title>
  <style>
    (  

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

    html, body {
      width: 1080px;
      height: 1080px;
      overflow: hidden;
      -webkit-font-smoothing: antialiased;
    }

    #bg {
      position: absolute;
      inset: 0;
      background-image: url('u%  ');
      background-size: cover;
      background-position: center;
      z-index: 0;
    }

    /* 상단~중단 오버레이 (텍스트 가독성) */
    #center-overlay {
      position: absolute;
      inset: 0;
      background: radial-gradient(
        ellipse 90% 70% at 50% 40%,
        rgba(0, 30, 60, 0.50) 0%,
        rgba(0, 10, 30, 0.20) 100%
      );
      z-index: 1;
    }

    /* 하단 그라디언트 */
    #bottom-grad {
      position: absolute;
      left: 0; right: 0;
      bottom: 0;
      height: 380px;
      background: linear-gradient(
        to bottom,
        rgba(0, 10, 25, 0) 0%,
        rgba(0, 10, 25, 0.88) 100%
      );
      z-index: 1;
    }

    #content {
      position: absolute;
      inset: 0;
      z-index: 10;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 0 80px;
    }

    .headline-block {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 16px;
      margin-bottom: 48px;
    }

    .headline-line {
      font-family: 'Black Han Sans', 'Noto Sans KR', sans-serif;
      font-size: 96px;
      font-weight: 900;
      color: #FFFFFF;
      text-align: center;
      line-height: 1.15;
      letter-spacing: -0.02em;
      word-break: keep-all;
      text-shadow:
        0 2px 12px rgba(0, 0, 0, 0.65),
        0 4px 40px rgba(0, 0, 0, 0.45);
    }

    .orange { color: #EA580C; }

    .subtext {
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 64px;
      font-weight: 600;
      color: #E0E0E0;
      text-align: center;
      letter-spacing: -0.01em;
      word-break: keep-all;
      text-shadow:
        0 2px 8px rgba(0, 0, 0, 0.55);
    }

    /* CTA 바 */
    #cta-bar {
      position: absolute;
      left: 0; right: 0;
      bottom: 0;
      height: 128px;
      background: rgba(0, 20, 45, 0.92);
      border-top: 3px solid rgba(4, 120, 87, 0.60);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 20;
    }

    .cta-text {
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 44px;
      font-weight: 700;
      color: #FFFFFF;
      text-align: center;
      letter-spacing: -0.01em;
      word-break: keep-all;
    }
  </style>
</head>
<body>
  <div id="bg"></div>
  <div id="center-overlay"></div>
  <div id="bottom-grad"></div>

  <div id="content">
    <div class="headline-block">
      <div class="headline-line">2026년 <span class="orange">7월</span>,</div>
      <div class="headline-line">게임이 바뀝니다.</div>
    </div>
    <div class="subtext"><span class="orange">지금</span>이 마지막 타이밍입니다.</div>
  </div>

  <div id="cta-bar">
    <span class="cta-text">변화 전에 움직이세요 →</span>
  </div>
</body>
</html>rv   rx   s    r   build_html_slide2r}   e  s-       &Y y'+N Nr   c                    dt          d|  dS )u$   슬라이드 3: 해결책 (Solution)u   <!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=1080" />
  <title>Slide 03 - 해결책</title>
  <style>
    r|   u9  ');
      background-size: cover;
      background-position: center;
      z-index: 0;
    }

    /* 오버레이: 상단 어둡게 하여 헤드라인 가독성 */
    #top-overlay {
      position: absolute;
      inset: 0;
      background: linear-gradient(
        to bottom,
        rgba(0, 30, 55, 0.55) 0%,
        rgba(0, 30, 55, 0.25) 40%,
        rgba(0, 30, 55, 0.10) 60%,
        rgba(0, 30, 55, 0.40) 100%
      );
      z-index: 1;
    }

    #content {
      position: absolute;
      inset: 0;
      z-index: 10;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
      padding-top: 88px;
    }

    /* 헤드라인 (상단 영역) */
    .headline-block {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 16px;
      margin-bottom: 56px;
    }

    .headline-line {
      font-family: 'Black Han Sans', 'Noto Sans KR', sans-serif;
      font-size: 84px;
      font-weight: 900;
      color: #FFFFFF;
      text-align: center;
      line-height: 1.18;
      letter-spacing: -0.02em;
      word-break: keep-all;
      text-shadow:
        0 2px 12px rgba(0, 0, 0, 0.65),
        0 4px 32px rgba(0, 0, 0, 0.40);
    }

    /* 핵심 데이터 블록 (center) */
    .data-block {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 16px;
      background: rgba(0, 30, 55, 0.52);
      border: 1px solid rgba(255,255,255,0.12);
      border-radius: 24px;
      padding: 48px 72px;
      backdrop-filter: blur(8px);
      -webkit-backdrop-filter: blur(8px);
    }

    .hero-number {
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 112px;
      font-weight: 900;
      color: #EA580C;
      text-align: center;
      letter-spacing: -0.03em;
      line-height: 1.0;
      text-shadow:
        0 0 40px rgba(234, 88, 12, 0.45),
        0 2px 8px rgba(0, 0, 0, 0.50);
    }

    .hero-label {
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 64px;
      font-weight: 700;
      color: #FFFFFF;
      text-align: center;
      letter-spacing: -0.01em;
    }

    .hero-sub {
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 64px;
      font-weight: 500;
      color: #E0E0E0;
      text-align: center;
      letter-spacing: -0.01em;
    }

    /* CTA 바 */
    #cta-bar {
      position: absolute;
      left: 0; right: 0;
      bottom: 0;
      height: 128px;
      background: #047857;
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 20;
    }

    .cta-text {
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 44px;
      font-weight: 700;
      color: #FFFFFF;
      text-align: center;
      letter-spacing: -0.01em;
      word-break: keep-all;
    }

    /* 면책 문구 */
    #disclaimer {
      position: absolute;
      bottom: 144px; /* CTA 바 위 */
      right: 40px;
      font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
      font-size: 40px;
      font-weight: 400;
      color: #CCCCCC;
      text-align: right;
      z-index: 20;
      text-shadow: 0 1px 4px rgba(0,0,0,0.50);
    }
  </style>
</head>
<body>
  <div id="bg"></div>
  <div id="top-overlay"></div>

  <div id="content">
    <div class="headline-block">
      <div class="headline-line">정착지원금이</div>
      <div class="headline-line">문제를 풀어줍니다.</div>
    </div>

    <div class="data-block">
      <div class="hero-number">최대 1,000만원</div>
      <div class="hero-label">신입 정착지원금</div>
      <div class="hero-sub">경력 직전 연봉 50%까지</div>
    </div>
  </div>

  <div id="cta-bar">
    <span class="cta-text">파격적 지원 확인하기 →</span>
  </div>

  <div id="disclaimer">조건 있음, 상담 시 확인</div>
</body>
</html>rv   rx   s    r   build_html_slide3r     s-       &Y `'+u ur   c                L   |j                  d      }|j                  | d       t               5 }|j                  j	                         }	 |j                  ddd      }|j                  d|j                          d	       |j                  d
       |j                  t        |      d       |j                          	 ddd       |j                         j                  dz  }t        d|j                   d|dd       y# |j                          w xY w# 1 sw Y   YxY w)u3   HTML 콘텐츠를 1080x1080 PNG로 캡처합니다.z.htmlr	   r
   i8  )widthheight)viewportfile://networkidle)
wait_untili	  png)r   typeNrO   u     [캡처] rP   rQ   KB))r_   
write_textr   chromiumlaunchnew_pagegotoresolvewait_for_timeout
screenshotr/   closera   rb   r&   rc   )html_contentrf   	html_filerH   browserpagers   s          r   capture_slider     s   ''0I8		 	a**##%	##tt-L#MDII	 1 1 345-IP!!$'OO[!1O>MMO	  ((4/G	K(()GC=
<= MMO	 	s$   DA&D1DDDD#c                 j   t         j                  dd       t        d       t        d       t        dt                 t        d       g d} i }t        d       | D ]  }t         d| z  }t        t         j	                  d| d	            }|r'|d
   ||<   t        d| d|d
   j
                          Zt        |t        |   |      }|s$t        d| d       t        j                  d       t        t         j	                  d| d	            }|s$t        d| d	       t        j                  d       |d
   ||<    t        d       t        t        t        d}| D ]J  }d||   j                          } ||   |      }	t         | dz  }
t        d| d       t        |	|
       L t        d       t        d       t        d       | D ]^  }t         | dz  }|j                         r1|j!                         j"                  dz  }t        d| d|dd       Pt        d| d       ` y )NT)parentsexist_okz<============================================================u7   T.O.P 캐러셀 배너 생성 시작 (슬라이드 1-3)u   출력 디렉토리: r   u(   
[Step 1] Gemini 배경 이미지 생성bg_z.*r   r<   u"   ] 기존 배경 이미지 사용: z
  [ERROR] u    배경 생성 실패r   u;     [ERROR] 배경 이미지 파일을 찾을 수 없음: bg_u(   
[Step 2] HTML 오버레이 + PNG 캡처r   rN   u   ] HTML 빌드 + 캡처...z=
============================================================u   생성 완료!rO   z  OK  z  (rQ   r   z  FAIL  u     (파일 없음))
OUTPUT_DIRmkdirr&   listglobrc   rt   
BG_PROMPTSr-   exitrz   r}   r   r   r   r   ra   rb   )slidesbg_pathsslidebg_outexistingoksavedhtml_buildersbg_path_strhtmlout_pngr   rs   s                r   mainr     sB   TD1	(O	
CD	!*
./	(O1F "H 

56 #E7m+
#eWB89&qkHUOCw@!AQAQ@RSTuj&7@Jug%:;<HHQKZ__s5'_56OPUwVXYZHHQK(##( 

56%%%M  % 7 7 9:;#}U#K0%~-E7345dG$% 
/	
	(O 5eWD>)::<hhj((4/GF3%s73-s34HSE!2345r   __main__)returnz
str | None)r   r/   )rd   r/   re   r/   rf   r   r   bool)ry   r/   r   r/   )r   r/   rf   r   r   None)__doc__
__future__r   r]   r@   r"   r-   rT   pathlibr   rU   playwright.sync_apir   r   r0   rS   rR   GEMINI_SCOPEr   r   r+   r3   rt   GOOGLE_FONTS_IMPORTrw   rz   r}   r   r   r   __name__rF   r   r   <module>r      s    #    
    / LM
9:	+DD	^	Z	A#
< 6&?L 
0IXPfw|>085v zF r   