본문 바로가기
장난감/유튜브 다운로더

유튜브 다운로더 4 - 음성 파일 with moviepy

by Mr. Green 2022. 8. 7.

 안녕하세요. 최근에 시작한 프로젝트인 mp3tagger를 만들다가 문제를 발견했습니다.

 

 처음 음성 파일 만들땐 mp4로 음성만 있는 파일을 받고 mp3로 '이름'을 바꿔서 만들었는데, 그러니 다른데에 음성을 쓰려니 오류가 나더랍니다. 그래서 영상을 mp3로 '변환'할 방법을 찾았습니다.

차례

0. 음성 원본 받기

1. moviepy

1-1. mp4 파일 정리

2. tkinter - Checkbutton

3. 프로젝트 3

3-1. pyinstaller 사용 시 주의

0. 음성 원본 받기

audio = yt.streams.filter(file_extension='mp4').order_by('abr').all()[-1]
audio.download(audio_dir)

 abr이 가장 좋은 걸 받습니다. ABR(Avarage Bitrate)란 음성 인코딩 방식 중 하나입니다.(더 많은 정보는 이쪽이나 이쪽으로) pytube는 영상형식(mp4, webm)으로만 받을 수 있기 때문에 일단 영상을 받고 변환해야 합니다.

 

 만약 이름 바꾸기로 확장자를 바꾸게 되면 그 파일을 moviepy 등에서 인식하지 못합니다.

1. moviepy

 영상과 음성을 다루는 라이브러리입니다. 저는 이중에서 영상을 음성으로 만들어주는 write_audiofile()을 사용하겠습니다.

from glob import glob
import moviepy.editor as mp


vfile = glob('./audio/*.mp4')

for fi in vfile:
	clip = mp.VideoFileClip(fi)
	clip.audio.write_audiofile('./audio/' + file_name + '.mp3')
  • glob() : 괄호 안 키워드에 해당하는 파일을 호출합니다.
    • * : 모든 문자열
      • *.mp4 : mp4 형식의 모든 파일
    • ? : 한 문자
      • file?.mp4 : file1.mp4, file2.mp4, filem.mp4...
  • mp.VideoFileClip() : 영상 파일을 읽습니다. 이때, pytube의 audio_only로 받게 되면 인식되지 않습니다.
  • clip.audio.write_audiofile(파일이름) : 읽어들인 영상파일을 음성 파일로 변환합니다. 파일 이름에 형식을 포함시키면 그 형식으로 만듭니다.

1-1. mp4파일 정리

  audio 안에는 mp3만 놓기 위해 받아놓은 mp4 파일을 정리합니다.

for fi in vfile:
	os.remove(fi)

2. tkinter - Checkbutton

from tkinter import Checkbutton

...

onlyMp4 = tkinter.IntVar()
only_mp4 = Checkbutton(window, text='only mp4', variable=onlyMp4)

for fi in vfile:
	...
    
	if onlyMp4.get() == 0:
        clip = mp.VideoFileClip(fi)
        clip.audio.write_audiofile('./audio/' + file_name + '.mp3')
  • Checkbutton() : 체크 버튼을 넣습니다.
    • variable=변수 : 체크박스를 통해 얻은 값을 '변수'로 받아줍니다.
      • 0, 1로 출력
    • IntVar() : int 형식의 변수 생성

3. 프로젝트 3

3-1. pyinstaller 사용 시 주의

 moviepy를 사용해서 pyinstaller로 exe 파일 만들 때, audio_fadein과 video(또는 crop)이 없다며 오류가 나는데, 아래 프로젝트 본문에 나온대로 import를 채워주시면 됩니다. (하지만 이것 때문에 용량이 폭발해 버린..)

from pathlib import Path
import re, os
from glob import glob

from pytube import YouTube
import urllib.request
import moviepy.editor as mp

import tkinter
from tkinter import Label, Entry, Button, Checkbutton

## https://github.com/Zulko/moviepy/issues/591
#pyinstall audio_fadein 관련 오류
from moviepy.audio.fx.audio_fadein import audio_fadein
from moviepy.audio.fx.audio_fadeout import audio_fadeout
from moviepy.audio.fx.audio_left_right import audio_left_right
from moviepy.audio.fx.audio_loop import audio_loop
from moviepy.audio.fx.audio_normalize import audio_normalize
from moviepy.audio.fx.volumex import volumex
#pyinstall video / crop 관련 오류
from moviepy.video.fx.accel_decel import accel_decel
from moviepy.video.fx.blackwhite import blackwhite
from moviepy.video.fx.blink import blink
from moviepy.video.fx.colorx import colorx
from moviepy.video.fx.crop import crop
from moviepy.video.fx.even_size import even_size
from moviepy.video.fx.fadein import fadein
from moviepy.video.fx.fadeout import fadeout
from moviepy.video.fx.freeze import freeze
from moviepy.video.fx.freeze_region import freeze_region
from moviepy.video.fx.gamma_corr import gamma_corr
from moviepy.video.fx.headblur import headblur
from moviepy.video.fx.invert_colors import invert_colors
from moviepy.video.fx.loop import loop
from moviepy.video.fx.lum_contrast import lum_contrast
from moviepy.video.fx.make_loopable import make_loopable
from moviepy.video.fx.margin import margin
from moviepy.video.fx.mask_and import mask_and
from moviepy.video.fx.mask_color import mask_color
from moviepy.video.fx.mask_or import mask_or
from moviepy.video.fx.mirror_x import mirror_x
from moviepy.video.fx.mirror_y import mirror_y
from moviepy.video.fx.painting import painting
from moviepy.video.fx.resize import resize
from moviepy.video.fx.rotate import rotate
from moviepy.video.fx.scroll import scroll
from moviepy.video.fx.speedx import speedx
from moviepy.video.fx.supersample import supersample
from moviepy.video.fx.time_mirror import time_mirror
from moviepy.video.fx.time_symmetrize import time_symmetrize


def downloader():
    audio_dir = './audio/'
    vidoe_dir = './video/'
    url = url_in.get()#input("url : ")
    yt = YouTube(url)

    video = yt.streams.get_highest_resolution()
    #video = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').last()
    video.download(vidoe_dir)

    translated = re.sub(r'[:?|/"]', '', yt.title)
    thum_dir = './thumbnail/' + translated + '.jpg'
    urllib.request.urlretrieve(yt.thumbnail_url, thum_dir)

    #print(yt.streams.filter(only_audio=True).all())
    if onlyMp4.get() == 0:
        audio = yt.streams.filter(file_extension='mp4').order_by('abr').all()[-1]
        audio.download(audio_dir)

        try:
            vfile = glob('./audio/*.mp4')
            
            for fi in vfile:
                file_name = Path(fi).stem
                clip = mp.VideoFileClip(fi)
                clip.audio.write_audiofile('./audio/' + file_name + '.mp3')
                
                #if fi == vfile[-1]:
                clip.close()
            
            for fi in vfile:
                os.remove(fi)


            done = Label(window, text="all done!")
            done.grid(row=7, column=1)
        except:
            audio_error = Label(window, text="audio download failed", height=10)
            audio_error.grid(row=7, column=1)

    if(yt.length%60 < 10):
        sec = '0' + str(yt.length%60)
    if(yt.length%60 >= 10):
        sec = str(yt.length%60)

    print('download done!')

    print('제목 : ', yt.title)
    print('영상 길이 : ', round(yt.length/60), ':', sec)
    print('게시자 : ', yt.author)
    print('게시일 : ', yt.publish_date)
    print('조회수 : ', yt.views)
    print('키워드 : ', yt.keywords)
    print('썸네일 주소 : ', yt.thumbnail_url)

    print('done!')
    
    vtitle = Label(window, text=yt.title, width=35)
    vlength = Label(window, text=str(round(yt.length/60)) + ':' + sec, width=20)
    vupload = Label(window, text=yt.author, width=20)
    vdate = Label(window, text=str(yt.publish_date), width=20)
    vtitle.grid(row=1, column=1)
    vlength.grid(row=2, column=1)
    vupload.grid(row=3, column=1)
    vdate.grid(row=4, column=1)



window = tkinter.Tk()

window.title('youtube-downloader 2.0.0')
window.geometry('400x400+400+200')
window.resizable(False, False)
window.iconbitmap('./icon.ico')

url_lbl = Label(window, text="url : ", width=10)
url_in = Entry(window, width=35)
btn = Button(window, text="여!", width=10, command=downloader)
onlyMp4 = tkinter.IntVar()
only_mp4 = Checkbutton(window, text='only MP4', variable=onlyMp4)
url_lbl.grid(row=0, column=0)
url_in.grid(row=0, column=1)
btn.grid(row=0, column=2)
only_mp4.grid(row=1, column=2)

lbl1 = Label(window, text='title : ', width=5)
lbl2 = Label(window, text='time : ', width=5)
lbl3 = Label(window, text='channel : ', width=5)
lbl4 = Label(window, text='date : ', width=5)
lbl1.grid(row=1, column=0)
lbl2.grid(row=2, column=0)
lbl3.grid(row=3, column=0)
lbl4.grid(row=4, column=0)

window.mainloop()