본문 바로가기

취미코딩/python

파이썬으로 유튜브 고화질 영상 다운로드 하기

서론부터. 유튜브 영상을 다운로드 하는 것이 규정 위반인지 의문이 있다. 얼마 전 Github에서 유튜브 다운로드 프로그램 하나가 삭제됐다가 복구됐다는 소식이 있었는데, 다운로드 행위 혹은 다운로드 방법에 대한 이야기 자체가 부적절하지는 않은 것 같다.

사실 유튜브 영상을 다운로드 받을 일이 얼마나 있나 싶다. 이미 경고하면 유튜브 영상 다운로드를 남용하면 차단당할 수 있다. 꼭 내려받아야 하는 영상이 생길 경우를 대비해 알아두는 정도가 좋지 않을까.

브라우저 확장으로 다운로드 할 때

크롬 혹은 크롬 앱스토어를 통해 비디오 다운로드 확장을 설치하면 유튜브는 서비스 제공을 하지 않는 것 같다. 하는 앱이 있는지 모르겠으나, 얼마 전 앱스토어에서 예전에 설치하고 비활성화한 다운로드 앱 하나에 멀웨어가 있다는 얘기를 듣고 함부로 설치하기가 좀 꺼려진다.

파이어폭스의 비디오 다운로드 확장 프로그램은 제법 잘 돌아가는 걸로 알고 있다. 유튜브에서도. 그런데 추가 프로그램을 설치해야 해서 찝찝하다(멀웨어 이슈는 없는 걸로 안다).

그러니 굳이 코딩 없이도 파이어폭스에서 유튜브 다운로드가 가능하긴 한데, 대부분 저화질이다. 720p도 찾기 어렵고 360p가 대부분이었다. 고화질 옵션을 찾아서 내려받으면 음성이 나오지 않는 경우도 있다.

이런 부분이 궁금해서 어떻게 제대로 된 유튜브 고화질 영상을 받을 수 있는지 알아봤다.

pytube

유튜브 다운로드로 유명한 파이썬 패키지다. 플레이리스트를 읽어서 한꺼번에 다운로드하는 것도 가능하다. 다만, 앞서 말했듯 남용은 차단을 부를 수 있다.

유튜브 페이지를 HTML 소스 코드로 열어보면 구글답게 자바스크립트 덩어리이다. pytube의 코드를 리뷰할 능력은 없지만, 대략 정규식으로 필요한 정보들을 추출해서 스트리밍 정보를 알려주는 걸로 보인다.

pytube 문서에도 잘 설명되어 있지만, 특정 영상의 스트리밍 정보는 하나가 아니다. 속도가 낮은 환경에서는 저화질로 자동으로 변경해서 끊김없이 재생하기 위해 여러 해상도의 스트리밍 영상정보로 나뉘어져 있다.

문서를 따라가다 보면 가장 고화질을 골라 다운로드하는 메소드는 한줄로 끝난다. 그런데 받아보면 마찬가지로 저화질인 경우가 대부분이다. 고화질 영상은 앞서 말했듯 음성 정보가 없는 영상만 있다.

유추해보면 유튜브 영상을 볼때 고화질이면 영상 따로, 오디오 따로 틀어주다가 속도가 낮아지면 저화질로 영상만 바꾸고 오디오는 끊김없이 계속 나오게 하는 것 같다. 이걸 DASH 방식이라고 부른단다. 영상과 오디오가 합쳐진 스트리밍(progressive라고 하는 것 같다)도 있지만 최고 해상도는 720p로 제한되고 실제 테스트한 영상들은 360p가 최고였다. 그래서 일반적인 다운로드로는 고화질 영상을 구할 수 없다.

그럼 고화질 영상은 어떻게 받느냐, pytube 문서에 설명된, 고화질 스트리밍(오디오가 제거된) 영상을 받고 오디오 스트리밍을 따로 받아서 합치는 방법을 써야 한다. 그런데 pytube 패키지는 영상과 오디오를 합치는 기능이 없다.

moviepy

moviepy는 영상을 다루는 파이썬 패키지다. 많은 일을 할 수 있는데, 영상과 오디오를 합치는 방법만 찾아봤다. 간단하다. 비디오 파일을 읽고, 오디오 파일을 읽고, 비디오 파일의 오디오 정보를 오디오 객체로 지정하고, 다시 새로운 비디오 파일로 쓰면 된다.

코딩할 때 유의점(=삽질 방지)

pytube가 가끔씩 정보를 못 읽어오는 영상이 있다. 내 경우 플레이리스트에 있는 영상을 모두 받아봤는데, 같은 제목의 중복 영상이 여러번 올라갔다면, 해당 동영상의 config 정보를 찾지 못한다고 (정규식으로 찾는데 정규식 패턴이 일치하지 않는다는 식의) 에러가 뜰 때가 있다. 해결책은 마땅치 않아서, 그냥 에러는 예외처리 해버리고 다음 동영상으로 넘어가는 방식을 썼다. 특정 유튜브 영상을 찍어서 받을 때는 경험하지 못했던 것 같다.

파일명을 따로 지정하지 않으면 pytube는 영상 제목을 파일명으로 쓴다. 나는 비디오와 오디오를 모두 mp4로 받았기 때문에, 같은 폴더에 받으면 파일이 덮어씌워진다. 폴더를 나눠서 받아야 한다.

영상마다 최고 화질이 어떻게 되는지 모른다. DASH 방식의 영상만 소팅했을 때 pytube가 알아서 최고화질 순서로 정렬해주는 것 같지만 혹시 모르니 재정렬하는 메소드를 추가로 붙였다.

from pytube import Playlist, YouTube
from moviepy.editor import *

fpath = lambda x: './src/' + x

def ydown(url: str, prefix: str = ""):
    yt = YouTube(url)
    vpath = (
        yt.streams.filter(adaptive=True, file_extension="mp4", only_video=True)
        .order_by("resolution")
        .desc()
        .first()
        .download(output_path=fpath("video/"), filename_prefix=f"{prefix} ")
    )
    apath = (
        yt.streams.filter(adaptive=True, file_extension="mp4", only_audio=True)
        .order_by("abr")
        .desc()
        .first()
        .download(output_path=fpath("audio/"), filename_prefix=f"{prefix} ")
    )

    v = VideoFileClip(vpath)
    a = AudioFileClip(apath)

    v.audio = a
    v.write_videofile(fpath(f"1080/{vpath.split('/')[-1]}"))


def playlistdown(url: str, prefix: str = ""):
    pl = Playlist(url)

    for v in pl.video_urls:
        try:
            ydown(v, prefix)
        except:
            continue

경로명은 대충 붙였으니 수정해서 쓰면 된다.

비디오 인코딩 작업이 함께 들어가 있기 때문에 시간은 꽤 걸린다. 플레잉타임이 긴 영상이라면 그 만큼 더 걸릴 것이다.

pytube에는 자막을 받을 수 있는 기능도 함께 제공되니 해당 부분도 같이 살펴보면 좋다.