스터디/Python

Python Moviepy - 코딩으로 동영상 만들기

Dalmangyi 2023. 3. 27.

사운드, 이미지 프로세싱만 해보다가, 영상은 이번에 처음 만들어보는데 정말 재밌네요!

 

만드는 방법을 짧게 소개할까 합니다.

 

 

 

 

Moviepy개요

https://github.com/Zulko/moviepy

 

GitHub - Zulko/moviepy: Video editing with Python

Video editing with Python. Contribute to Zulko/moviepy development by creating an account on GitHub.

github.com

python의 moviepy 패키지를 이용하면 코딩으로 얼마든지 동영상을 만드실 수 있습니다!

moviepy는 정말이지 좋은 프로젝트이지만, 더이상 발전이 무의미해졌는지. 아주 3년전부터 패치가 멈춘 프로젝트 입니다.

 

 

영상편집을 위해서는 음성, 음성이펙트, 텍스트, 이미지, 영상, 영상 이펙트, 디코딩, 인코딩, 포맷등 다양한 지식이 필요합니다. moviepy도 그 모든것을 구현하긴 힘드니, 다른 프로젝트들을 python으로 사용할 수 있게 랩핑해준 프로젝트 입니다.

 

 

 

음성, 이미지, 영상은 아직도 현업에서 정말 많이 쓰는 갓라이브러리 FFmpeg을 이용하였고,

텍스트는 아주아주 오래된 고전 갓라이브러리 ImageMagick을 이용하였습니다.

https://imagemagick.org/index.php

거기다가 영상 이펙트들은 대부분 opencv를 이용하였습니다.

 

 

 

 

 

 

 

설치방법

설치방법은 윈도우와 맥은 별도 입니다.

자세한 설명은 다른 게시글이 많아서 줄이겠습니다.

 

 

*맥은 명령어로 모든 부분이 진행이 가능했고,

*윈도우는 명령어 불가해서 사이트에서 직접 다운받고 직접 설치하고 환경변수까지 설정해줘야 했습니다. (ffmpeg, imagemagick 등)

 

*주의할점은 시스템 폰트가 아닌 커스텀 폰트를 읽을때는 ImageMagick내부에 GhostScript로 만들어진 폰트 가이드파일을 업데이트해서 이용해야합니다.  다른 폰트가 안 나온다고 해매지 마세요.. (해맨건 저로 족해요..ㅠ)

 

*또 주의할점. mp3의 경우 라이센스 문제가 있는지, 별도로 설치해야 했습니다. 

 

 

 

 

 

 

개발하기전에

1. ffmpeg을 이용하지만, gpu 가속을 할수 없는 경우가 많아서 프로세싱 시간이 대부분 오래 걸립니다.

2. CPU를 멀티코어로 사용하지 못합니다. 그래서 단일 클럭이 높은 CPU가 빠릅니다. (AMD보단 인텔이 유리함)

3. 3분짜리 영상을 만드는데 10분이 소요될 수 있습니다.

4. 다른영상에서 참조되는 부분이 많으면 많을수록 RAM을 정말 많이 소모 합니다.

5. 미리미리 프로세싱해서 합치는 방식이 속도면에서 유리합니다.

6. 이쁘고 화려한 영상을 만드시려면 영상편집툴이 더 좋습니다. moviepy는 단순 반복작업을 개선하는 방식으로 사용해주세요.

7. python은 코루틴이 되지만, moviepy에서 사용하는 함수들은 외부 프로그램을 호출하는 방식으로 되어있기 때문에, 외부프로그램은 동시에 실행이 불가해서 결국.. 싱글 쓰레드로 동작합니다.

8. moviepy를 검색하면 나오는 문서면 정말 모든게 해결된다. (https://zulko.github.io/moviepy/ref/AudioClip.html)

 

 

 

 

기본개념

1. duration : 영상이나 음성에서 전체 길이를 말합니다.

2. position : 영상에선 좌표를 의미하고, 음성에선 특정 시간을 의미합니다. 왼쪽위가 0,0으로 시작함.

3. clip : 영상이나 음성, 이미지, 텍스트 등 의 미디어 단위 입니다.

4. subclip : 미디어 단위에서 편집된 더 작은 단위를 말합니다.

5. fx : 함수, 이펙트 function(x)를 주로 줄여서 이야기 합니다.

6. AudioFileClip : 오디오 파일 클립

7. ImageClip : 이미지 파일 클립

8. VideoFileClip : 비디오 파일 클립

9. TextClip : 텍스트 클립

10. 프로세싱 시점 : Composite명령어 (CompositeAudioClip, CompositeVideoClip)을 사용하거나, export할때만 프로세싱이 진행됩니다. 그 전까지는 clip안에 명령어를 누적해서 저장하고 있는 방식으로 진행됩니다.

11. CompositeVideoClip 명령어로 ImageClip, TextClip, VideoFileClip을 합칠 수 있습니다.

 

 

 

 

예제

전체적인 코딩을 설명하긴 힘들어서 간단한 예제들로 보여드리겠습니다.

 

 

1. 여러 음성 합치기

bg_audio_clip = AudioFileClip("./edit_video/dreams.mp3")
bg_audio_clip = afx.audio_loop(bg_audio_clip, duration=voice_clip_duration)
bg_audio_clip = bg_audio_clip.subclip(0,voice_clip_duration)
bg_audio_clip = bg_audio_clip.set_start(0)
bg_audio_clip = bg_audio_clip.set_end(voice_clip_duration)
bg_audio_clip = bg_audio_clip.volumex(0.2)  
bg_audio_clip = bg_audio_clip.fx(afx.audio_fadeout, 0.5)
main_audio_clip = CompositeAudioClip([bg_audio_clip, voice_clip])

배경음을 무한루프 시키고,

음성 시간만큼 배경음을 자르고

클립 자체의 시작시간과 끝시간을 정해줍니다. (다른 클립과 합쳐질때 기준이 됩니다.)

그리고 볼륨을 정하고, 점점 소리가 작아지게 fadeout을 해줍니다.

이 배경음과 음성을 CompositeAudioClip함수를 통해서 합쳐줍니다.

 

 

2. 영상을 원하는 사이즈에 맞게 크롭하는 과정

# clip
video_clip = VideoFileClip(file_path)
print(f'original clip duration:{video_clip.duration}') 

# 동영상의 가로 사이즈 맞추기
if video_clip.size[0] < MAX_SIZE[0]:
    w_ratio = MAX_SIZE[0]/video_clip.size[0]
    (w,h) = video_clip.size
    video_clip = video_clip.resize((w*w_ratio, h*w_ratio)) 
    video_clip.set_position('center') #중심으로 변경

# 동영상 중심 맞춰서 자르기 
if video_clip.size[0] != MAX_SIZE[0] or video_clip.size[1] != MAX_SIZE[1]:
    (w,h) = video_clip.size 
    video_clip = crop(video_clip, width=MAX_SIZE[0], height=MAX_SIZE[1], x_center=w/2, y_center=h/2)

# 음성제거
if without_audio:
    video_clip = video_clip.without_audio()

동영상을 비디오 클립으로 만듭니다.

원하는 사이즈가 될 수 있도록 resize합니다.

set_position을 호출해서 화면의 중심이 되도록 예약합니다. (바로 변환되지 않습니다.)

영상이 맥스사이즈를 넘어 삐져나가는것을 막기위해 crop해줍니다.

음성을 따로 준비했기 때문에, 영상에 포함된 음성을 제거해줍니다. (바로 제거되지 않습니다.)

 

 

 

3. 여러 영상 합치기

 for path in folder_file_list:

        # 파일 선택
        temp_path = f"{video_resource_folder_path}/{path}"
        file_size = os.path.getsize(temp_path)
        if file_size == 0:
            continue


        # 일관된 형태로 변경
        video_clip = convert_video(temp_path)
        video_clip = video_clip.fx(vfx.speedx, SPEED) # 배속효과
        video_clip_duration = video_clip.duration
        video_clip = video_clip.set_start(total_video_clip_duration)
        video_clip = video_clip.set_end(total_video_clip_duration+video_clip_duration)

        # 비디오 선택
        bg_video_clip_list.append(video_clip)
        print(f'selected video path:{temp_path}')
        total_video_clip_duration += video_clip_duration 
         
    # 영상만들기
    bg_video_clip_list.extend(verse_video_clip_list)
    main_video_clip = CompositeVideoClip(bg_video_clip_list)
    main_video_clip = main_video_clip.subclip(0, verse_voice_clip_duration) 
    main_video_clip.audio = main_audio_clip
    main_video_clip.write_videofile(full_file_path, threads=32, fps=24)

영상을 파일로 부터 가져옵니다.

배속을 적용합니다.

배속적용후에 시간을 구하면, 배속이 적용된 시간이 반환됩니다.

비디오를 순차적으로 붙이기 위해서, 누적해서 늘어가는 시간을 시작지점으로 정합니다.

비디오 클립의 끝 시간은 시작지점으로 부터 동영상 길이 만큼 이후의 시점으로 정합니다.

이렇게 모든 비디오 클립을 시간을 다르게 지정해놓고, 

CompositeVideoClip을 호출해서 하나의 클립으로 변환하면, 파일이 변환되기 시작합니다.

깔끔한 정리를 위해 비디오를 부분적으로 추출(subclip)합니다.

미리 만들어놓은 오디오로 새팅하고,

24fps를 가진 영상을 추출합니다. 이때 쓰레드는 32개를 사용한다는데. 실제로 그렇게 사용되지 않습니다.

쓰레드 개수는 구동하는 CPU의 Core개수만큼 쓰시면됩니다. 

 

 

4. 텍스트 클립 만들기

txt_clip2 = TextClip(
    txt, 
    fontsize=45, color='white', font=FONT, method='caption', stroke_color="#a8a8a8", stroke_width=2,
    size=(MAX_SIZE[0], MAX_SIZE[1]*0.1)
) 
txt_clip2 = txt_clip2.set_duration(duration)
txt_clip2 = txt_clip2.set_start(offset_start-FADE_TIME).crossfadein(FADE_TIME)
txt_clip2 = txt_clip2.set_end(offset_start+duration+FADE_TIME).crossfadeout(FADE_TIME)
txt_clip2 = txt_clip2.set_position(('center','bottom'))

텍스트 클립은 글자(txt)와 사이즈(fontsize), 색상(color), 외각선색(stroke_color), 외각선 굵기(stroke_width) 등으로 속성을 부여해서 만들 수 있습니다. 

여기서 중요한건 method입니다. 

method로 정할수 있는 옵션은 2가지, caption과 label입니다.

caption은 자막, label은 라벨(표시용 글자) 입니다. 

caption은 자막인 만큼, 가로 사이즈를 size 속성으로 정하면, 글이 가로 사이즈를 넘치면 다음줄로 넘어가면서 세로 사이즈가 증가하게 됩니다.

label은 원하는 텍스트가 그대로 적히기 원할때 주로 쓰며, size는 적지 않고, 만들고난 뒤에 측정해서 배치할때 사용됩니다.

 

 

 

5. 오디오 파일클립 출력

voice_clip.write_audiofile(export_temp_path, 44100, 2, 2000, "mp3")

오디오 파일을 출력할때는 여러가지 파라매터가 사용됩니다.

파일 출력위치와, 초당 프레임 수, 채널수, 버퍼사이즈, 코덱을 입력합니다.

초당 프레임 수는 주로 사람이 들을 수 있는 가청주파수 대역에 맞춰 48000 또는 44100으로 입력합니다.

채널은 스피커로 출력하는 갯수로 생가하면되며, 주로 왼쪽 오른쪽을 따로 듣기 때문에 2를 적습니다. 1개 신호를 16bit나 32bit로 음성을 처리하는데 이때 읽는 단위를 말합니다. 

버퍼사이즈는 그냥 2000적습니다.

코덱은 흔히 접하는 mp3로 정합니다. 

 

 

6. 이미지클립

# 타이틀 (그림)
title_image_clip = ImageClip('./edit_video/short-title.png')
title_image_clip = title_image_clip.set_duration(OPENING_DURATION)
title_image_clip = title_image_clip.set_start(0).crossfadein(FADE_TIME)
title_image_clip = title_image_clip.set_end(OPENING_DURATION).crossfadeout(FADE_TIME)
title_image_clip_scale = 1
if title_image_clip.w < MAX_SIZE[0]:
    title_image_clip_scale = MAX_SIZE[0] / title_image_clip.w
if title_image_clip.h < MAX_SIZE[1]:
    temp_title_image_clip_scale = MAX_SIZE[1] / title_image_clip.h
    if title_image_clip_scale < temp_title_image_clip_scale:
        title_image_clip_scale = temp_title_image_clip_scale
title_image_clip = title_image_clip.resize(title_image_clip_scale)
title_image_clip = title_image_clip.set_position(('center','center'))

이미지 클립을 만들고,

영상으로 합쳐질대를 대비해서 duration과 시작시간, 끝시간, fadein, fadeout를 정해놓습니다.

그리고 영상 사이즈에 맞춰서 최대로 확대하기 위해 resize를 적용합니다.

그리고 영상이 합쳐질때 가운데를 유지하기 위해서 set_position으로 (가로가운데, 세로가운데)를 세팅해둡니다.

 

 

 

 

 

마치며.

생각보다 간단하지만, 막상 파일을 만들어서 재생하기 전까지 디버깅이 힘들기 때문에

디버깅하기 위해서 많은 시간이 걸린다는 점이 있었지만

그래도 영상을 코딩을 편집할 수있는점이 너무 매력적이였습니다.

 

요즘엔 Paas, Saas등등 웹에서 영상 편집할 일이 많아지다보니,

개발자들도 서버단에서 이렇게 개발하지 않나 싶네요.

 

하다가 막히는게 있으시면 언제든 댓글달아주세요. 

제가 아는 부분이면 바로 답해드리고! 아니면 같이 고민해보자구요~

 

 

 

 

ps. 이런버그도 있습니다.

https://dalgonakit.tistory.com/221

 

Python moviepy audiofileclip error. noise.

Moviepy 소개 python으로 동영상 편집을 할때 아주 유용한 moviepy라는 라이브러리가 있습니다. 영상과 오디오를 편집할 수 있으며, 다양한 이펙트 효과도 직접 코딩할 수 있습니다. 이펙트 효과들은

dalgonakit.tistory.com

 

댓글