|
import os |
|
from pathlib import Path |
|
from typing import List |
|
from loguru import logger as log |
|
|
|
import requests |
|
import streamlit as st |
|
from streamlit_option_menu import option_menu |
|
|
|
from footer import footer |
|
from header import header |
|
from helpers import ( |
|
load_audio_segment, |
|
load_list_of_songs, |
|
plot_audio, |
|
url_is_valid, |
|
file_size_is_valid, |
|
delete_old_files, |
|
) |
|
from service.demucs_runner import separator |
|
from service.vocal_remover.runner import load_model, separate |
|
from style import CSS_TABS |
|
|
|
|
|
label_sources = { |
|
"no_vocals.mp3": "๐ถ Instrumental", |
|
"vocals.mp3": "๐ค Vocals", |
|
"drums.mp3": "๐ฅ Drums", |
|
"bass.mp3": "๐ธ Bass", |
|
"guitar.mp3": "๐ธ Guitar", |
|
"piano.mp3": "๐น Piano", |
|
"other.mp3": "๐ถ Other", |
|
} |
|
|
|
separation_mode_to_model = { |
|
"Vocals & Instrumental (Low Quality, Faster)": ( |
|
"vocal_remover", |
|
["vocals.mp3", "no_vocals.mp3"], |
|
), |
|
"Vocals & Instrumental (High Quality, Slower)": ("htdemucs", ["vocals.mp3", "no_vocals.mp3"]), |
|
"Vocals, Drums, Bass & Other (Slower)": ( |
|
"htdemucs", |
|
["vocals.mp3", "drums.mp3", "bass.mp3", "other.mp3"], |
|
), |
|
"Vocal, Drums, Bass, Guitar, Piano & Other (Slowest)": ( |
|
"htdemucs_6s", |
|
["vocals.mp3", "drums.mp3", "bass.mp3", "guitar.mp3", "piano.mp3", "other.mp3"], |
|
), |
|
} |
|
|
|
extensions = ["mp3", "wav", "ogg", "flac"] |
|
|
|
out_path = Path("/tmp") |
|
in_path = Path("/tmp") |
|
|
|
|
|
@st.cache_data(show_spinner=False) |
|
def get_sources(path, file_sources): |
|
sources = {} |
|
for file in file_sources: |
|
fullpath = path / file |
|
if fullpath.exists(): |
|
sources[file] = fullpath |
|
return sources |
|
|
|
|
|
def reset_execution(): |
|
st.session_state.executed = False |
|
|
|
|
|
def show_results(model_name: str, dir_name_output: str, file_sources: List): |
|
sources = get_sources(out_path / Path(model_name) / dir_name_output, file_sources) |
|
tab_sources = st.tabs([f"**{label_sources.get(k)}**" for k in sources.keys()]) |
|
for i, (file, pathname) in enumerate(sources.items()): |
|
with tab_sources[i]: |
|
cols = st.columns(2) |
|
with cols[0]: |
|
auseg = load_audio_segment(pathname, "mp3") |
|
st.image( |
|
plot_audio( |
|
auseg, |
|
32767, |
|
file=file, |
|
model_name=model_name, |
|
dir_name_output=dir_name_output, |
|
), |
|
use_column_width="always", |
|
) |
|
with cols[1]: |
|
st.audio(str(pathname)) |
|
log.info(f"Displaying results for {dir_name_output} - {model_name}") |
|
|
|
|
|
def body(): |
|
filename = None |
|
name_song = None |
|
st.markdown( |
|
"<h4><center>Extract Vocals & Instrumental from any song</center></h4>", |
|
unsafe_allow_html=True, |
|
) |
|
st.markdown(CSS_TABS, unsafe_allow_html=True) |
|
|
|
cols = st.columns([1, 4, 1, 3, 1]) |
|
|
|
with cols[1]: |
|
with st.columns([1, 8, 1])[1]: |
|
option = option_menu( |
|
menu_title=None, |
|
options=["Examples", "Upload File", "From URL"], |
|
icons=["cloud-upload-fill", "link-45deg", "music-note-list"], |
|
orientation="horizontal", |
|
styles={ |
|
"container": { |
|
"width": "100%", |
|
"height": "3.5rem", |
|
"margin": "0px", |
|
"padding": "0px", |
|
}, |
|
"icon": {"font-size": "1rem"}, |
|
"nav-link": { |
|
"display": "flex", |
|
"height": "3rem", |
|
"justify-content": "center", |
|
"align-items": "center", |
|
"text-align": "center", |
|
"flex-direction": "column", |
|
"font-size": "1rem", |
|
"padding-left": "0px", |
|
"padding-right": "0px", |
|
}, |
|
}, |
|
key="option_separate", |
|
) |
|
if option == "Examples": |
|
samples_song = load_list_of_songs(path="separate_songs.json") |
|
if samples_song is not None: |
|
name_song = st.selectbox( |
|
label="Select a sample song and listen to sources separated", |
|
options=list(samples_song.keys()) + [""], |
|
format_func=lambda x: x.replace("_", " "), |
|
index=len(samples_song), |
|
key="select_example", |
|
) |
|
full_path = f"{in_path}/{name_song}" |
|
if name_song != "" and Path(full_path).exists(): |
|
st.audio(full_path) |
|
else: |
|
name_song = None |
|
|
|
elif option == "Upload File": |
|
uploaded_file = st.file_uploader( |
|
"Choose a file", |
|
type=extensions, |
|
key="file", |
|
help="Supported formats: mp3, wav, ogg, flac.", |
|
) |
|
if uploaded_file is not None: |
|
with st.spinner("Loading audio..."): |
|
with open(in_path / uploaded_file.name, "wb") as f: |
|
f.write(uploaded_file.getbuffer()) |
|
filename = uploaded_file.name |
|
st.audio(uploaded_file) |
|
|
|
elif option == "From URL": |
|
url = st.text_input( |
|
"Paste the URL of the audio file", |
|
key="url_input", |
|
help="Supported formats: mp3, wav, ogg, flac.", |
|
) |
|
|
|
if url != "" and url_is_valid(url): |
|
with st.spinner("Downloading audio..."): |
|
filename = url.split("/")[-1] |
|
response = requests.get(url, stream=True) |
|
if response.status_code == 200 and file_size_is_valid( |
|
response.headers.get("Content-Length") |
|
): |
|
file_size = 0 |
|
with open(in_path / filename, "wb") as audio_file: |
|
for chunk in response.iter_content(chunk_size=1024): |
|
if chunk: |
|
audio_file.write(chunk) |
|
file_size += len(chunk) |
|
if not file_size_is_valid(file_size): |
|
audio_file.close() |
|
os.remove(in_path / filename) |
|
filename = None |
|
return |
|
st.audio(f"{in_path}/{filename}") |
|
else: |
|
st.error( |
|
"Failed to download audio file. Try to download it manually and upload it." |
|
) |
|
filename = None |
|
|
|
with cols[3]: |
|
separation_mode = st.selectbox( |
|
"Choose the separation mode", |
|
[ |
|
"Vocals & Instrumental (Low Quality, Faster)", |
|
"Vocals & Instrumental (High Quality, Slower)", |
|
"Vocals, Drums, Bass & Other (Slower)", |
|
"Vocal, Drums, Bass, Guitar, Piano & Other (Slowest)", |
|
], |
|
on_change=reset_execution(), |
|
key="separation_mode", |
|
) |
|
if separation_mode == "Vocals & Instrumental (Low Quality, Faster)": |
|
max_duration = 30 |
|
else: |
|
max_duration = 15 |
|
model_name, file_sources = separation_mode_to_model[separation_mode] |
|
|
|
if filename is not None: |
|
song = load_audio_segment(in_path / filename, filename.split(".")[-1]) |
|
n_secs = round(len(song) / 1000) |
|
if os.environ.get("ENV_LIMITATION", False): |
|
with cols[3]: |
|
start_time = st.number_input( |
|
"Choose the start time", |
|
min_value=0, |
|
max_value=n_secs, |
|
step=1, |
|
value=0, |
|
help=f"Maximum duration is {max_duration} seconds for this separation mode.\nDuplicate this space to [remove any limit](https://github.com/fabiogra/moseca#are-there-any-limitations).", |
|
format="%d", |
|
) |
|
st.session_state.start_time = start_time |
|
end_time = min(start_time + max_duration, n_secs) |
|
song = song[start_time * 1000 : end_time * 1000] |
|
st.info( |
|
f"Audio source will be processed from {start_time} to {end_time} seconds.\nDuplicate this space to [remove any limit](https://github.com/fabiogra/moseca#are-there-any-limitations).", |
|
icon="โฑ", |
|
) |
|
else: |
|
start_time = 0 |
|
end_time = n_secs |
|
with st.columns([2, 1, 2])[1]: |
|
execute = st.button( |
|
"Separate Music Sources ๐ถ", type="primary", use_container_width=True |
|
) |
|
if execute or st.session_state.executed: |
|
if execute: |
|
st.session_state.executed = False |
|
|
|
if not st.session_state.executed: |
|
log.info(f"{option} - Separating {filename} with {separation_mode}...") |
|
song.export(in_path / filename, format=filename.split(".")[-1]) |
|
with st.columns([1, 1, 1])[1]: |
|
with st.spinner("Separating source audio, it will take a while..."): |
|
if model_name == "vocal_remover": |
|
model, device = load_model(pretrained_model="baseline.pth") |
|
separate( |
|
input=in_path / filename, |
|
model=model, |
|
device=device, |
|
output_dir=out_path, |
|
) |
|
else: |
|
stem = None |
|
if separation_mode == "Vocals & Instrumental (High Quality, Slower)": |
|
stem = "vocals" |
|
|
|
separator( |
|
tracks=[in_path / filename], |
|
out=out_path, |
|
model=model_name, |
|
shifts=1, |
|
overlap=0.5, |
|
stem=stem, |
|
int24=False, |
|
float32=False, |
|
clip_mode="rescale", |
|
mp3=True, |
|
mp3_bitrate=320, |
|
verbose=True, |
|
start_time=start_time, |
|
end_time=end_time, |
|
) |
|
dir_name_output = ".".join(filename.split(".")[:-1]) |
|
filename = None |
|
st.session_state.executed = True |
|
show_results(model_name, dir_name_output, file_sources) |
|
elif name_song is not None and option == "Examples": |
|
show_results(model_name, name_song, file_sources) |
|
|
|
|
|
if __name__ == "__main__": |
|
header() |
|
body() |
|
footer() |
|
delete_old_files("/tmp", 60 * 30) |
|
|