AI로 목소리를 학습하여 노래를 부르게 하자! #4

Apic·2023년 7월 25일
0

심심한 프로젝트

목록 보기
11/18

데이터 준비

저 모델을 사용하기 전에 먼저 학습시킬 데이터들을 준비해야 한다.

나는 이쪽에서 참고를 했는데 이 게시글의 말에 따르면 학습시킬 모델들은 반주와 화음이 없는 노래음성, 잡음과 배경음이 없는 말하기 음성이 있어야 한다고 한다.

그리고 음성 데이터 파일의 크기는 3 ~ 15초 사이면 된다고 한다.

한 마디로 딱 목소리만 있으면 된다. 학습시킬 때는 반주와 배경음은 필요가 없다.

따로 목소리 추출이 귀찮다면 이쪽으로 가보자.

유튜브 영상에서 음원 추출

유튜브에서 노래 데이터, 인터뷰 데이터들을 다운로드해서 거기서 목소리를 추출해볼 생각이다.

유튜브에서 영상을 다운로드 하는 방법은 여러가지가 있지만, 많은 영상을 다운로드 해야 하기 때문에, 파이썬 코드로 구현해 보았다.

유튜브 다운로드 패키지를 다운로드 한다.

pip install pytube

유튜브에서 영상을 다운받는데에는 2가지 경우가 있다.

  1. 영상 1개 다운받을 때
  2. 플레이리스트를 다운로드 받을 때

노래의 경우 플레이리스트로 묶여있는 경우가 많기 때문에 이 플레이리스트 하나만 하면 수십개의 영상을 다운로드 받을 수 있어 노래라면 플레이리스트를 다운로드 하자.

urls = [
    'https://www.youtube.com/watch?v=UCyz1pNO2oc',  ##김광석 - 잊어야 한다는 마음으로
    'https://www.youtube.com/watch?v=DMw5RKcbKVY',  ##김광석 - 서른 즈음에
    'https://www.youtube.com/watch?v=CYtv_k3fZyU',  ##김광석 - 이등병의 편지
    'https://www.youtube.com/watch?v=nFXTf9PodCw',  ##김광석 - 먼지가 되어
    ...
    'https://www.youtube.com/watch?v=xejK2sawBSo',  ##김광석 - 새장 속의 친구
    'https://www.youtube.com/watch?v=VIJiOBBzeMk',  ##김광석 - 변해가네
]

이런식으로 노래 영상 하나하나 다운로드 할 수 있고

urls = [
	Playlist('https://www.youtube.com/watch?v=DN6p7lASXS4&list=PL2ZmURExG4aGUD4LewOuwMxz7hUbWfbAg')
]

이런식으로 플레이리스트를 통째로 가져올 수 있다.
플레이 리스트로 가져오는 경우에는 반복문을 통해 하나하나 가져올 수 있다.

클래스를 만들었다.

def __init__(self) -> None:
    self.download_streams = []
    self.path = './music_download/original_files/'
    self.cores = multiprocessing.cpu_count()

영상을 다운로드 할때 시간이 좀 걸리므로, 멀티프로세스를 이용하여 다운로드할 예정이다.

##음악 개수
total_range = sum([1 if type(a) == str else len(a) for a in urls])

시간이 얼마나 걸리는지 확인하기 위해, 음악 개수를 세주고 하나 될 때 마다 프로그래스바를 한 칸씩 늘린다.

def yt_make_download_list(self, url, pbar):
	##만약 플레이리스트가 아니라면(일반 음악 1개라면)
    if not 'playlist' in str(type(url)):
        yt = YouTube(url)
        ##오디오만 가져오기, 형식은 mp4
        streams = yt.streams.filter(only_audio=True, file_extension='mp4')
        ##나온 리스트들 중에서 음질이 가장 좋은 것을 선택
        max_abr = 0
        for stream in streams:
            abr = int(re.findall(r'\d+', stream.abr)[0])
            if abr > max_abr:
                max_abr = abr
                max_stream = stream
        ##다운로드할 배열에 넣기
        self.download_streams.append(max_stream)
        ##프로그래스바 1칸 이동
        pbar.update(1)
    else:
        ##플레이리스트라면 하나씩 빼서 가장 음질이 좋은 파일을 다운로드 목록에 넣는다.
        for video in url:
            yt = YouTube(video)
            streams = yt.streams.filter(only_audio=True, file_extension='mp4')
            max_abr = 0
            for stream in streams:
                abr = int(re.findall(r'\d+', stream.abr)[0])
                if abr > max_abr:
                    max_abr = abr
                    max_stream = stream

            self.download_streams.append(max_stream)
            pbar.update(1)

pbar = tqdm(total=total_range, desc='영상 다운로드 중...')
    for url in urls:
        self.yt_make_download_list(url, pbar)
    pbar.close()

이렇게 다운로드 받을 목록을 넣고, 이제 멀티프로세스를 이용해 다운로드 한다.

def yt_audio_download(self, url):
    url.download(self.path

parmap.map(self.yt_audio_download, self.download_streams, pm_pbar=True, pm_processes=self.cores)

이제 영상을 모두 다운로드 했다!

이 영상들을 모두 .wav 파일로 바꿔주자

files = glob('./music_download/original_files/*.mp4')
print(files)
for file in tqdm(files, desc='mp4 -> wav 작업중...'):
    file_name = os.path.basename(file)[:-4]
    file_name = re.sub(r"[^\uAC00-\uD7A30-9a-zA-Z\s]", "", file_name).replace(' ','').replace('musicdownloadoriginalfiles', '')+'.mp3'

    os.rename(file, self.path+file_name)
    ##mp3 -> wav
    infile = self.path+file_name
    outfile = f'./music_download/wav_files/{os.path.basename(file_name)[:-4]}.wav'

    command = f'ffmpeg -i {infile} -ac 2 -f wav {outfile}'
    os.system(command)
    os.system(f'del {infile}')

일단 pytube로 다운로드 하게 되면 몇 몇 특수문자가 안나오게 되기 때문에 다운로드 받은 목록을 이용해서 하기가 힘들다.

그래서 다운로드 폴더를 읽어서 파일 이름을 읽는데 문제가 생기지 않도록, 특수문자들과 띄어쓰기를 모두 제거해준다.

그리고 ffmpeg 파일을 이용해 mp3 파일을 wav 파일로 바꿔준다.
ffmpeg를 설치하는 방법은 여기에 있다.

음성 파일 설정 변경

앞서 말했듯이, 이 모델은 정해진 음성 파일만 받는다.
44.1khz, 모노인 파일만 받는데 (16만 되는긴지는 모르겠는데 대부분 16이라 상관 없을 듯?)

먼저 다운로드 받은 파일들의 속성을 보도록 하자

import wave
for file in files:
    with wave.open(file, 'rb') as audio_file:
        ##sr(samplingrate 확인)
        sr = audio_file.getframerate()
        ##채널 수 확인(1=모노)
        channels = audio_file.getnchannels()
        ##비트 수 확인(16비트)
        bit_depth = audio_file.getsampwidth() * 8


    ##print(os.path.basename(file),sr, channels, bit_depth)
    print(sr, channels, bit_depth)

44100 2 16

확인해 보니, 1(모노)가 아닌 2(스테레오)로 다운로드 받아져 있다.
이것을 모노로 변경해야 한다.

그리고 sr(samplerate)도 44.1khz, 16khz가 많은데 이것도 다르다면 44.1khz로 맞춰주자.

class audio_setting:
    def __init__(self) -> None:
        self.change_set = 0
        pass

    ##스테레오 -> 모노 변환
    def stero_to_mono(self, file_path):
        sound = AudioSegment.from_wav(file_path)
        sound = sound.set_channels(1)
        sound.export(file_path, format="wav")

    def sr_change(self, file_path):
        ##음성 파일 불러오기
        y, sr = librosa.load(file_path, sr=None)

        ##리샘플링
        y_resampled = librosa.resample(y, sr, 44100)

        ##결과 저장하기
        librosa.output.write_wav(file_path, y_resampled, sr=44100)

    def audio_change(self, paths:list):
        for path in tqdm(paths, desc='음성 설정중...'):
            with wave.open(path, 'rb') as audio_file:
                ##sr(samplingrate 확인)
                sr = audio_file.getframerate()
                ##채널 수 확인(1=모노)
                channels = audio_file.getnchannels()
                ##비트 수 확인(16비트)
                bit_depth = audio_file.getsampwidth() * 8

            if channels == 2:
                self.stero_to_mono(path)
                self.change_set += 1

            if sr != 44100:
                self.sr_change(path)
                self.change_set += 1

        print('바뀐 데이터 수: ', self.change_set)

목소리 추출

이제 대망의 목소리 추출이다.

목소리 추출하는데 사용하는 패키지는 spleeter라고 한다.

이 spleeter를 쓰면서 느끼는 건데 이거, 진짜 짜증난다.

이유는 글을 쓰면서 차차 설명하겠다.

하지만 주의해라. 잘못하면 다 밀고 다시 해야한다.

만약 파이썬으로 하는게 그렇다면, spleeter-gui 이것을 사용해도 좋다.

spleeter를 설치하자
pip isntall spleeter

spleeter를 사용하려면 해당 파일이 존재하는 폴더로 가야한다.

나는 앞의 코드를 이용해 음성 파일을 ./music_download/wav_files에 저장을 했다.

now_path = os.getcwd()
os.chdir('./music_files/wavfile/')
files = os.listdir(os.getcwd())
for file in tqdm(files):
    if '.wav' in file:
    	##목소리 추출 2라면 보컬, 나머지로 분리가 되며 최대 5까지 가능하다.
        spl = f'spleeter separate -p spleeter:2stems -o output {file}'
        os.system(spl)

        ##추출이 끝났으면 반주와 목소리의 파일 명을 바꾸고, 각기 다른 폴더에 넣는다.
        try:
            shutil.move(f'./output/{file[:-4]}/vocals.wav', f'../spleeter vocals/vocals.wav')
            os.rename('../spleeter vocals/vocals.wav', f'../spleeter vocals/{file}')
            shutil.move(f'./{file}', f'../use/{file}')
        except FileExistsError:
            print('파일이 이미 존재합니다.')

os.chdir(now_path)

이렇게 하면 목소리 추출이 완료된다.

접기/펼치기

접은 제목

접은 내용

이것 같은 경우는 노래 파일이 통째로 들어있기 때문에 약 3 ~ 4분정도의 길이다.

이것을 15초 이내, 나는 8초 간격으로 잘라줄 예정이다.

자르기 전에 무음부분을 없애주자.

def delete_no_voice(self, file_path):
    ##Load audio file
    file_name = os.path.basename(file_path)
    audio = AudioSegment.from_file(file_path)

    ##Remove silent parts
    audio_filtered = audio.strip_silence(silence_thresh=-30)


    ##Save the output
    audio_filtered.export(f'./music_download/split_files/{file_name}', format='wav')

이렇게 하면 일정 구간(-30) 이하의 소리는 모두 무음 처리된다.

이렇게 하고 노래를 들어보면 숨도 안쉬고 부르는 것을 볼 수 있다.

노래 길이도 4분 46초에서 2분 43초로 줄어든 것을 볼 수 있다.

이제 이 음성을 8초 간격으로 분리해주자.

idx = 0
for file in tqdm(files):

    with wave.open(file, 'rb') as wave_file:
        ##샘플링 레이트, 채널 수, 샘플 수 정보 가져오기
        framerate = wave_file.getframerate()
        n_frames = wave_file.getnframes()

        ##음성 파일 길이 계산 (초 단위)
        duration = n_frames / float(framerate)
        duration = int(math.ceil(duration))

    ##8초 이하인 음성 파일은 넘어감
    if not duration <= split_num:
        ##8초 간격으로 분리하기
        if idx == 0:
            idx = self.split_sudio(file, split_num)
        else:
            idx = self.split_sudio(file, split_num, idx)

    elif duration < 3:
        os.system(f'del {file}')
        print(f'{duration}초 이므로 파일 삭제')

이렇게 하면 split0 ~ 차례대로 학습 파일이 생성된다!

profile
코딩 공부하는 사람

0개의 댓글