본문 바로가기
개발

유튜브 영상 다운로더 (Python)

by DenverAlmighty 2025. 7. 23.

Gitub link

https://github.com/AlmightyDenver/youtube_video_downloader/tree/main

 

GitHub - AlmightyDenver/youtube_video_downloader: Python Script for Downloading the Highest Quality YouTube Video with pytubefix

Python Script for Downloading the Highest Quality YouTube Video with pytubefix - AlmightyDenver/youtube_video_downloader

github.com

 

🎬 youtube_video_downloader

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#----------------------------------------------------------------------------
# Python = 3.9
# Created By  : DenverAlmighty
# Created Date: 2025-07-23
# Updated Date : 2025-07-23
# version = '1.0.0'
# ---------------------------------------------------------------------------

import ffmpeg
from pytubefix import YouTube
from pytubefix.cli import on_progress
import os
import subprocess

# ANSI 색상 코드 설정 (콘솔에서 출력되는 텍스트의 색상 설정)
RED     =   '\033[1;31m'
YELLOW  =   '\033[1;33m'
GREEN   =   '\033[1;32m'
BLUE    =   '\033[1;34m'
OFF     =   '\033[0;0m '

# 변수 초기화
preferred_video_itag = ''
best_resolution_so_far = '0'
audio_only_itag = ''

################## 사용자 설정 (필수) #########################
# 선호 해상도
# preferred_resolution = '2160' # 4k
preferred_resolution = '1440' # QHD
# 파일 저장 위치
SAVE_PATH = '/Users/username/Downloads'
# 다운로드할 동영상 URL (형식 -> https://www.youtube.com/watch?v=) 
video_list = [
    'https://www.youtube.com/shorts/jHc19tczuzk',
    'https://www.youtube.com/watch?v=evYWmr4L-So',
    ]
## !!디바이스가 AV1 코덱 지원한다면 line 155~159  활성화
############################################################


# 비디오에서 오디오 트랙이 포함되어 있는지 확인하는 함수
# VIDEO example: 
# <Stream: itag='400' mime_type='video/mp4' res='1440p' fps='24fps' vcodec='av01.0.12M.08' progressive='False' sabr='False' type='video'>
# itag='400' ---> res='1440p'
def check_audio_stream(input_file):
    try:
        # ffmpeg를 사용하여 비디오의 오디오 스트림 확인
        probe = ffmpeg.probe(input_file, v='error', select_streams='a', show_entries='stream=codec_type')
        
        # 오디오 스트림 확인
        if 'streams' in probe and len(probe['streams']) > 0:
            print('\n🎶 오디오 트랙이 포함되어 있습니다.')
            return True
        else:
            print('\n❌ 오디오 트랙이 없습니다.')
            return False
    except ffmpeg.Error as e:
    #     print(f'\n❌ 오류 발생: {e}')
        return False

# 가장 높은 해상도 비디오 itag를 찾는 함수
def find_best_resolution(yt, preferred_resolution):
    global preferred_video_itag
    global best_resolution_so_far
    for stream in yt.streams.filter(file_extension='mp4'):
        # 더 높은 해상도면 itag 갱신
        if stream.resolution and int(stream.resolution[:-1]) > int(best_resolution_so_far):
            preferred_video_itag = stream.itag
            best_resolution_so_far = stream.resolution[:-1]
        # 선호 해상도 찾으면 해당 비디오의 preferred_video_itag에 itag 설정
        if stream.resolution and stream.resolution[:-1] == preferred_resolution:
            preferred_video_itag = stream.itag
            print(f'Using preferred video itag = {preferred_video_itag}')
            break

# 가장 높은 bitrate itag 찾는 함수
# AUDIO example: itag='250' ---> abr='70kbps'  itag='251' ---> abr='160kbps'
def find_best_audio_bitrate(yt):
    last_stream = yt.fmt_streams[-1:] 
    global preferred_audio_itag

    if last_stream and last_stream[0].abr:
        preferred_audio_itag = last_stream[0].itag
        print(f'Using preferred audio itag = {preferred_audio_itag}')

# 비디오 병합 (ffmpeg 정보 콘솔 출력 안하기 위함)
def merge_audio_video(downloaded_video, downloaded_audio, output_file, vcodec='copy'):
    '''Merge audio and video into a single file using ffmpeg and suppress console output.'''
    try:
        # Build the ffmpeg command
        command = [
            'ffmpeg',
            '-i', downloaded_video,   # Input video
            '-i', downloaded_audio,   # Input audio
            '-c:v', vcodec,           # Video codec (copy or H.264)
            '-c:a', 'aac',            # Audio codec (AAC)
            '-y',                     # Automatically overwrite output file if it exists
            output_file               # Output file path
        ]

        # Run the command using subprocess and suppress output
        subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

        print(f'병합 완료: {output_file}')

    except subprocess.CalledProcessError as e:
        print(f'❌ 병합 중 오류 발생: {e}')
        print(f'stderr: {e.stderr}')
        print(f'stdout: {e.stdout}')
        
        
# 메인 다운로드 및 병합 로직
def download_video(url):
    try:
        yt = YouTube(url, on_progress_callback=on_progress, use_oauth=False)
        print(f'{YELLOW} ▶ Video title: {yt.title} {OFF}')
        
        # 이름에서 특수 기호 제거
        original_title = ''.join(filter(lambda x: x not in '?.!/;:', yt.title))
        yt.title = 'tmp'  

        # 'preferred_video_resolution' 까지 최고 화질 찾기
        find_best_resolution(yt, preferred_resolution)
        
        # 비디오 다운로드
        video = yt.streams.get_by_itag(preferred_video_itag)
        print(f'⏬ {best_resolution_so_far}p 화질로 다운로드 시작...')
        video.download(output_path=SAVE_PATH)
        downloaded_video = os.path.join(SAVE_PATH, f'{yt.title}.mp4')
        
        # 최종 파일명
        fname = f'{original_title}_{best_resolution_so_far}.mp4'
        output_file = os.path.join(SAVE_PATH, fname)    

        # 오디오 미포함 파일 : 1440p 이상은 AV1 코덱으로 오디오 다운로드 안되어서 오디오 별도로 다운받아서 합쳐야함.
        if not check_audio_stream(downloaded_video):
            # 최고 bitrate 찾기
            find_best_audio_bitrate(yt)
            audio_only_itag = yt.streams.get_audio_only().itag
            audio = yt.streams.get_by_itag(audio_only_itag)
            
            # 오디오 다운로드
            print(f'⏬ 오디오 파일 다운로드 시작...')
            audio.download(output_path=SAVE_PATH)
            downloaded_audio = os.path.join(SAVE_PATH, f'{yt.title}.m4a')
            check_audio_stream(downloaded_audio)
            
            # 비디오 + 오디오 병합하기
            vcodec = 'copy'  # 비디오 코덱 그대로 사용
            
            # # 디바이스가 AV1 코덱 지원한다면
            # if int(best_resolution_so_far) <= 1080:
            #     vcodec = 'avc1.640028'  # H.264 for 1080p 이하
            # else:
            #     vcodec = 'av01.0.12M.08'  # AV1 for 1440p 이상
            
            # ffmpeg로 병합 파일 생성 
            merge_audio_video(downloaded_video, downloaded_audio, output_file, vcodec)
            
            # 병합 후 임시 파일 삭제
            os.remove(downloaded_video)
            os.remove(downloaded_audio)
        
        # 1080p 이하 오디오가 이미 포함된 경우, 비디오 파일 이름만 변경
        else:
            os.rename(downloaded_video, output_file)

        print(f'{GREEN}✅ \'{fname}\' 다운로드 완료!{OFF}')

    except Exception as e:
        print(f'{RED}❌ Error occurred {OFF}')
        print(f'{RED}error : {str(e)}{OFF}')

if __name__ == '__main__':
    print('🎬 YouTube 영상 다운로더')
    # 사용자 설정 안했으면 현재 디렉토리로 초기화
    if SAVE_PATH == '/Users/username/Downloads':
        SAVE_PATH = '.'

    for url in video_list:
        print('===========================================================================')
        print(f'▶ URL: {url}')
        download_video(url)

💡 설명

pytubefix를 사용해 유튜브 영상을 최고 해상도로 다운받는 스크립트입니다.

주요 기능

주요 기능은 아래와 같습니다:

기능 1: 선호하는 해상도를 설정해 유튜브 영상을 mp4 형식으로 다운로드 합니다.
기능 2: 디바이스가 AV1 코덱 지원한다면 4k 해상도로 다운로드 가능합니다.

코드 설명

  1. 선호하는 해상도를 설정하면 해당 해상도 이하의 가장 높은 해상도를 찾습니다.
  2. 다운로드한 mp4 파일에 오디오가 포함되어있다면 임시 파일 이름을 변경합니다.
  3. 다운로드한 mp4 파일에 오디오가 포함되어있지 않다면 오디오를 m4a 형식으로 다운로드 받은 후 ffmpeg로 오디오와 비디오를 병합하고 임시 파일은 삭제합니다.



🛠️ 필수 조건

ffmpeg 설치 필요합니다.

# mac
brew install ffmpeg
 

 

requirements.txt

ffmpeg-python==0.2.0
pytubefix==9.4.1
 



⚙️ 실행 방법

아래 변수 설정 후 스크립트 실행

preferred_resolution : 선호 해상도 (기본값 )
SAVE_PATH : 파일 저장 위치
video_list : 다운로드할 유튜브 url 리스트

* 디바이스가 AV1 코덱 지원한다면 line 155~159  활성화
 



🖥️ 실행 화면





🔗 참고한 오픈소스

이 코드는 [Stack Overflow pytube 관련 질문 링크]에서 참고한 코드입니다. 원본 코드를 확인하고 싶으면 아래 링크를 방문해 주세요:
https://stackoverflow.com/questions/65355569/get-highest-resolution-function-doesnt-work-in-pytube

 

 

⚠️ 발생한 오류 및 해결

  • pytube를 사용하면 400 에러가 난다. -> pytubefix를 사용해야한다.
  • SSL 오류가나는데 /Applications/Python3.9/Install Certificates.command 를 한번 실행하면 해결된다.
<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)>
  • mp4가 아니라 webm 파일로 다운로드 되었는데 mp4만 필터링 할 수 있다. (yt.streams.filter(file_extension='mp4'))
  • mp4로 다운로드해서 재생해보니 오디오가 없었는데 크롬으로 재생하니 소리가 나왔다. -> 내 노트북이 av1 코덱을 지원하지 않아서그런거였다.  
    • <Stream: itag="136" mime_type="video/mp4" res="720p" fps="24fps" vcodec="avc1.4d401f" progressive="False" sabr="False" type="video">
    • <Stream: itag="137" mime_type="video/mp4" res="1080p" fps="24fps" vcodec="avc1.640028" progressive="False" sabr="False" type="video">
    • <Stream: itag="401" mime_type="video/mp4" res="2160p" fps="24fps" vcodec="av01.0.12M.08" progressive="False" sabr="False" type="video">
    • <Stream: itag="401" mime_type="video/mp4" res="2160p" fps="24fps" vcodec="av01.0.12M.08" progressive="False" sabr="False" type="video">

 

'개발' 카테고리의 다른 글

[Python] json.dump 한글 깨짐  (0) 2023.03.11
라인 Notify 봇 만들기  (0) 2022.08.29
[Python] 슬라이딩 윈도우  (0) 2020.08.16