o
    |iQ                     @   s   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mZ ddlZddl	Z	ddl
mZmZmZmZ ddlmZ ddlZddlmZ dejd	< G d
d dZdd Zedkr[e  dS dS )u   
YouTube 동영상을 AI 기반으로 스마트 요약하는 프로그램
- Whisper로 음성 인식
- 중요 구간 자동 추출
- ChatGPT API로 요약 생성
Author: 최만규
    N)Path)datetime)VideoFileClipconcatenate_videoclipsTextClipCompositeVideoClip)OpenAI)CounterdummySDL_AUDIODRIVERc                   @   sz   e Zd ZdZdddZdddZd	d
 Zd ddZdd Zd!ddZ	d"ddZ
dd Zdd Zdd Z			d#ddZdS )$SmartShortsConverteru.   AI 기반 스마트 동영상 요약 클래스./smart_shorts_outputNc                 C   sp   t || _| jjdd | jd | _| jjdd |ptd| _| jr,t| jd| _nt	d d| _d| _
dS )u   
        초기화
        
        Args:
            output_dir (str): 출력 디렉토리
            openai_api_key (str): OpenAI API 키
        T)exist_oktempOPENAI_API_KEY)api_keyud   ⚠️  OpenAI API 키가 설정되지 않았습니다. 요약 기능은 사용할 수 없습니다.N)r   
output_dirmkdirtemp_dirosgetenvopenai_api_keyr   clientprintwhisper_model)selfr   r    r   2/var/www/html/bicorn/ndegree/smart_shorts_maker.py__init__   s   

zSmartShortsConverter.__init__basec                 C   s4   | j du rtd| d t|| _ td | j S )u   
        Whisper 모델 로드
        
        Args:
            model_size (str): 모델 크기 (tiny, base, small, medium, large)
        Nu    🤖 Whisper 모델 로딩 중 (z)...u    ✅ Whisper 모델 로드 완료)r   r   whisper
load_model)r   
model_sizer   r   r   load_whisper_model7   s
   
z'SmartShortsConverter.load_whisper_modelc                 C   sr  t d|  dt| jd ddddddgdd	d
idddgddgdid	}z{t|k}t d |j|dd}|d }|d }g d}d}|D ]}	| j| d|	  }
|
 r]|
} nqI|srt| jd}|rrt	|dd d}|rx| s|t
dt d|  t d|  t||fW  d   W S 1 sw   Y  W dS  t
y } z
t d t|   d}~ww )!u    유튜브 동영상 다운로드u+   📥 유튜브 동영상 다운로드 중: zbestvideo[ext=mp4][height<=1080]+bestaudio[ext=m4a]/best[ext=mp4][height<=1080]/bestvideo[ext=mp4]+bestaudio/best[ext=mp4]/bestvideo+bestaudio/bestz%(id)s.%(ext)sFmp4FFmpegVideoConvertor)keypreferedformatTz
User-Agentz<Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36youtubeandroidwebhlsdash)player_clientskip)	formatouttmplquietno_warningsmerge_output_formatpostprocessorsnocheckcertificatehttp_headersextractor_argsu#   🔍 동영상 정보 추출 중...)downloadidtitle)r$   webmmkvN.*c                 S   s
   |   jS )N)statst_mtimexr   r   r   <lambda>w   s   
 z=SmartShortsConverter.download_youtube_video.<locals>.<lambda>r&   u2   다운로드된 파일을 찾을 수 없습니다.u   ✅ 다운로드 완료: u   📁 파일: u   ❌ 다운로드 실패: )r   strr   yt_dlp	YoutubeDLextract_infoexistslistglobmax	Exception)r   urlydl_optsydlinfovideo_idvideo_titlepossible_extensionsdownloaded_fileext	candidatefileser   r   r   download_youtube_videoD   s^   
(z+SmartShortsConverter.download_youtube_videokoc                 C   s   t d | d}t d |j||ddd}t dt|d  d	 | jd
 }t|ddd}tj||ddd W d   n1 sBw   Y  t d|  |S )u"  
        동영상 음성을 텍스트로 변환 (Whisper)
        
        Args:
            video_path (str): 동영상 파일 경로
            language (str): 언어 코드 (ko, en 등)
            
        Returns:
            dict: 타임스탬프가 포함된 전사 결과
        u+   
🎙️  음성 인식 시작 (Whisper)...r   u6   📝 전사 중... (시간이 걸릴 수 있습니다)
transcribeF)languagetaskverboseu   ✅ 전사 완료: segments   개 세그먼트ztranscript.jsonwutf-8encoding   )ensure_asciiindentNu   💾 전사 내용 저장: )r   r#   r\   lenr   openjsondump)r   
video_pathr]   modelresulttranscript_filefr   r   r   transcribe_video   s    

z%SmartShortsConverter.transcribe_videoc              	      s"  t d |d }g }g d}|D ]\}|d   |d }|d }|| }	d}
t fdd	|D }|
|d
 7 }
dt   k rCdk rIn n|
d7 }
|	d
krQ|
d7 }
|| }|dk s]|dkra|
d7 }
||| |
|	d q|jdd dd t dt| d t d|r|d d nd  |S )u8  
        전사 내용을 분석하여 중요한 구간 추출
        
        Args:
            transcription (dict): Whisper 전사 결과
            video_duration (float): 동영상 총 길이
            
        Returns:
            list: 중요 구간 리스트 [(start, end, text, score), ...]
        u!   
🧠 중요 구간 분석 중...r`   )!u   중요u   핵심u	   포인트u   주목u   강조u   정리u   요약u   결론u   첫째u   둘째u   셋째u   먼저u   다음u	   마지막u   이것u   이거u   바로u   특히u   예를 들어u   예시u   문제u   해결u   방법u   기법u   원리u   개념u   정의u   질문u   답u   이유u   왜냐하면u	   그래서u	   따라서textstartendr   c                 3   s    | ]	}| v rd V  qdS )   Nr   ).0kwrs   r   r   	<genexpr>   s    zBSmartShortsConverter.analyze_important_segments.<locals>.<genexpr>   
      rf   rv   g??)rt   ru   rs   scoredurationc                 S      | d S )Nr   r   rA   r   r   r   rC          zASmartShortsConverter.analyze_important_segments.<locals>.<lambda>T)r&   reverseu   📊 분석 완료: 총 ra   u      상위 점수: r   )r   stripsumri   appendsort)r   transcriptionvideo_durationr`   scored_segmentsimportant_keywordssegrt   ru   r   r   keyword_countposition_ratior   ry   r   analyze_important_segments   s<   
	z/SmartShortsConverter.analyze_important_segments<   c                 C   s   t d| d g }d}|D ]}||d  |kr#|| ||d 7 }||d kr+ nq|jdd d t d	t| d
|dd |S )u!  
        목표 시간에 맞게 세그먼트 선택
        
        Args:
            scored_segments (list): 점수가 매겨진 세그먼트
            target_duration (int): 목표 시간 (초)
            
        Returns:
            list: 선택된 세그먼트 리스트
        u   
🎯 u   초 분량 선택 중...r   r   r~   c                 S   r   )Nrt   r   rA   r   r   r   rC     r   zJSmartShortsConverter.select_segments_for_target_duration.<locals>.<lambda>rD   u   ✅ u   개 구간 선택됨 (총 .1fu   초))r   r   r   ri   )r   r   target_durationselectedtotal_durationr   r   r   r   #select_segments_for_target_duration   s   
z8SmartShortsConverter.select_segments_for_target_durationFc                 C   s.  t d t|}g }t|D ]\}}t d|d  dt| d|d dd|d	 dd
	 ||d |d	 }|r|d rz7t|d }	tdtdd|	d d  }
t|d dd |
dddddddjddd	|j
}t||g}W n ty } zt dt|  W Y d}~nd}~ww || qt d t|dd}t d  |j\}}d!}d"}|| }||| kr|j|d#}n|j|d$}|j\}}|j|d |d ||d%}t d&}d'| d(}| j| }t d)|j  |jt|d*d+dd,d-d.dd/ |  |  |  |  t d0 t|S )1uB  
        선택된 구간으로 쇼츠 동영상 생성
        
        Args:
            video_path (str): 원본 동영상 경로
            selected_segments (list): 선택된 구간
            add_subtitles (bool): 자막 추가 여부
            
        Returns:
            str: 생성된 쇼츠 경로
        u$   
🎬 쇼츠 동영상 생성 중...u	     구간 rv   /z: rt   r   s ~ ru   srs      2   i,  r|   Nd   whiteArialblackrf   caption)i  N)fontsizecolorfontstroke_colorstroke_widthmethodsize)centerg333333?T)relativeu"       ⚠️  자막 추가 실패: u   
🔗 클립 연결 중...compose)r   u&   📐 쇼츠 포맷으로 변환 중...i8  i  )height)width)x_centery_centerr   r   z%Y%m%d_%H%M%Ssmart_shorts_.mp4u   
💾 인코딩 중: libx264aacmedium5000kF)codecaudio_codecfpspresetbitrater_   loggeru   ✅ 쇼츠 생성 완료!)r   r   	enumerateri   subcliprL   minr   set_positionset_durationr   r   rM   rE   r   r   r   resizecropr   nowstrftimer   namewrite_videofileclose)r   rm   selected_segmentsadd_subtitlesvideoclipsir   cliptext_lengthr   subtitlerY   final_videooriginal_widthoriginal_heightshorts_widthshorts_heightoriginal_ratioresizedresized_widthresized_heightcropped	timestampoutput_filenameoutput_pathr   r   r   create_smart_shorts  s   6		


z(SmartShortsConverter.create_smart_shortsc              
   C   s   | j s	td dS td ddd |d D }t|dkr'|d	d d
 }z.| j jjjdddddd| dgddd}|jd jj	
 }td td|  |W S  tyw } ztdt|  dt| W  Y d	}~S d	}~ww )u   
        ChatGPT API로 동영상 내용 요약
        
        Args:
            transcription (dict): Whisper 전사 결과
            
        Returns:
            str: 1-2줄 요약
        uF   ⚠️  OpenAI API 키가 없어 요약을 생성할 수 없습니다.u%   요약 생성 불가 (API 키 필요)u%   
🤖 ChatGPT로 요약 생성 중... c                 S   s   g | ]}|d  qS ry   r   )rw   r   r   r   r   
<listcomp>  s    zBSmartShortsConverter.generate_summary_with_gpt.<locals>.<listcomp>r`   i  Nz...zgpt-4o-minisystemu   당신은 강의 동영상을 요약하는 전문가입니다. 주어진 전사 내용을 1-2줄로 핵심만 간결하게 요약해주세요.)rolecontentuseru7   다음 강의 내용을 1-2줄로 요약해주세요:

r}   gffffff?)rn   messages
max_tokenstemperaturer   u   ✅ 요약 생성 완료   📝 요약: u   ❌ 요약 생성 실패: u   요약 생성 실패: )r   r   joinri   chatcompletionscreatechoicesmessager   r   rM   rE   )r   r   	full_textresponsesummaryrY   r   r   r   generate_summary_with_gptn  s:   


z.SmartShortsConverter.generate_summary_with_gptc                 C   s  t |d}t|dddg}|d |d |d |d || d	 |d |d
t| d |d t|dD ](\}}|| d|d dd|d dd|d  d |d|d  d	 qEW d   n1 sxw   Y  td|  t|S )u   
        요약 정보를 텍스트 파일로 저장
        
        Args:
            summary (str): ChatGPT 요약
            selected_segments (list): 선택된 구간
            output_path (str): 쇼츠 파일 경로
        z.txtrb   rc   rd   z=============================================================
u$   📹 스마트 쇼츠 요약 정보
z>============================================================

u   📝 AI 요약 (ChatGPT):
z

u   📊 포함된 구간 (u   개):
rv   z. [rt   r   r   ru   u   s] (점수: r   z)
z   rs   Nu   💾 요약 파일 저장: )r   with_suffixrj   writeri   r   r   rE   )r   r   r   r   summary_filerq   r   r   r   r   r   save_summary_file  s"   	





2z&SmartShortsConverter.save_summary_filec              
   C   s|   t d z| jdD ]}|jdv r|  t d|j  qW dS  ty= } zt dt|  W Y d}~dS d}~ww )u   임시 파일 정리u!   
🧹 임시 파일 정리 중...r>   )r   z.webmz.mkvu
     삭제: u   ⚠️  정리 중 오류: N)r   r   rK   suffixunlinkr   rM   rE   )r   filerY   r   r   r   cleanup_temp_files  s   
 z'SmartShortsConverter.cleanup_temp_filesc              
   C   s  t d t d t d zd| |\}}| j||d}t|}	|	j}
|	  | ||
}| ||}| |||}| 	|}| 
|||}|sK|   t d t d t d t d|  t d|  t d|  ||fW S  ty } z
t d	t|   d
}~ww )u  
        유튜브 URL을 AI 기반 스마트 쇼츠로 변환 (전체 프로세스)
        
        Args:
            youtube_url (str): 유튜브 URL
            target_duration (int): 목표 길이 (초)
            add_subtitles (bool): 자막 추가 여부
            keep_temp (bool): 임시 파일 유지 여부
            language (str): 음성 언어 (ko, en 등)
            
        Returns:
            tuple: (쇼츠 경로, 요약 텍스트)
        z<============================================================u)   🧠 AI 스마트 쇼츠 메이커 시작)r]   z=
============================================================u   🎉 모든 작업 완료!u   📹 쇼츠: r   u   💬 한줄 요약: u   
❌ 작업 실패: N)r   rZ   rr   r   r   r   r   r   r   r   r   r   rM   rE   )r   youtube_urlr   r   	keep_tempr]   rm   rS   r   r   r   r   r   shorts_pathr   r   rY   r   r   r   convert_url_to_smart_shorts  s@   

z0SmartShortsConverter.convert_url_to_smart_shorts)r   N)r   )r[   )r   )F)r   FFr[   )__name__
__module____qualname____doc__r   r#   rZ   rr   r   r   r   r   r   r   r   r   r   r   r   r      s     


A"
E
c2r   c               
   C   s  t jdt jdd} | jddd | jddtd	d
d | jddddd | jddd | jdddd | jdddd | jdddd |  }t|j|jd}z|j	|j
|j|j|j|jd\}}td W d!S  ty } ztdt|  td  W Y d!}~d!S d!}~ww )"u   메인 함수u8   AI 기반 스마트 쇼츠 메이커 - Whisper + ChatGPTu   
사용 예시:
  python smart_shorts_maker.py "URL" --api-key "sk-..."
  python smart_shorts_maker.py "URL" -d 60 --subtitles
  
환경 변수:
  export OPENAI_API_KEY="sk-..."
        )descriptionformatter_classepilogrN   u   유튜브 URL)helpz-dz
--durationr   u"   목표 길이 (초, 기본값: 60))typedefaultr  z-oz--outputr   u   출력 디렉토리)r  r  z	--api-keyu   OpenAI API 키z--subtitles
store_trueu   자막 추가)actionr  z--keep-tempu   임시 파일 유지z
--languager[   u   음성 언어 (기본값: ko))r   r   )r   r   r   r   r]   u   
✨ 완료!u   
💥 오류: rv   N)argparseArgumentParserRawDescriptionHelpFormatteradd_argumentint
parse_argsr   outputr   r   rN   r   	subtitlesr   r]   r   rM   rE   sysexit)parserargs	converterr   r   rY   r   r   r   main  sR   

r  __main__)r   r   r  r  rk   pathlibr   r   rF   r    moviepy.editorr   r   r   r   openair   numpynpcollectionsr	   environr   r  r   r   r   r   r   <module>   s,   
   u4
