Spaces:
Running
Running
| #!/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**를 사용하여 텍스트나 이미지로부터 비디오를 생성합니다. | |
| [](https://replicate.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 | |
| ) |