O2Sound_ver2_final/backend/app/infra/media/moviepy_service.py

115 lines
4.1 KiB
Python

from moviepy.editor import AudioFileClip, ImageClip, VideoFileClip, AudioFileClip, concatenate_videoclips
from moviepy.video.fx.fadein import fadein
from moviepy.video.fx.fadeout import fadeout
import os
from app.shared.logger import setup_logger
logger = setup_logger(__name__)
class MoviepyService:
def __init__(self):
self.save_dir = "./uploads/"
def create_video_from_existing_images(
self,
image_paths: list[str],
music_path: str = None,
output_filename: str = "final_video.mp4",
progress_id: str = None
) -> str:
try:
if not image_paths:
raise ValueError("image_paths가 비어 있습니다.")
os.makedirs(self.save_dir, exist_ok=True)
logger.info(f"[moviepy] {len(image_paths)}장의 이미지로 70초 영상 생성 시작")
# ✅ 영상 전체 길이 고정
total_duration = 70.0 # 1분 10초
image_duration = total_duration / len(image_paths)
fade_duration = min(1.0, image_duration * 0.15)
# ✅ 오디오 로드 (선택 사항)
audio = None
if music_path and os.path.exists(music_path):
try:
audio = AudioFileClip(music_path)
logger.info(f"[moviepy] 음악 로드 성공 (길이: {audio.duration:.2f}s)")
except Exception as e:
logger.warning(f"[moviepy] 음악 로드 실패 → 무음 처리됨: {e}")
audio = None
# ✅ 이미지 클립 생성
clips = []
for i, path in enumerate(image_paths):
clip = (
ImageClip(path)
.set_duration(image_duration)
.resize(height=1080)
.fx(fadein, fade_duration)
.fx(fadeout, fade_duration)
)
clips.append(clip)
logger.info(f"[moviepy] 이미지 클립 {i+1}/{len(image_paths)} 생성 완료")
if progress_id:
from app.shared.progress import set_progress
set_progress(progress_id, int(60 + (i + 1) / len(image_paths) * 30))
# ✅ 비디오 합치기
final_clip = concatenate_videoclips(clips, method="compose")
if audio:
final_clip = final_clip.set_audio(audio)
# ✅ 저장
output_path = os.path.join(self.save_dir, output_filename)
final_clip.write_videofile(
output_path,
fps=24,
codec="libx264",
audio_codec="aac" if audio else None,
threads=4,
verbose=False,
logger=None
)
logger.info(f"[moviepy] 비디오 생성 완료: {output_path}")
return output_path
except Exception as e:
logger.error(f"[moviepy] 비디오 생성 실패: {e}")
raise
def merge_video_and_audio(
self,
video_path: str,
audio_path: str,
output_filename: str = "final_merged_video.mp4"
) -> str:
try:
if not os.path.exists(video_path):
raise FileNotFoundError(f"비디오 파일이 존재하지 않습니다: {video_path}")
if not os.path.exists(audio_path):
raise FileNotFoundError(f"오디오 파일이 존재하지 않습니다: {audio_path}")
video = VideoFileClip(video_path)
audio = AudioFileClip(audio_path)
final_clip = video.set_audio(audio)
output_path = os.path.join(self.save_dir, output_filename)
final_clip.write_videofile(
output_path,
codec="libx264",
audio_codec="aac",
threads=4,
fps=24,
verbose=False,
logger=None
)
logger.info(f"[moviepy] 오디오 합성 완료: {output_path}")
return output_path
except Exception as e:
logger.error(f"[moviepy] 영상과 오디오 합성 실패: {e}")
raise