#!/usr/bin/env python3 """ AI Video Generator with Gradio Single file application - app.py """ import os import gradio as gr import replicate import base64 from PIL import Image import io import requests from datetime import datetime import tempfile # API 토큰 설정 api_token = os.getenv("RAPI_TOKEN") if api_token: os.environ["REPLICATE_API_TOKEN"] = api_token # 화면 비율 옵션 ASPECT_RATIOS = { "16:9": "16:9 (YouTube, 일반 동영상)", "4:3": "4:3 (전통적인 TV 형식)", "1:1": "1:1 (Instagram 피드)", "3:4": "3:4 (Instagram 포트레이트)", "9:16": "9:16 (Instagram 릴스, TikTok)", "21:9": "21:9 (시네마틱 와이드)", "9:21": "9:21 (울트라 세로형)" } def update_prompt_placeholder(mode): """모드에 따라 프롬프트 플레이스홀더 업데이트""" if mode == "텍스트 to 비디오": return gr.update(placeholder="생성할 비디오를 설명해주세요.\n예: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn.") else: return gr.update(placeholder="이미지를 어떻게 움직이게 할지 설명해주세요.\n예: Camera slowly zooms in while clouds move across the sky. The subject's hair gently moves in the wind.") def update_image_input(mode): """모드에 따라 이미지 입력 표시/숨김""" if mode == "이미지 to 비디오": return gr.update(visible=True) else: return gr.update(visible=False) def generate_video(mode, prompt, image, aspect_ratio, seed, api_key_input, progress=gr.Progress()): """비디오 생성 메인 함수""" # API 토큰 확인 token = api_key_input or api_token if not token: return None, "❌ API 토큰이 필요합니다. 환경변수 RAPI_TOKEN을 설정하거나 API 키를 입력하세요." os.environ["REPLICATE_API_TOKEN"] = token # 입력 검증 if not prompt: return None, "❌ 프롬프트를 입력해주세요." if mode == "이미지 to 비디오" and image is None: return None, "❌ 이미지를 업로드해주세요." try: progress(0, desc="비디오 생성 준비 중...") # 입력 파라미터 설정 input_params = { "prompt": prompt, "duration": 5, "resolution": "480p", "aspect_ratio": aspect_ratio, "seed": seed } # 이미지 to 비디오 모드 if mode == "이미지 to 비디오" and image is not None: progress(0.1, desc="이미지 처리 중...") # PIL Image를 base64로 변환 if isinstance(image, str): # 파일 경로인 경우 with Image.open(image) as img: buffered = io.BytesIO() img.save(buffered, format="PNG") image_base64 = base64.b64encode(buffered.getvalue()).decode() else: # PIL Image 객체인 경우 buffered = io.BytesIO() image.save(buffered, format="PNG") image_base64 = base64.b64encode(buffered.getvalue()).decode() input_params["image"] = f"data:image/png;base64,{image_base64}" progress(0.3, desc="Replicate API 호출 중...") # Replicate 실행 output = replicate.run( "bytedance/seedance-1-lite", input=input_params ) progress(0.7, desc="비디오 다운로드 중...") # 비디오 데이터 가져오기 if hasattr(output, 'read'): video_data = output.read() else: # URL인 경우 다운로드 response = requests.get(output) video_data = response.content # 임시 파일로 저장 with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file: tmp_file.write(video_data) video_path = tmp_file.name # output.mp4로도 저장 with open("output.mp4", "wb") as file: file.write(video_data) progress(1.0, desc="완료!") # 생성 정보 info = f"""✅ 비디오가 성공적으로 생성되었습니다! 📊 생성 정보: - 모드: {mode} - 화면 비율: {aspect_ratio} - Seed: {seed} - 재생 시간: 5초 - 해상도: 480p - 파일: output.mp4""" return video_path, info except Exception as e: error_msg = f"❌ 오류가 발생했습니다: {str(e)}" return None, error_msg # Gradio 인터페이스 생성 with gr.Blocks(title="AI Video Generator", theme=gr.themes.Soft()) as app: gr.Markdown(""" # 🎬 AI Video Generator **Replicate API**를 사용하여 텍스트나 이미지로부터 비디오를 생성합니다. [![Powered by Ginigen](https://img.shields.io/badge/Powered%20by-Replicate-blue)](https://ginigen.com/) """) with gr.Row(): with gr.Column(scale=1): # API 설정 with gr.Accordion("⚙️ API 설정", open=not bool(api_token)): if api_token: gr.Markdown("✅ API 토큰이 환경변수에서 로드되었습니다.") api_key_input = gr.Textbox( label="Replicate API Token (선택사항)", type="password", placeholder="환경변수를 덮어쓰려면 여기에 입력", value="" ) else: gr.Markdown("⚠️ 환경변수 RAPI_TOKEN이 설정되지 않았습니다.") api_key_input = gr.Textbox( label="Replicate API Token (필수)", type="password", placeholder="Replicate API 토큰을 입력하세요", value="" ) # 생성 모드 mode = gr.Radio( label="🎯 생성 모드", choices=["텍스트 to 비디오", "이미지 to 비디오"], value="텍스트 to 비디오" ) # 이미지 업로드 image_input = gr.Image( label="📷 이미지 업로드", type="pil", visible=False ) # 화면 비율 aspect_ratio = gr.Dropdown( label="📐 화면 비율", choices=list(ASPECT_RATIOS.keys()), value="16:9", info="SNS 플랫폼에 최적화된 비율을 선택하세요" ) # 비율 설명 표시 ratio_info = gr.Markdown(value=f"선택된 비율: {ASPECT_RATIOS['16:9']}") # Seed 설정 seed = gr.Number( label="🎲 랜덤 시드", value=42, precision=0, info="동일한 시드값으로 동일한 결과를 재현할 수 있습니다" ) # 고정 설정 표시 gr.Markdown(""" ### 📋 고정 설정 - **재생 시간**: 5초 - **해상도**: 480p """) with gr.Column(scale=2): # 프롬프트 입력 prompt = gr.Textbox( label="✍️ 프롬프트", lines=5, placeholder="생성할 비디오를 설명해주세요.\n예: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn." ) # 생성 버튼 generate_btn = gr.Button("🎬 비디오 생성", variant="primary", size="lg") # 결과 표시 with gr.Column(): output_video = gr.Video( label="📹 생성된 비디오", autoplay=True ) output_info = gr.Textbox( label="정보", lines=8, interactive=False ) # 사용 방법 with gr.Accordion("📖 사용 방법", open=False): gr.Markdown(""" ### 설치 방법 1. **필요한 패키지 설치**: ```bash pip install gradio replicate pillow requests ``` 2. **환경변수 설정** (선택사항): ```bash export RAPI_TOKEN="your-replicate-api-token" ``` 3. **실행**: ```bash python app.py ``` ### 기능 설명 - **텍스트 to 비디오**: 텍스트 설명만으로 비디오를 생성합니다. - **이미지 to 비디오**: 업로드한 이미지를 움직이는 비디오로 변환합니다. - **화면 비율**: 다양한 SNS 플랫폼에 최적화된 비율을 선택할 수 있습니다. - **Seed 값**: 동일한 시드값으로 동일한 결과를 재현할 수 있습니다. ### 프롬프트 작성 팁 - 구체적이고 상세한 설명을 사용하세요 - 카메라 움직임을 명시하세요 (예: zoom in, pan left, tracking shot) - 조명과 분위기를 설명하세요 (예: golden hour, dramatic lighting) - 움직임의 속도를 지정하세요 (예: slowly, rapidly, gently) """) # 예시 gr.Examples( examples=[ ["텍스트 to 비디오", "A serene lake at sunrise with mist rolling over the water. Camera slowly pans across the landscape as birds fly overhead.", None, "16:9", 42], ["텍스트 to 비디오", "Urban street scene at night with neon lights reflecting on wet pavement. People walking with umbrellas, camera tracking forward.", None, "9:16", 123], ["텍스트 to 비디오", "Close-up of a flower blooming in time-lapse, soft natural lighting, shallow depth of field.", None, "1:1", 789], ], inputs=[mode, prompt, image_input, aspect_ratio, seed], label="예시 프롬프트" ) # 이벤트 핸들러 mode.change( fn=update_prompt_placeholder, inputs=[mode], outputs=[prompt] ) mode.change( fn=update_image_input, inputs=[mode], outputs=[image_input] ) aspect_ratio.change( fn=lambda x: f"선택된 비율: {ASPECT_RATIOS[x]}", inputs=[aspect_ratio], outputs=[ratio_info] ) generate_btn.click( fn=generate_video, inputs=[mode, prompt, image_input, aspect_ratio, seed, api_key_input], outputs=[output_video, output_info] ) # 앱 실행 if __name__ == "__main__": app.launch( server_name="0.0.0.0", server_port=7860, share=False, inbrowser=True )