์งํ๊ด๋์ ์ํ ์๋ฒฝ ์๋ํ ์ค์ ๊ฐ์ด๋
๋์คํฌ ์ฉ๋ ์ต์ ํ, Spotify Basic-Pitch(ํผ์น ์ถ์ถ), Demucs(๋ณด์ปฌ ๋ถ๋ฆฌ), ๊ทธ๋ฆฌ๊ณ ๋ณด์ปฌ ์ค์ ์์ง๊น์ง ํ ๋ฒ์ ํด๊ฒฐํฉ๋๋ค.
ํ์ฌ ํ๊ฒฝ์์ demucs์ basic-pitch ๊ฐ์ ๋ํ AI ๋ชจ๋ธ์ ์ค์นํ๋ ค๋ฉด ์ต์ 4GB ์ด์์ ์ฌ์ ๊ณต๊ฐ์ด ํ์ํฉ๋๋ค. ์๋ ๋ช
๋ น์ด๋ฅผ ์์๋๋ก ์คํํ์ฌ ๋ถํ์ํ ์บ์๋ฅผ ์ญ์ ํฉ๋๋ค.
# 1. pip ์บ์ ์ญ์ (๊ฐ์ฅ ํฐ ์ฉ๋ ์ฐจ์ง)
rm -rf ~/.cache/pip
# 2. huggingface ์บ์ ์ด๊ธฐํ (์ด๋ฏธ ๋ค์ด๋ก๋๋ ์ค๋ณต ๋ชจ๋ธ ์ ๊ฑฐ)
rm -rf ~/.cache/huggingface
# 3. ์์คํ
์์ ํ์ผ ์ ๋ฆฌ
sudo apt-get clean
sudo apt-get autoremove -y
# 4. ํ์ฌ ๋จ์ ์ฉ๋ ํ์ธ (์ต์ 4GB ์ด์์ด์ด์ผ ํจ)
df -h .๐ก ํ์ธ: ์ ๋ช ๋ น์ด ์คํ ํ
Avail์ปฌ๋ผ์ด 4G ์ด์์ธ์ง ํ์ธํ์ธ์. ๋ถ์กฑํ๋ค๋ฉด ํฐ ํ์ผ์ ์ง์ ์ญ์ ํด์ผ ํฉ๋๋ค.
ํ์ด์ฌ ๊ฐ์ํ๊ฒฝ์ ์์ฑํ๊ณ , ์ค๋์ค ์ฒ๋ฆฌ ๋ฐ AI ๋ชจ๋ธ์ ํ์ํ ๋ชจ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ ๋ฒ์ ์ค์นํฉ๋๋ค.
# 1. Python ๊ฐ์ํ๊ฒฝ ์์ฑ (ํ๋ก์ ํธ ํด๋ ๋ด์ venv ์์ฑ)
python3 -m venv venv
# 2. ๊ฐ์ํ๊ฒฝ ํ์ฑํ
source venv/bin/activate
# 3. ํ์ ํจํค์ง ์
๊ทธ๋ ์ด๋
pip install --upgrade pip setuptools wheel
# 4. [ํต์ฌ] ์ค๋์ค ์ฒ๋ฆฌ ๋ฐ AI ๋ชจ๋ธ ์ผ๊ด ์ค์น
# - torch: GPU ๊ฐ์์ฉ (CPU๋ง ์์ ๊ฒฝ์ฐ ์๋ CPU ๋ฒ์ ์ค์น)
# - demucs: ๋ณด์ปฌ/๋ฐ์ฃผ ๋ถ๋ฆฌ (Spotify ์ฐ๊ตฌ์ง ๊ฐ๋ฐ)
# - basic-pitch: ์ค๋์คโMIDI ํผ์น ์ถ์ถ (Spotify ์ฐ๊ตฌ์ง ๊ฐ๋ฐ)
# - librosa, soundfile: ์ค๋์ค ๋ถ์ ๋ฐ ์ ์ฅ
# - numpy, scipy: ์์น ์ฐ์ฐ
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install demucs basic-pitch librosa soundfile numpy scipy pydub
# 5. FFmpeg ์ค์น (์ค๋์ค ๋ณํ ํ์ ๋๊ตฌ)
sudo apt-get update
sudo apt-get install -y ffmpegโณ ์์ ์๊ฐ: ๋คํธ์ํฌ ์๋์ ๋ฐ๋ผ 5~10 ๋ถ ์ ๋ ์์๋ฉ๋๋ค. ์ค๊ฐ์ ๋๊ธฐ์ง ์๋๋ก ๊ธฐ๋ค๋ ค์ฃผ์ธ์.
์์ ํ ์์ ํ์ผ์ ์ค๋นํฉ๋๋ค.
- ํ๋ก์ ํธ ํด๋์
inputs๋ผ๋ ํด๋๋ฅผ ๋ง๋ญ๋๋ค. - ๋ณด์ปฌ์ ๊ต์ฒดํ๋ ค๋ ์๋ณธ ์์
ํ์ผ (์:
original_song.mp3) ์inputsํด๋์ ๋ฃ์ต๋๋ค. - ์๋ก์ด ๊ฐ์ฌ์ ๋ฉ๋ก๋๊ฐ ์ ์๋ ํ
์คํธ ํ์ผ (์:
lyrics.txt) ์ ๋ฃจํธ ํด๋์ ๋ก๋๋ค.
mkdir -p inputs
# ์์: original_song.mp3 ๋ฅผ inputs ํด๋๋ก ์ด๋
# mv /path/to/your/song.mp3 inputs/original_song.mp3์๋ ์ฝ๋๋ฅผ run_axon_vocal.py ๋ผ๋ ์ด๋ฆ์ผ๋ก ์ ์ฅํ๊ณ ์คํํ๋ฉด, ๋ถ๋ฆฌ โ ํผ์น์ถ์ถ โ ๋ณด์ปฌ์์ฑ โ ํฉ์ฑ ์ ๊ณผ์ ์ด ์๋์ผ๋ก ์ํ๋ฉ๋๋ค.
import os
import subprocess
import sys
import librosa
import soundfile as sf
import numpy as np
from pathlib import Path
import shutil
# ์ค์
INPUT_AUDIO = "inputs/original_song.mp3" # ์
๋ ฅ ์์
ํ์ผ
OUTPUT_DIR = "outputs"
VOCAL_MODEL_PATH = "htdemucs" # Demucs ๋ชจ๋ธ ์ข
๋ฅ (htdemucs ๊ฐ ๊ฐ์ฅ ์ ํํจ)
def check_disk_space():
"""๋์คํฌ ์ฉ๋ ํ์ธ"""
total, used, free = shutil.disk_usage("/")
if free < (4 * 1024**3): # 4GB ๋ฏธ๋ง
print("โ ์๋ฌ: ๋์คํฌ ์ฉ๋์ด ๋ถ์กฑํฉ๋๋ค. (์ต์ 4GB ํ์)")
sys.exit(1)
print(f"โ
๋์คํฌ ์ฉ๋ ์ถฉ๋ถํจ: {free // (1024**3)}GB ๋จ์")
def separate_vocals(input_path, output_dir):
"""1 ๋จ๊ณ: Demucs ๋ฅผ ์ด์ฉํด ๋ณด์ปฌ๊ณผ ๋ฐ์ฃผ ๋ถ๋ฆฌ"""
print("\n๐ค [1/4] ๋ณด์ปฌ๊ณผ ๋ฐ์ฃผ๋ฅผ ๋ถ๋ฆฌ ์ค์
๋๋ค (Demucs)...")
cmd = [
"python", "-m", "demucs",
"-n", VOCAL_MODEL_PATH,
"-o", output_dir,
input_path
]
try:
subprocess.run(cmd, check=True)
# Demucs ์ถ๋ ฅ ๊ฒฝ๋ก ํ์ธ
base_name = Path(input_path).stem
separated_path = Path(output_dir) / VOCAL_MODEL_PATH / base_name
vocals_path = separated_path / "vocals.wav"
instrumental_path = separated_path / "no_vocals.wav"
if not vocals_path.exists():
raise FileNotFoundError("๋ณด์ปฌ ํ์ผ ์ถ์ถ ์คํจ")
print(f"โ
๋ณด์ปฌ ๋ถ๋ฆฌ ์๋ฃ: {vocals_path}")
return str(vocals_path), str(instrumental_path)
except Exception as e:
print(f"โ ๋ณด์ปฌ ๋ถ๋ฆฌ ์คํจ: {e}")
sys.exit(1)
def extract_pitch(audio_path, output_midi):
"""2 ๋จ๊ณ: Spotify Basic-Pitch ๋ก ๋ณด์ปฌ์ ํผ์น (MIDI) ์ถ์ถ"""
print("\n๐น [2/4] ๋ณด์ปฌ์ ์์ (MIDI) ์ ์ถ์ถ ์ค์
๋๋ค (Spotify Basic-Pitch)...")
cmd = [
"basic-pitch",
audio_path,
"--output-dir", os.path.dirname(output_midi),
"--save-model-outputs"
]
try:
subprocess.run(cmd, check=True)
# basic-pitch ๋ ์๋์ผ๋ก _basic_pitch.json ๊ณผ .mid ํ์ผ์ ์์ฑํจ
midi_path = audio_path.replace('.wav', '_basic_pitch.mid')
if not os.path.exists(midi_path):
# ํ์ผ๋ช
ํจํด์ด ๋ค๋ฅผ ์ ์์ผ๋ ๊ฒ์
dir_path = os.path.dirname(audio_path)
mid_files = [f for f in os.listdir(dir_path) if f.endswith('.mid')]
if mid_files:
midi_path = os.path.join(dir_path, mid_files[0])
else:
raise FileNotFoundError("MIDI ํ์ผ ์์ฑ ์คํจ")
print(f"โ
ํผ์น ์ถ์ถ ์๋ฃ: {midi_path}")
return midi_path
except Exception as e:
print(f"โ ํผ์น ์ถ์ถ ์คํจ: {e}")
# ํผ์น ์ถ์ถ ์คํจ ์์๋ ์งํ์ ๊ฐ๋ฅํ๋ ๊ฒฝ๊ณ
print("โ ๏ธ ํผ์น ์ถ์ถ ์์ด ๊ธฐ๋ณธ ์์ ์ผ๋ก ์งํํฉ๋๋ค.")
return None
def load_audio(path, target_sr=44100):
"""์ค๋์ค ๋ก๋ ๋ฐ ์ํ๋ง ๋ ์ดํธ ํต์ผ"""
y, sr = librosa.load(path, sr=target_sr)
return y, sr
def save_audio(path, data, sr):
"""์ค๋์ค ์ ์ฅ"""
sf.write(path, data, sr)
def mix_audio(instrumental, new_vocal, output_path, vocal_gain=1.0):
"""3 ๋จ๊ณ: ๋ฐ์ฃผ์ ์๋ก์ด ๋ณด์ปฌ์ ๋ฏน์ฑ"""
print("\n๐๏ธ [3/4] ๋ฐ์ฃผ์ ์ ๋ณด์ปฌ์ ๋ฏน์ฑ ์ค์
๋๋ค...")
# ๋ก๋
inst, sr = load_audio(instrumental)
vocal, _ = load_audio(new_vocal, sr)
# ๊ธธ์ด ๋ง์ถ๊ธฐ (์งง์ ์ชฝ์ ์ ๋ก ํจ๋ฉ)
length = max(len(inst), len(vocal))
inst = np.pad(inst, (0, length - len(inst)))
vocal = np.pad(vocal, (0, length - len(vocal)))
# ๊ฒ์ธ ์กฐ์ ๋ฐ ํฉ์น๊ธฐ
mixed = inst + (vocal * vocal_gain)
# ํด๋ฆฌํ ๋ฐฉ์ง (Normalize)
max_val = np.max(np.abs(mixed))
if max_val > 1.0:
mixed = mixed / max_val * 0.95
save_audio(output_path, mixed, sr)
print(f"โ
๋ฏน์ฑ ์๋ฃ: {output_path}")
def main():
# 0. ์ฌ์ ํ์ธ
if not os.path.exists(INPUT_AUDIO):
print(f"โ ์
๋ ฅ ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {INPUT_AUDIO}")
sys.exit(1)
os.makedirs(OUTPUT_DIR, exist_ok=True)
check_disk_space()
# 1. ๋ณด์ปฌ ๋ถ๋ฆฌ
orig_vocal_path, inst_path = separate_vocals(INPUT_AUDIO, OUTPUT_DIR)
# 2. ํผ์น ์ถ์ถ (์ ํ์ : ์ถํ SVS ๋ชจ๋ธ ์
๋ ฅ์ผ๋ก ์ฌ์ฉ)
midi_path = extract_pitch(orig_vocal_path, os.path.join(OUTPUT_DIR, "pitch.mid"))
# ---------------------------------------------------------
# [TODO] ์ฌ๊ธฐ์ ์งํ๊ด๋์ SVS ๋ชจ๋ธ (VocalRender/AxonVocal) ํธ์ถ ๋ก์ง ์ฝ์
# ํ์ฌ๋ ํ
์คํธ๋ฅผ ์ํด ์๋ณธ ๋ณด์ปฌ์ ์ฝ๊ฐ ๋ณ์กฐํ์ฌ '์ ๋ณด์ปฌ'๋ก ๊ฐ์ ํฉ๋๋ค.
# ์ค์ ๊ตฌํ ์์๋ render_full_song() ํจ์๋ฅผ ํธ์ถํ์ฌ new_vocal_path ๋ฅผ ์์ฑํ์ธ์.
# ---------------------------------------------------------
print("\n๐จ [Simulating] AI ๋ณด์ปฌ ํฉ์ฑ ๋จ๊ณ (SVS Model)...")
# ์์: ์๋ณธ ๋ณด์ปฌ์ ๊ทธ๋๋ก ์ฌ์ฉํ๊ฑฐ๋, pitch shift ๋ฑ์ ์ ์ฉํ ํ์ผ์ ์์ฑํ๋ค๊ณ ๊ฐ์
# ์ค์ ์ฝ๋์์๋ ์ฌ๊ธฐ์์ render_full_song(lyrics, midi_path) ๋ฑ์ ํธ์ถ
new_vocal_path = orig_vocal_path # ์์๋ก ์๋ณธ ๋ณด์ปฌ ์ฌ์ฌ์ฉ (์ค์ ๋ก AI ์์ฑ ํ์ผ ๊ฒฝ๋ก)
print("โ
(์๋ฎฌ๋ ์ด์
) AI ๋ณด์ปฌ ์์ฑ ์๋ฃ ๊ฐ์ ")
# 3. ๋ฏน์ฑ
final_output = os.path.join(OUTPUT_DIR, "final_axon_vocal_swap.wav")
mix_audio(inst_path, new_vocal_path, final_output, vocal_gain=1.2)
print("\n" + "="*40)
print("๐ ๋ชจ๋ ๊ณผ์ ์ด ์๋ฃ๋์์ต๋๋ค!")
print(f"๐ ๊ฒฐ๊ณผ๋ฌผ ์์น: {final_output}")
print("="*40)
if __name__ == "__main__":
main()์ ํ์ด์ฌ ์ฝ๋๋ฅผ ์ ์ฅํ๋ค๋ฉด, ์ด์ ์ํด๋ฆญ์ผ๋ก ์คํํฉ๋๋ค.
# ๊ฐ์ํ๊ฒฝ ํ์ฑํ (์์ง ์ผ์ ธ์์ง ์๋ค๋ฉด)
source venv/bin/activate
# ์คํฌ๋ฆฝํธ ์คํ
python run_axon_vocal.py| ์ฆ์ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
|---|---|
| No module named 'demucs' | pip install demucs ์ฌ์คํ ๋๋ venv ํ์ฑํ ํ์ธ |
| Disk full error | rm -rf ~/.cache/pip ์คํ ํ ์ฌ์๋ |
| FFmpeg not found | sudo apt-get install -y ffmpeg ์คํ |
| CUDA out of memory | GPU ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ. CPU ๋ชจ๋๋ก ์คํ๋๊ฑฐ๋ ๋ฐฐ์น ์ฌ์ด์ฆ ์ถ์ ํ์ |
| ์ ๊ธฐ ๋ชฉ์๋ฆฌ ๋ฐ์ | mix_audio ํจ์ ๋ด vocal_gain ์กฐ์ ๋๋ SVS ๋ชจ๋ธ์ pitch_shift ํ๋ผ๋ฏธํฐ ํ์ธ (-2.0 ๊ถ์ฅ) |
- GPU ๊ฐ์: NVIDIA GPU ๊ฐ ์๋ค๋ฉด
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118๋ก ์ค์นํ๋ฉด ์๋๊ฐ 10 ๋ฐฐ ์ด์ ๋นจ๋ผ์ง๋๋ค. - ๊ณ ์์ง ๋ชจ๋:
demucs์คํ ์-n htdemucs_ft์ต์ ์ ์ฌ์ฉํ๋ฉด ๋ ์ ๊ตํ ๋ถ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค (๋ชจ๋ธ ํฌ๊ธฐ ์ฆ๊ฐ). - ๋ฐฐ์น ์ฒ๋ฆฌ: ์ฌ๋ฌ ๊ณก์ ํ ๋ฒ์ ํ๋ ค๋ฉด
inputsํด๋์ ์ฌ๋ฌ ํ์ผ์ ๋ฃ๊ณ ์คํฌ๋ฆฝํธ๋ฅผ ์์ ํ์ฌ ๋ฃจํ๋ฅผ ๋๋ฆฌ๋ฉด ๋ฉ๋๋ค.
์ด ๊ฐ์ด๋๋๋ก ์งํํ๋ฉด ๋์คํฌ ์ฉ๋ ๋ฌธ์ ํด๊ฒฐ๋ถํฐ ๋ณด์ปฌ ์ค์ ์์ฑ๊น์ง ๋งํ์์ด ์ํํ์ค ์ ์์ต๋๋ค! ๐