
    iY                    6   U 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Zddl	Z	ddl
m
Z
 ddlmZ ddlmZ ddlZdZdZd	e d
e Z ed      Z ed      Zedz  edz  edz  dZded<   edz  Zg ddgdgdgdZded<   dZdZ ej6                  ej8                  dd        ej:                  d      Zd+d Zd,d!Z 	 	 	 	 	 	 d-d"Z!	 	 	 	 d.d#Z"	 	 	 	 	 	 	 	 d/d$Z#	 	 	 	 	 	 	 	 	 	 d0d%Z$	 	 	 	 	 	 	 	 	 	 	 	 d1d&Z%	 	 	 	 	 	 d2d'Z&d3d(Z'd4d)Z(e)d*k(  r e(        yy)5u8  Gemini API를 사용한 v3 캠페인 이미지 생성 스크립트.

gemini-2.0-flash-preview-image-generation 모델을 통해
v3_prompts.json에 정의된 프롬프트 기반으로 이미지를 생성합니다.
한글 깨짐 방지를 위한 다중 시도(attempt) 및 결과 기록 기능을 지원합니다.
    )annotationsN)datetime)Path)Anyzgemini-3-pro-image-preview'AIzaSyB4X1239KxqIYIuhr9rBrawwxs2RddbYccz8https://generativelanguage.googleapis.com/v1beta/models/z:generateContent?key=z./home/jay/workspace/teams/dev1/v3_prompts.jsonz1/home/jay/workspace/output/campaign-top/v3-geminizmeta-carousel-azmeta-carousel-bz	naver-gfa)meta_carousel_ameta_carousel_b	naver_gfazdict[str, Path]OUTPUT_DIRSzgeneration_results.jsonr   r	   r
   allabgfazdict[str, list[str]]	GROUP_MAP,     z2%(asctime)s [%(levelname)s] %(name)s - %(message)sz%Y-%m-%d %H:%M:%S)levelformatdatefmtv3_generatec                 .    t         j                  d       y)uh   API 키를 반환합니다. (하위호환용 — API 키가 URL에 포함되므로 빈 문자열 반환)u(   API 키 인증 사용 (URL 파라미터) )loggerinfo     -/home/jay/workspace/teams/dev1/v3_generate.pyget_auth_tokenr   E   s    
KK:;r   c                ~   | j                         s!t        j                  d|        t        d      	 t	        | d      5 }t        j                  |      }ddd       t        j                  d|        S # 1 sw Y   !xY w# t
        j                  $ r'}t        j                  d|       t        d      |d}~ww xY w)u  v3_prompts.json 파일에서 프롬프트 설정을 읽어옵니다.

    Args:
        prompts_file: 프롬프트 JSON 파일 경로.

    Returns:
        그룹명 → 슬라이드 목록 딕셔너리.

    Raises:
        SystemExit: 파일이 없거나 JSON 파싱 실패 시.
    u2   프롬프트 파일을 찾을 수 없습니다: %s   utf-8encodingNu%   프롬프트 파일 로드 완료: %su*   프롬프트 파일 JSON 파싱 실패: %s)	existsr   error
SystemExitopenjsonloadr   JSONDecodeError)prompts_filefdataes       r   load_promptsr0   N   s      I<Xm#,1 	AQ48IIaLD	A;\J	A 	A  #A1Em"#s/   B  A6B 6A?;B B<"B77B<c                X   ddi}dd|igigddgddd	d
d}t         j                  dt               t        j                  t        ||t
              }|j                  s>t         j                  d|j                  |j                  dd        |j                          |j                         S )uZ  Gemini API에 이미지 생성 요청을 보냅니다.

    Args:
        token: Bearer 인증 토큰.
        prompt: 이미지 생성 프롬프트.

    Returns:
        API 응답 JSON 딕셔너리.

    Raises:
        requests.HTTPError: HTTP 4xx/5xx 오류 발생 시.
        requests.RequestException: 네트워크 오류 발생 시.
    zContent-Typezapplication/jsonpartstextIMAGETEXTr!   gffffff?(   )responseModalitiestemperaturetopPtopK)contentsgenerationConfigu   API 호출: %s)headersr)   timeoutu   HTTP 오류 %d: %sN  )r   debugGEMINI_API_ENDPOINTrequestspostREQUEST_TIMEOUTokr&   status_coder3   raise_for_statusr)   )tokenpromptr=   payloadresponses        r   call_gemini_apirL   k   s    $ 	*G  0123#*F"3	
G LL!#67}}	H ;;   MM$3	

 	!!#==?r   c                   | j                  dg       }|s$t        dt        j                  |       dd        |d   j                  di       j                  dg       }|D ]>  }d|v s|d   j                  d	d
      }|d   d   }t	        j
                  |      }||fc S  |D cg c]  }d|v s|j                  dd       }}|rt        j                  d|dd        t        d|dd        c c}w )u  Gemini API 응답에서 이미지 데이터와 MIME 타입을 추출합니다.

    Args:
        response_data: Gemini API 응답 JSON.

    Returns:
        (이미지 바이트, mime_type) 튜플.

    Raises:
        ValueError: 이미지 데이터가 응답에 없는 경우.
    
candidatesu.   응답에 candidates가 없습니다. 응답: Nr   r   contentr2   
inlineDatamimeTypez
image/jpegr.   r3   r   u0   이미지 데이터 없음. 텍스트 응답: %s   uA   이미지 데이터가 응답에 없습니다. 텍스트 파트: )get
ValueErrorr)   dumpsbase64	b64decoder   warning)	response_datarN   r2   part	mime_type	image_b64image_bytesp
text_partss	            r   parse_image_from_responser`      s(    ""<4J<TZZ=VW[X[=\<]^
 	
 qMi,00"=E  *4!,/33JMI!,/7I **95K	))* .3Bfk!%%#BJBI:VXWX>Z

KJWYXYNK[\  Cs   %	C1/C1c                F   d|v rdnd}|j                   j                         |k7  r|j                  |      }|j                  j	                  dd       |j                  |        t        j                  d|j                  |j                         j                  d|       |S )uh  이미지 바이트를 MIME 타입에 맞는 확장자로 저장합니다.

    Args:
        image_bytes: 저장할 이미지 바이트 데이터.
        mime_type: 이미지 MIME 타입 (예: image/jpeg, image/png).
        output_path: 저장 경로 (확장자는 MIME 타입에 따라 보정됨).

    Returns:
        실제 저장된 파일 경로.
    jpegz.jpg.pngTparentsexist_oku/   이미지 저장 완료: %s (%s bytes, mime=%s),)suffixlowerwith_suffixparentmkdirwrite_bytesr   r   namestatst_size)r]   r[   output_pathexts       r   
save_imagers      s     i'&VC!S(!--c2TD9K(
KK9%%a
(	 r   c           
        |j                   }|j                  xs d}|j                  | d| |       }|t        |      ddddd}t	        j                         }	 t        | |      }	t        |	      \  }
}t        |
||      }t	        j                         |z
  }|j                  |t        |      |j                         j                  t        |d      |ddd       |S # t        j                  $ r}t	        j                         |z
  }d	|j                  j                   d
|j                  j                   dd  }t"        j%                  d||       t        |d      |d<   ||d<   Y d}~|S d}~wt&        $ rV}t	        j                         |z
  }t"        j%                  d||       t        |d      |d<   t        |      |d<   Y d}~|S d}~wt(        $ rg}t	        j                         |z
  }t+        |      j,                   d
| }t"        j%                  d||       t        |d      |d<   ||d<   Y d}~|S d}~ww xY w)u  단일 attempt에 대해 이미지를 생성하고 저장합니다.

    파일명 규칙: `{stem}_attempt{attempt}{ext}`

    Args:
        token: Bearer 인증 토큰.
        prompt: 이미지 생성 프롬프트.
        base_output_path: 기본 출력 경로 (확장자 포함).
        attempt: 현재 시도 번호 (1부터 시작).

    Returns:
        생성 결과 딕셔너리 (success, file_path, file_size, elapsed, error 등).
    rc   _attemptNF)attempt	file_path	file_sizeelapsed_secondssuccessr&   rR   T)rv   rw   rx   ry   r[   rz   r&   zHTTP z: r?   u   Attempt %d HTTP 오류: %sry   r&   u&   Attempt %d 이미지 파싱 오류: %su&   Attempt %d 예기치 않은 오류: %s)stemrh   	with_namestrtimerL   r`   rs   updatero   rp   roundrB   	HTTPErrorrK   rF   r3   r   r&   rT   	Exceptiontype__name__)rH   rI   base_output_pathrv   r{   rh   attempt_pathresult
start_timerY   r]   r[   
saved_pathelapsedr/   	error_msgs                   r   generate_single_imager      s4   &   D$$.F#--hwix.PQL &F J#$'v6!:=!IYYE
))+
*" _'__.66#(!#4&
	
> M)  $))+
*AJJ**+2ajjoods.C-DE 	 	17IF$)'1$5 !#w M  !))+
*=wJ$)'1$5 !a&w M  $))+
*Aw''(1#.	=w	R$)'1$5 !#wM$s4   BC   H>3A5E..H>:AGH>AH99H>c                ,   |j                  dd      }|j                  dd      }|j                  dd      }|j                  dd| d	| d
      }t        |   }	|	|z  }
|dd }t        j                  d||||       ||||t	        |
      g dddddt        j                         j                         dd}|r<t        d| d| d|        t        d|
        t        d|        d|d<   d|d<   |S |	j                  dd       t        j                         }d}t        d|dz         D ]  }t        j                  d||||       t        | ||
|      }|d   j                  |       |d   r2t        |d         }t        j                  d||||j                         nt        j!                  d ||||d          ||k  st        j#                  d!t$               t        j&                  t$                t        j                         |z
  }||d"<   t)        |d#      |d$<   ||	|z  }|j*                  |j*                  k7  r|j-                  |j*                        }t/        j0                  ||       t        j                  d%|||j                  |j                         d|d<   t	        |      |d&<   |j3                         j4                  |d'<   d|d<   |S |d   r|d   d(   d   nd)}d|d<   d*| d+| |d<   t        j7                  d,|||d          |S )-u  슬라이드 하나에 대해 최대 max_retries번 이미지를 생성합니다.

    성공한 마지막 attempt 파일을 기본 파일명(output_file)으로도 복사합니다.

    Args:
        token: Bearer 인증 토큰.
        group_key: 프롬프트 그룹 키 (예: meta_carousel_a).
        slide_config: 슬라이드 설정 딕셔너리.
        max_retries: 최대 재시도 횟수.
        dry_run: True이면 API 호출 없이 프롬프트만 출력.

    Returns:
        슬라이드 전체 결과 딕셔너리.
    slide?rn   unknownrI   r   output_fileslide__rc   N2   u=   [%s] 슬라이드 %s (%s) 처리 시작 | 프롬프트: %s...r   F)groupr   rn   prompt_summaryr   attemptstotal_attemptsrz   final_file_pathfinal_file_sizetotal_elapsed_seconds	timestampr&   u   
[DRY-RUN] 그룹: u    | 슬라이드: u    | 이름: u     출력 경로: u     프롬프트: Trz   u   dry-run (API 호출 생략)r&   rd   r!   u)   [%s] 슬라이드 %s attempt %d/%d 시작r   rw   u*   [%s] 슬라이드 %s attempt %d 성공: %su*   [%s] 슬라이드 %s attempt %d 실패: %su"   다음 attempt 전 %d초 대기...r   rR   r   u4   [%s] 슬라이드 %s 최종 파일 복사: %s → %sr   r   u   알 수 없는 오류u   모든 u%   회 시도 실패. 마지막 오류: u-   [%s] 슬라이드 %s 모든 시도 실패: %s)rS   r   r   r   r}   r   now	isoformatprintrl   r~   ranger   appendr   rn   rX   r@   INTER_IMAGE_DELAYsleepr   rh   rj   shutilcopy2ro   rp   r&   )rH   	group_keyslide_configmax_retriesdry_run	slide_numrn   rI   r   
output_dirr   r   slide_resulttotal_startlast_success_pathrv   attempt_resulttotal_elapsed
final_path
last_errors                       r   process_slider   6  s   *   #.IFI.D""8R0F#''	{!D6QU7VWKY'J!K/CR[N
KKG (+,!%\\^--/$L  $YK/@;W[V\]^!"2!345 )*"&Y =WTD1))+K%)K!O, !*7	
 /uf>NPWXZ ''7)$ $^K%@ AKK<!&& NN<w' [ LL=?PQJJ()C!*F IIK+-M%0L!",1-,CL()$+-
##z'8'88#//0A0H0HIJ&
3B""OO	
 #'Y*-j/&'*4//*;*C*C&' $W  ?K:>V\*-b1':\s
"'Y")+6[\f[g hW;!		
 r   c           	        t         j                  j                  dd       t        d | D              }t	        |       |z
  }t        j                         j                         t        t	        |       ||t        |d      | d}t        t         dd      5 }t        j                  ||d	d
       ddd       t        j                  dt                y# 1 sw Y   $xY w)u   생성 결과를 generation_results.json에 저장합니다.

    Args:
        results: 슬라이드별 결과 리스트.
        total_elapsed: 전체 소요 시간(초).
    Trd   c              3  D   K   | ]  }|j                  d       sd  ywrz   r!   N)rS   ).0rs     r   	<genexpr>zsave_results.<locals>.<genexpr>  s     ?aaeeI.>?s     rR   )run_timestampmodeltotal_slidessuccess_count
fail_countr   resultswr"   r#   F)ensure_asciiindentNu   결과 저장 완료: %s)RESULTS_JSONrk   rl   sumlenr   r   r   MODEL_IDr   r(   r)   dumpr   r   )r   r   r   r   output_datar-   s         r   save_resultsr     s     dT:?7??MW-J "113G& !&}a!8#K 
lC'	2 @a		+quQ?@ KK*L9@ @s   CCc                     t        j                  dt         j                  d      } | j                  dg ddd       | j                  d	t        d
dd       | j                  ddd       | j                         S )u^   CLI 인자를 파싱합니다.

    Returns:
        파싱된 argparse.Namespace 객체.
    u5   Gemini API v3 캠페인 이미지 생성 스크립트ub  
예시:
  python v3_generate.py                    # 전체 생성
  python v3_generate.py --group a          # meta_carousel_a만 생성
  python v3_generate.py --group gfa        # naver_gfa만 생성
  python v3_generate.py --max-retries 5    # 최대 5회 재시도
  python v3_generate.py --dry-run          # API 호출 없이 프롬프트 확인
)descriptionformatter_classepilogz--groupr   r   u%   생성할 그룹 선택 (기본: all))choicesdefaulthelpz--max-retries   Nu0   이미지당 최대 재시도 횟수 (기본: 3))r   r   metavarr   z	--dry-run
store_trueu3   API 호출 없이 프롬프트 및 설정만 출력)actionr   )argparseArgumentParserRawDescriptionHelpFormatteradd_argumentint
parse_args)parsers    r   r   r     s     $$K <<F (4	   ?   B  
 r   c                 j   t               t        d       t        d       t        dt                t        dj                          t        dj                   d       t        dj
                          t        dt                t        d       t        t              } t        j                     }t        j                  d	|       d
}j
                  s
t               }g }t        j                         }d}|D ]  }|| vrt        j                  d|       | |   }t        j                  d|t        |             |D ]  }|dkD  r?j
                  s3t        j!                  dt"               t        j$                  t"               t'        |||j                  j
                        }	|j)                  |	       |dz  }  t        j                         |z
  }
j
                  st+        ||
       t        d       t        d       t        d       t-        fd|D              }j
                  st        |      |z
  nd}t        dt        |              j
                  s3t        d| d|        t        d|
dd       t        dt.                |D ]  }j
                  r|j1                  d      rdnd}|j1                  dd
      }|j1                  dd       }|j1                  d!d
      }t        |j1                  d"g             }|j1                  d#      }|j1                  d$      }|r|d%d&nd'}|r|ddnd'}|j1                  d(      rd)|d(    nd
}t        d*| d+| d,| d-| d.| d/| d)| |         j
                  rt        d0       t        d1       y2y2)3u   메인 실행 함수.zF======================================================================u$   v3 캠페인 이미지 생성 시작u   모델: u   그룹: u   최대 재시도: u   회z	Dry-run: u   출력 디렉토리: u   처리 대상 그룹: %sr   r   uD   프롬프트 파일에 그룹 '%s'가 없습니다. 건너뜁니다.u/   
[%s] 그룹 처리 시작 (%d개 슬라이드)u&   rate limit 방지: %d초 대기 중...)rH   r   r   r   r   r!   zG
======================================================================u   생성 결과 요약c              3  `   K   | ]%  }|j                  d       sj                  r"d ' ywr   )rS   r   )r   r   argss     r   r   zmain.<locals>.<genexpr>Q  s"     Xa!%%	2B4<<Xs   ...u   총 슬라이드: u   성공: u    / 실패: u   총 소요 시간: z.1fu   초u   결과 파일: rz   OKFAILr   r   r   rn   r   r   r   rg   z byteszN/Ar&   z | z  [z] z/slide_r   u    | 시도: u   회 | uM   
[DRY-RUN 완료] 위 프롬프트로 이미지가 생성될 예정입니다.u?   실제 생성하려면 --dry-run 옵션 없이 실행하세요.N)r   r   r   r   r   r   OUTPUT_BASEr0   PROMPTS_FILEr   r   r   r   r~   rX   r   r@   r   r   r   r   r   r   r   rS   )promptstarget_groupsrH   all_resultsr   slide_indexr   slidesr   r   r   r   r   r   statusr   r   rn   r   sizer   size_strtime_str	error_strr   s                           @r   mainr     s   <D	(O	
01	HXJ
 	HTZZL
!"	t//0
45	Idll^
$%	!+
/0	(O <(G djj)M
KK*M: E<< (*K))+KK" 	G#NNaclm#>	3v;	
 # 	LQt||EGXY

,-(#) ,,L |,1K	4 IIK+-M <<[-0 
/	
 !	(OX;XXM9=[!M11J	s;/0
12<<{:,?@#M##6c:;~./ 
<<y)vgr"gs#uuVR quuZ,-uu&'%%/0(,d1XV$%*1gc]#&u*+%%.c!G*&b	&E7'%$ 8jxjH:i[J	

$ ||^_OP r   __main__)returnr}   )r,   r   r   zdict[str, list[dict[str, Any]]])rH   r}   rI   r}   r   dict[str, Any])rY   r   r   ztuple[bytes, str])r]   bytesr[   r}   rq   r   r   r   )
rH   r}   rI   r}   r   r   rv   r   r   r   )rH   r}   r   r}   r   r   r   r   r   boolr   r   )r   zlist[dict[str, Any]]r   floatr   None)r   zargparse.Namespace)r   r   )*__doc__
__future__r   r   rV   r)   loggingr   sysr~   r   pathlibr   typingr   rB   r   GEMINI_API_KEYrA   r   r   r   __annotations__r   r   rD   r   basicConfigINFO	getLoggerr   r   r0   rL   r`   rs   r   r   r   r   r   r   r   r   r   <module>r     s3   #      
     
 (:>j%n%57 
 DEFG"%66"%66{* _  66 ?
	
	=	#	   
   
,,?
 
		=	)#:... .h$!$$T  
	BFFF F 	F
 FXHHH !H 	H
 H H\:!:: 
:D$T]Q@ zF r   